Merge "Only reset time from NITZ in zone fix." into jb-dev
diff --git a/api/16.txt b/api/16.txt
index 2c02347..81efd34 100644
--- a/api/16.txt
+++ b/api/16.txt
@@ -9243,7 +9243,6 @@
     method public int getMinimumWidth();
     method public abstract int getOpacity();
     method public boolean getPadding(android.graphics.Rect);
-    method public int getResolvedLayoutDirectionSelf();
     method public int[] getState();
     method public android.graphics.Region getTransparentRegion();
     method public void inflate(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
@@ -27329,7 +27328,7 @@
     field public int gravity;
   }
 
-  public class Gallery extends android.widget.AbsSpinner implements android.view.GestureDetector.OnGestureListener {
+  public deprecated class Gallery extends android.widget.AbsSpinner implements android.view.GestureDetector.OnGestureListener {
     ctor public Gallery(android.content.Context);
     ctor public Gallery(android.content.Context, android.util.AttributeSet);
     ctor public Gallery(android.content.Context, android.util.AttributeSet, int);
@@ -28620,8 +28619,6 @@
     method public void onEndBatchEdit();
     method public boolean onPreDraw();
     method public boolean onPrivateIMECommand(java.lang.String, android.os.Bundle);
-    method public void onResolvedLayoutDirectionReset();
-    method public void onResolvedTextDirectionChanged();
     method public void onRestoreInstanceState(android.os.Parcelable);
     method public android.os.Parcelable onSaveInstanceState();
     method protected void onSelectionChanged(int, int);
diff --git a/api/current.txt b/api/current.txt
index 2c02347..81efd34 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9243,7 +9243,6 @@
     method public int getMinimumWidth();
     method public abstract int getOpacity();
     method public boolean getPadding(android.graphics.Rect);
-    method public int getResolvedLayoutDirectionSelf();
     method public int[] getState();
     method public android.graphics.Region getTransparentRegion();
     method public void inflate(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
@@ -27329,7 +27328,7 @@
     field public int gravity;
   }
 
-  public class Gallery extends android.widget.AbsSpinner implements android.view.GestureDetector.OnGestureListener {
+  public deprecated class Gallery extends android.widget.AbsSpinner implements android.view.GestureDetector.OnGestureListener {
     ctor public Gallery(android.content.Context);
     ctor public Gallery(android.content.Context, android.util.AttributeSet);
     ctor public Gallery(android.content.Context, android.util.AttributeSet, int);
@@ -28620,8 +28619,6 @@
     method public void onEndBatchEdit();
     method public boolean onPreDraw();
     method public boolean onPrivateIMECommand(java.lang.String, android.os.Bundle);
-    method public void onResolvedLayoutDirectionReset();
-    method public void onResolvedTextDirectionChanged();
     method public void onRestoreInstanceState(android.os.Parcelable);
     method public android.os.Parcelable onSaveInstanceState();
     method protected void onSelectionChanged(int, int);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index faf946c..f20fd33 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -3273,7 +3273,7 @@
         if (mMenuInflater == null) {
             initActionBar();
             if (mActionBar != null) {
-                mMenuInflater = new MenuInflater(mActionBar.getThemedContext());
+                mMenuInflater = new MenuInflater(mActionBar.getThemedContext(), this);
             } else {
                 mMenuInflater = new MenuInflater(this);
             }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 2eea171..c7afd2b2 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1850,7 +1850,7 @@
             contentView.setViewVisibility(R.id.text2, View.GONE);
 
             int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
-                    R.id.inbox_text4};
+                    R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
 
             // Make sure all rows are gone in case we reuse a view.
             for (int rowId : rowIds) {
@@ -1867,6 +1867,12 @@
                 i++;
             }
 
+            if  (mTexts.size() > rowIds.length) {
+                contentView.setViewVisibility(R.id.inbox_more, View.VISIBLE);
+            } else {
+                contentView.setViewVisibility(R.id.inbox_more, View.GONE);
+            }
+
             return contentView;
         }
 
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 3137947..9b6f82a 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -41,8 +41,13 @@
     // Keyboard layouts configuration.
     KeyboardLayout[] getKeyboardLayouts();
     KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor);
-    String getKeyboardLayoutForInputDevice(String inputDeviceDescriptor);
-    void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+    String getCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor);
+    void setCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+            String keyboardLayoutDescriptor);
+    String[] getKeyboardLayoutsForInputDevice(String inputDeviceDescriptor);
+    void addKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+            String keyboardLayoutDescriptor);
+    void removeKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
             String keyboardLayoutDescriptor);
 
     // Registers an input devices changed listener.
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index dfd35e1..262d87d 100755
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -16,6 +16,8 @@
 
 package android.hardware.input;
 
+import com.android.internal.util.ArrayUtils;
+
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.content.Context;
@@ -217,6 +219,41 @@
     }
 
     /**
+     * Gets information about the input device with the specified descriptor.
+     * @param descriptor The input device descriptor.
+     * @return The input device or null if not found.
+     * @hide
+     */
+    public InputDevice getInputDeviceByDescriptor(String descriptor) {
+        if (descriptor == null) {
+            throw new IllegalArgumentException("descriptor must not be null.");
+        }
+
+        synchronized (mInputDevicesLock) {
+            populateInputDevicesLocked();
+
+            int numDevices = mInputDevices.size();
+            for (int i = 0; i < numDevices; i++) {
+                InputDevice inputDevice = mInputDevices.valueAt(i);
+                if (inputDevice == null) {
+                    int id = mInputDevices.keyAt(i);
+                    try {
+                        inputDevice = mIm.getInputDevice(id);
+                    } catch (RemoteException ex) {
+                        // Ignore the problem for the purposes of this method.
+                        continue;
+                    }
+                    mInputDevices.setValueAt(i, inputDevice);
+                }
+                if (descriptor.equals(inputDevice.getDescriptor())) {
+                    return inputDevice;
+                }
+            }
+            return null;
+        }
+    }
+
+    /**
      * Gets the ids of all input devices in the system.
      * @return The input device ids.
      */
@@ -332,50 +369,129 @@
     }
 
     /**
-     * Gets the keyboard layout descriptor for the specified input device.
+     * Gets the current keyboard layout descriptor for the specified input device.
      *
      * @param inputDeviceDescriptor The input device descriptor.
-     * @return The keyboard layout descriptor, or null if unknown or if the default
-     * keyboard layout will be used.
+     * @return The keyboard layout descriptor, or null if no keyboard layout has been set.
      *
      * @hide
      */
-    public String getKeyboardLayoutForInputDevice(String inputDeviceDescriptor) {
+    public String getCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor) {
         if (inputDeviceDescriptor == null) {
             throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
         }
 
         try {
-            return mIm.getKeyboardLayoutForInputDevice(inputDeviceDescriptor);
+            return mIm.getCurrentKeyboardLayoutForInputDevice(inputDeviceDescriptor);
         } catch (RemoteException ex) {
-            Log.w(TAG, "Could not get keyboard layout for input device.", ex);
+            Log.w(TAG, "Could not get current keyboard layout for input device.", ex);
             return null;
         }
     }
 
     /**
-     * Sets the keyboard layout descriptor for the specified input device.
+     * Sets the current keyboard layout descriptor for the specified input device.
      * <p>
      * This method may have the side-effect of causing the input device in question
      * to be reconfigured.
      * </p>
      *
      * @param inputDeviceDescriptor The input device descriptor.
-     * @param keyboardLayoutDescriptor The keyboard layout descriptor, or null to remove
-     * the mapping so that the default keyboard layout will be used for the input device.
+     * @param keyboardLayoutDescriptor The keyboard layout descriptor to use, must not be null.
      *
      * @hide
      */
-    public void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+    public void setCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
             String keyboardLayoutDescriptor) {
         if (inputDeviceDescriptor == null) {
             throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
         }
+        if (keyboardLayoutDescriptor == null) {
+            throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+        }
+
+        try {
+            mIm.setCurrentKeyboardLayoutForInputDevice(inputDeviceDescriptor,
+                    keyboardLayoutDescriptor);
+        } catch (RemoteException ex) {
+            Log.w(TAG, "Could not set current keyboard layout for input device.", ex);
+        }
+    }
+
+    /**
+     * Gets all keyboard layout descriptors that are enabled for the specified input device.
+     *
+     * @param inputDeviceDescriptor The input device descriptor.
+     * @return The keyboard layout descriptors.
+     *
+     * @hide
+     */
+    public String[] getKeyboardLayoutsForInputDevice(String inputDeviceDescriptor) {
+        if (inputDeviceDescriptor == null) {
+            throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+        }
 
         try {
-            mIm.setKeyboardLayoutForInputDevice(inputDeviceDescriptor, keyboardLayoutDescriptor);
+            return mIm.getKeyboardLayoutsForInputDevice(inputDeviceDescriptor);
         } catch (RemoteException ex) {
-            Log.w(TAG, "Could not set keyboard layout for input device.", ex);
+            Log.w(TAG, "Could not get keyboard layouts for input device.", ex);
+            return ArrayUtils.emptyArray(String.class);
+        }
+    }
+
+    /**
+     * Adds the keyboard layout descriptor for the specified input device.
+     * <p>
+     * This method may have the side-effect of causing the input device in question
+     * to be reconfigured.
+     * </p>
+     *
+     * @param inputDeviceDescriptor The input device descriptor.
+     * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to add.
+     *
+     * @hide
+     */
+    public void addKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+            String keyboardLayoutDescriptor) {
+        if (inputDeviceDescriptor == null) {
+            throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+        }
+        if (keyboardLayoutDescriptor == null) {
+            throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+        }
+
+        try {
+            mIm.addKeyboardLayoutForInputDevice(inputDeviceDescriptor, keyboardLayoutDescriptor);
+        } catch (RemoteException ex) {
+            Log.w(TAG, "Could not add keyboard layout for input device.", ex);
+        }
+    }
+
+    /**
+     * Removes the keyboard layout descriptor for the specified input device.
+     * <p>
+     * This method may have the side-effect of causing the input device in question
+     * to be reconfigured.
+     * </p>
+     *
+     * @param inputDeviceDescriptor The input device descriptor.
+     * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to remove.
+     *
+     * @hide
+     */
+    public void removeKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+            String keyboardLayoutDescriptor) {
+        if (inputDeviceDescriptor == null) {
+            throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+        }
+        if (keyboardLayoutDescriptor == null) {
+            throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+        }
+
+        try {
+            mIm.removeKeyboardLayoutForInputDevice(inputDeviceDescriptor, keyboardLayoutDescriptor);
+        } catch (RemoteException ex) {
+            Log.w(TAG, "Could not remove keyboard layout for input device.", ex);
         }
     }
 
diff --git a/core/java/android/view/MenuInflater.java b/core/java/android/view/MenuInflater.java
index c46e43a..a7ee12b 100644
--- a/core/java/android/view/MenuInflater.java
+++ b/core/java/android/view/MenuInflater.java
@@ -65,6 +65,7 @@
     private final Object[] mActionProviderConstructorArguments;
 
     private Context mContext;
+    private Object mRealOwner;
 
     /**
      * Constructs a menu inflater.
@@ -73,6 +74,20 @@
      */
     public MenuInflater(Context context) {
         mContext = context;
+        mRealOwner = context;
+        mActionViewConstructorArguments = new Object[] {context};
+        mActionProviderConstructorArguments = mActionViewConstructorArguments;
+    }
+
+    /**
+     * Constructs a menu inflater.
+     *
+     * @see Activity#getMenuInflater()
+     * @hide
+     */
+    public MenuInflater(Context context, Object realOwner) {
+        mContext = context;
+        mRealOwner = realOwner;
         mActionViewConstructorArguments = new Object[] {context};
         mActionProviderConstructorArguments = mActionViewConstructorArguments;
     }
@@ -190,12 +205,12 @@
             implements MenuItem.OnMenuItemClickListener {
         private static final Class<?>[] PARAM_TYPES = new Class[] { MenuItem.class };
         
-        private Context mContext;
+        private Object mRealOwner;
         private Method mMethod;
         
-        public InflatedOnMenuItemClickListener(Context context, String methodName) {
-            mContext = context;
-            Class<?> c = context.getClass();
+        public InflatedOnMenuItemClickListener(Object realOwner, String methodName) {
+            mRealOwner = realOwner;
+            Class<?> c = realOwner.getClass();
             try {
                 mMethod = c.getMethod(methodName, PARAM_TYPES);
             } catch (Exception e) {
@@ -210,9 +225,9 @@
         public boolean onMenuItemClick(MenuItem item) {
             try {
                 if (mMethod.getReturnType() == Boolean.TYPE) {
-                    return (Boolean) mMethod.invoke(mContext, item);
+                    return (Boolean) mMethod.invoke(mRealOwner, item);
                 } else {
-                    mMethod.invoke(mContext, item);
+                    mMethod.invoke(mRealOwner, item);
                     return true;
                 }
             } catch (Exception e) {
@@ -400,7 +415,7 @@
                             + "be used within a restricted context");
                 }
                 item.setOnMenuItemClickListener(
-                        new InflatedOnMenuItemClickListener(mContext, itemListenerMethodName));
+                        new InflatedOnMenuItemClickListener(mRealOwner, itemListenerMethodName));
             }
 
             if (item instanceof MenuItemImpl) {
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 0c5d6ea..ceb9fe6 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -386,6 +386,12 @@
          */
         public InputChannel monitorInput(String name);
 
+        /**
+         * Switch the keyboard layout for the given device.
+         * Direction should be +1 or -1 to go to the next or previous keyboard layout.
+         */
+        public void switchKeyboardLayout(int deviceId, int direction);
+
         public void shutdown();
         public void rebootSafeMode();
     }
diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java
index 6658fc2..e4e7239 100644
--- a/core/java/android/widget/Gallery.java
+++ b/core/java/android/widget/Gallery.java
@@ -56,7 +56,12 @@
  * @attr ref android.R.styleable#Gallery_animationDuration
  * @attr ref android.R.styleable#Gallery_spacing
  * @attr ref android.R.styleable#Gallery_gravity
+ * 
+ * @deprecated This widget is no longer supported. Other horizontally scrolling
+ * widgets include {@link HorizontalScrollView} and {@link android.support.v4.view.ViewPager}
+ * from the support library.
  */
+@Deprecated
 @Widget
 public class Gallery extends AbsSpinner implements GestureDetector.OnGestureListener {
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 112881c..aef7929 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -5625,6 +5625,7 @@
                       physicalWidth, false);
     }
 
+    /** @hide */
     @Override
     public void onResolvedLayoutDirectionReset() {
         if (mLayoutAlignment != null) {
@@ -8161,6 +8162,7 @@
         return mEditor.mInBatchEditControllers;
     }
 
+    /** @hide */
     @Override
     public void onResolvedTextDirectionChanged() {
         if (hasPasswordTransformationMethod()) {
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index 88d7e05..fafc113 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -120,7 +120,12 @@
      */
     public void cancel() {
         mTN.hide();
-        // TODO this still needs to cancel the inflight notification if any
+
+        try {
+            getService().cancelToast(mContext.getPackageName(), mTN);
+        } catch (RemoteException e) {
+            // Empty
+        }
     }
     
     /**
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index e3d11f2..82f8984 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -774,11 +774,12 @@
     float transform[16];
     sp<SurfaceTexture> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, surface));
 
-    surfaceTexture->updateTexImage();
-    surfaceTexture->getTransformMatrix(transform);
-    GLenum renderTarget = surfaceTexture->getCurrentTextureTarget();
+    if (surfaceTexture->updateTexImage() == NO_ERROR) {
+        surfaceTexture->getTransformMatrix(transform);
+        GLenum renderTarget = surfaceTexture->getCurrentTextureTarget();
 
-    LayerRenderer::updateTextureLayer(layer, width, height, isOpaque, renderTarget, transform);
+        LayerRenderer::updateTextureLayer(layer, width, height, isOpaque, renderTarget, transform);
+    }
 }
 
 static void android_view_GLES20Canvas_updateRenderLayer(JNIEnv* env, jobject clazz,
diff --git a/core/res/res/drawable-hdpi/ic_settings_language.png b/core/res/res/drawable-hdpi/ic_settings_language.png
new file mode 100755
index 0000000..f635b2e
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_settings_language.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_settings_language.png b/core/res/res/drawable-mdpi/ic_settings_language.png
new file mode 100644
index 0000000..f8aca67
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_settings_language.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_settings_language.png b/core/res/res/drawable-xhdpi/ic_settings_language.png
new file mode 100644
index 0000000..2c42db3
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_settings_language.png
Binary files differ
diff --git a/core/res/res/layout/notification_template_inbox.xml b/core/res/res/layout/notification_template_inbox.xml
index eb5e759..3b00feb 100644
--- a/core/res/res/layout/notification_template_inbox.xml
+++ b/core/res/res/layout/notification_template_inbox.xml
@@ -132,6 +132,34 @@
             android:visibility="gone"
             android:layout_weight="1"
             />
+        <TextView android:id="@+id/inbox_text5"
+            android:textAppearance="@style/TextAppearance.StatusBar.EventContent"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:visibility="gone"
+            android:layout_weight="1"
+            />
+        <TextView android:id="@+id/inbox_text6"
+            android:textAppearance="@style/TextAppearance.StatusBar.EventContent"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:visibility="gone"
+            android:layout_weight="1"
+            />
+        <TextView android:id="@+id/inbox_more"
+            android:textAppearance="@style/TextAppearance.StatusBar.EventContent"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:visibility="gone"
+            android:layout_weight="1"
+            android:text="@android:string/ellipsis"
+            />
         <include
             layout="@layout/notification_action_list"
             android:id="@+id/actions"
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index a24e345c..9829d89 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -208,6 +208,9 @@
   <java-symbol type="id" name="inbox_text2" />
   <java-symbol type="id" name="inbox_text3" />
   <java-symbol type="id" name="inbox_text4" />
+  <java-symbol type="id" name="inbox_text5" />
+  <java-symbol type="id" name="inbox_text6" />
+  <java-symbol type="id" name="inbox_more" />
   <java-symbol type="id" name="status_bar_latest_event_content" />
 
   <java-symbol type="attr" name="actionModeShareDrawable" />
@@ -1489,6 +1492,8 @@
   <java-symbol type="string" name="low_internal_storage_view_title" />
   <java-symbol type="string" name="report" />
   <java-symbol type="string" name="select_input_method" />
+  <java-symbol type="string" name="select_keyboard_layout_notification_title" />
+  <java-symbol type="string" name="select_keyboard_layout_notification_message" />
   <java-symbol type="string" name="smv_application" />
   <java-symbol type="string" name="smv_process" />
   <java-symbol type="string" name="tethered_notification_message" />
@@ -1580,6 +1585,7 @@
   <java-symbol type="drawable" name="expander_ic_minimized" />
   <java-symbol type="drawable" name="ic_menu_archive" />
   <java-symbol type="drawable" name="ic_menu_goto" />
+  <java-symbol type="drawable" name="ic_settings_language" />
   <java-symbol type="drawable" name="title_bar_medium" />
   <java-symbol type="id" name="body" />
   <java-symbol type="string" name="fast_scroll_alphabet" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 4511201..d50e2de 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3092,6 +3092,11 @@
     <!-- Title of the physical keyboard category in the input method selector [CHAR LIMIT=10] -->
     <string name="hardware">Hardware</string>
 
+    <!-- Title of the notification to prompt the user to select a keyboard layout. -->
+    <string name="select_keyboard_layout_notification_title">Select keyboard layout</string>
+    <!-- Message of the notification to prompt the user to select a keyboard layout. -->
+    <string name="select_keyboard_layout_notification_message">Touch to select a keyboard layout.</string>
+
     <string name="fast_scroll_alphabet">\u0020ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
     <string name="fast_scroll_numeric_alphabet">\u00200123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
 
@@ -3572,6 +3577,6 @@
 
     <!-- Title for a button to choose the currently selected activity
          from the activity resolver to use just this once. [CHAR LIMIT=25] -->
-    <string name="activity_resolver_use_once">Just Once</string>
+    <string name="activity_resolver_use_once">Just once</string>
 
 </resources>
diff --git a/data/keyboards/Generic.kcm b/data/keyboards/Generic.kcm
index 544076f..782a701 100644
--- a/data/keyboards/Generic.kcm
+++ b/data/keyboards/Generic.kcm
@@ -252,6 +252,7 @@
     label:                              ' '
     base:                               ' '
     alt, meta:                          fallback SEARCH
+    ctrl:                               fallback LANGUAGE_SWITCH
 }
 
 key ENTER {
diff --git a/data/keyboards/Virtual.kcm b/data/keyboards/Virtual.kcm
index e592013..d90b790 100644
--- a/data/keyboards/Virtual.kcm
+++ b/data/keyboards/Virtual.kcm
@@ -249,6 +249,7 @@
     label:                              ' '
     base:                               ' '
     alt, meta:                          fallback SEARCH
+    ctrl:                               fallback LANGUAGE_SWITCH
 }
 
 key ENTER {
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 5f74c01..785582c 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -386,6 +386,7 @@
 
     /**
      * Get the resolved layout direction of this Drawable.
+     * @hide
      */
     public int getResolvedLayoutDirectionSelf() {
         final Callback callback = getCallback();
diff --git a/media/mca/filterpacks/java/android/filterpacks/videosink/MediaEncoderFilter.java b/media/mca/filterpacks/java/android/filterpacks/videosink/MediaEncoderFilter.java
index 3657d8a..d8aa40f 100644
--- a/media/mca/filterpacks/java/android/filterpacks/videosink/MediaEncoderFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/videosink/MediaEncoderFilter.java
@@ -376,8 +376,6 @@
 
     @Override
     public void process(FilterContext context) {
-        if (mLogVerbose) Log.v(TAG, "Starting frame processing");
-
         GLEnvironment glEnv = context.getGLEnvironment();
         // Get input frame
         Frame input = pullInput("videoframe");
diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java
index b023e42..674a2bd 100644
--- a/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java
+++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java
@@ -121,6 +121,7 @@
     }
 
     public void updateRenderMode() {
+        if (mLogVerbose) Log.v(TAG, "updateRenderMode. Thread: " + Thread.currentThread());
         if (mRenderModeString != null) {
             if (mRenderModeString.equals("stretch")) {
                 mRenderMode = RENDERMODE_STRETCH;
@@ -139,6 +140,7 @@
 
     @Override
     public void prepare(FilterContext context) {
+        if (mLogVerbose) Log.v(TAG, "Prepare. Thread: " + Thread.currentThread());
         // Create identity shader to render, and make sure to render upside-down, as textures
         // are stored internally bottom-to-top.
         mProgram = ShaderProgram.createIdentity(context);
@@ -214,8 +216,10 @@
         float currentAspectRatio =
           (float)input.getFormat().getWidth() / input.getFormat().getHeight();
         if (currentAspectRatio != mAspectRatio) {
-            if (mLogVerbose) Log.v(TAG, "New aspect ratio: " + currentAspectRatio +
-                ", previously: " + mAspectRatio);
+            if (mLogVerbose) {
+                Log.v(TAG, "Process. New aspect ratio: " + currentAspectRatio +
+                    ", previously: " + mAspectRatio + ". Thread: " + Thread.currentThread());
+            }
             mAspectRatio = currentAspectRatio;
             updateTargetRect();
         }
@@ -249,6 +253,7 @@
 
     @Override
     public void fieldPortValueUpdated(String name, FilterContext context) {
+        if (mLogVerbose) Log.v(TAG, "FPVU. Thread: " + Thread.currentThread());
         updateRenderMode();
     }
 
@@ -260,16 +265,22 @@
     }
 
     private void updateTargetRect() {
+        if (mLogVerbose) Log.v(TAG, "updateTargetRect. Thread: " + Thread.currentThread());
         if (mScreenWidth > 0 && mScreenHeight > 0 && mProgram != null) {
             float screenAspectRatio = (float)mScreenWidth / mScreenHeight;
             float relativeAspectRatio = screenAspectRatio / mAspectRatio;
+            if (mLogVerbose) {
+                Log.v(TAG, "UTR. screen w = " + (float)mScreenWidth + " x screen h = " +
+                    (float)mScreenHeight + " Screen AR: " + screenAspectRatio +
+                    ", frame AR: "  + mAspectRatio + ", relative AR: " + relativeAspectRatio);
+            }
 
             if (relativeAspectRatio == 1.0f && mRenderMode != RENDERMODE_CUSTOMIZE) {
+                mProgram.setTargetRect(0, 0, 1, 1);
                 mProgram.setClearsOutput(false);
             } else {
                 switch (mRenderMode) {
                     case RENDERMODE_STRETCH:
-                        mProgram.setTargetRect(0, 0, 1, 1);
                         mTargetQuad.p0.set(0f, 0.0f);
                         mTargetQuad.p1.set(1f, 0.0f);
                         mTargetQuad.p2.set(0f, 1.0f);
@@ -313,6 +324,7 @@
                         ((ShaderProgram) mProgram).setSourceRegion(mSourceQuad);
                         break;
                 }
+                if (mLogVerbose) Log.v(TAG,  "UTR. quad: " + mTargetQuad);
                 ((ShaderProgram) mProgram).setTargetRegion(mTargetQuad);
             }
         }
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index cee01ac..29de5c1 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -326,6 +326,7 @@
 
     RecentApplicationsDialog mRecentAppsDialog;
     int mRecentAppsDialogHeldModifiers;
+    boolean mLanguageSwitchKeyPressed;
 
     int mLidState = LID_ABSENT;
     boolean mHaveBuiltInKeyboard;
@@ -1943,6 +1944,22 @@
                     RECENT_APPS_BEHAVIOR_DISMISS_AND_SWITCH);
         }
 
+        // Handle keyboard language switching.
+        if (down && repeatCount == 0
+                && (keyCode == KeyEvent.KEYCODE_LANGUAGE_SWITCH
+                        || (keyCode == KeyEvent.KEYCODE_SPACE
+                                && (metaState & KeyEvent.META_CTRL_MASK) != 0))) {
+            int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
+            mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction);
+            return -1;
+        }
+        if (mLanguageSwitchKeyPressed && !down
+                && (keyCode == KeyEvent.KEYCODE_LANGUAGE_SWITCH
+                        || keyCode == KeyEvent.KEYCODE_SPACE)) {
+            mLanguageSwitchKeyPressed = false;
+            return -1;
+        }
+
         // Let the application handle the key.
         return 0;
     }
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index a49ccf7..ff14568 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -195,6 +195,7 @@
     private PendingIntent mImeSwitchPendingIntent;
     private boolean mShowOngoingImeSwitcherForPhones;
     private boolean mNotificationShown;
+    private final boolean mImeSelectedOnBoot;
 
     class SessionState {
         final ClientState client;
@@ -590,7 +591,6 @@
         mImeSwitcherNotification.vibrate = null;
         Intent intent = new Intent(Settings.ACTION_SHOW_INPUT_METHOD_PICKER);
         mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
-        mLastSystemLocale = mRes.getConfiguration().locale;
 
         mShowOngoingImeSwitcherForPhones = false;
 
@@ -612,11 +612,17 @@
         // mSettings should be created before buildInputMethodListLocked
         mSettings = new InputMethodSettings(
                 mRes, context.getContentResolver(), mMethodMap, mMethodList);
+
+        // Just checking if defaultImiId is empty or not
+        final String defaultImiId = Settings.Secure.getString(
+                mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+        mImeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
+
         buildInputMethodListLocked(mMethodList, mMethodMap);
         mSettings.enableAllIMEsIfThereIsNoEnabledIME();
 
-        if (TextUtils.isEmpty(Settings.Secure.getString(
-                mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD))) {
+        if (!mImeSelectedOnBoot) {
+            Slog.w(TAG, "No IME selected. Choose the most applicable IME.");
             resetDefaultImeLocked(context);
         }
 
@@ -639,6 +645,10 @@
     }
 
     private void checkCurrentLocaleChangedLocked() {
+        if (!mSystemReady) {
+            // not system ready
+            return;
+        }
         final Locale newLocale = mRes.getConfiguration().locale;
         if (newLocale != null && !newLocale.equals(mLastSystemLocale)) {
             if (DEBUG) {
@@ -647,6 +657,7 @@
             buildInputMethodListLocked(mMethodList, mMethodMap);
             // Reset the current ime to the proper one
             resetDefaultImeLocked(mContext);
+            updateFromSettingsLocked();
             mLastSystemLocale = newLocale;
         }
     }
@@ -675,7 +686,10 @@
         }
     }
 
-    private static boolean isValidSystemDefaultIme(InputMethodInfo imi, Context context) {
+    private boolean isValidSystemDefaultIme(InputMethodInfo imi, Context context) {
+        if (!mSystemReady) {
+            return false;
+        }
         if (!isSystemIme(imi)) {
             return false;
         }
@@ -738,7 +752,6 @@
                         mContext.getSystemService(Context.KEYGUARD_SERVICE);
                 mNotificationManager = (NotificationManager)
                         mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-                mLastSystemLocale = mContext.getResources().getConfiguration().locale;
                 mStatusBar = statusBar;
                 statusBar.setIconVisibility("ime", false);
                 updateImeWindowStatusLocked();
@@ -748,6 +761,12 @@
                     mWindowManagerService.setOnHardKeyboardStatusChangeListener(
                             mHardKeyboardListener);
                 }
+                buildInputMethodListLocked(mMethodList, mMethodMap);
+                if (!mImeSelectedOnBoot) {
+                    Slog.w(TAG, "Reset the default IME as \"Resource\" is ready here.");
+                    checkCurrentLocaleChangedLocked();
+                }
+                mLastSystemLocale = mRes.getConfiguration().locale;
                 try {
                     startInputInnerLocked();
                 } catch (RuntimeException e) {
@@ -1421,34 +1440,41 @@
             throw new IllegalArgumentException("Unknown id: " + id);
         }
 
+        // See if we need to notify a subtype change within the same IME.
         if (id.equals(mCurMethodId)) {
-            InputMethodSubtype subtype = null;
-            if (subtypeId >= 0 && subtypeId < info.getSubtypeCount()) {
-                subtype = info.getSubtypeAt(subtypeId);
+            final int subtypeCount = info.getSubtypeCount();
+            if (subtypeCount <= 0) {
+                return;
             }
-            if (subtype != mCurrentSubtype) {
-                synchronized (mMethodMap) {
-                    if (subtype != null) {
-                        setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
-                    }
-                    if (mCurMethod != null) {
-                        try {
-                            refreshImeWindowVisibilityLocked();
-                            // If subtype is null, try to find the most applicable one from
-                            // getCurrentInputMethodSubtype.
-                            if (subtype == null) {
-                                subtype = getCurrentInputMethodSubtype();
-                            }
-                            mCurMethod.changeInputMethodSubtype(subtype);
-                        } catch (RemoteException e) {
-                            return;
-                        }
+            final InputMethodSubtype oldSubtype = mCurrentSubtype;
+            final InputMethodSubtype newSubtype;
+            if (subtypeId >= 0 && subtypeId < subtypeCount) {
+                newSubtype = info.getSubtypeAt(subtypeId);
+            } else {
+                // If subtype is null, try to find the most applicable one from
+                // getCurrentInputMethodSubtype.
+                newSubtype = getCurrentInputMethodSubtype();
+            }
+            if (newSubtype == null || oldSubtype == null) {
+                Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype
+                        + ", new subtype = " + newSubtype);
+                return;
+            }
+            if (newSubtype != oldSubtype) {
+                setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
+                if (mCurMethod != null) {
+                    try {
+                        refreshImeWindowVisibilityLocked();
+                        mCurMethod.changeInputMethodSubtype(newSubtype);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Failed to call changeInputMethodSubtype");
                     }
                 }
             }
             return;
         }
 
+        // Changing to a different IME.
         final long ident = Binder.clearCallingIdentity();
         try {
             // Set a subtype to this input method.
@@ -2137,7 +2163,6 @@
         return subtypes;
     }
 
-
     private static ArrayList<InputMethodSubtype> getOverridingImplicitlyEnabledSubtypes(
             InputMethodInfo imi, String mode) {
         ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
@@ -2155,15 +2180,19 @@
         List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
         if (enabled != null && enabled.size() > 0) {
             // We'd prefer to fall back on a system IME, since that is safer.
-            int i=enabled.size();
+            int i = enabled.size();
+            int firstFoundSystemIme = -1;
             while (i > 0) {
                 i--;
                 final InputMethodInfo imi = enabled.get(i);
-                if (isSystemIme(imi) && !imi.isAuxiliaryIme()) {
-                    break;
+                if (isSystemImeThatHasEnglishSubtype(imi) && !imi.isAuxiliaryIme()) {
+                    return imi;
+                }
+                if (firstFoundSystemIme < 0 && isSystemIme(imi) && !imi.isAuxiliaryIme()) {
+                    firstFoundSystemIme = i;
                 }
             }
-            return enabled.get(i);
+            return enabled.get(Math.max(firstFoundSystemIme, 0));
         }
         return null;
     }
@@ -2238,11 +2267,17 @@
             }
         }
 
-        String defaultIme = Settings.Secure.getString(mContext
+        final String defaultImiId = Settings.Secure.getString(mContext
                 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
-        if (!TextUtils.isEmpty(defaultIme) && !map.containsKey(defaultIme)) {
-            if (chooseNewDefaultIMELocked()) {
-                updateFromSettingsLocked();
+        if (!TextUtils.isEmpty(defaultImiId)) {
+            if (!map.containsKey(defaultImiId)) {
+                Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
+                if (chooseNewDefaultIMELocked()) {
+                    updateFromSettingsLocked();
+                }
+            } else {
+                // Double check that the default IME is certainly enabled.
+                setInputMethodEnabledLocked(defaultImiId, true);
             }
         }
     }
@@ -2626,7 +2661,8 @@
                 mCurrentSubtype = subtype;
             } else {
                 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
-                mCurrentSubtype = null;
+                // If the subtype is not specified, choose the most applicable one
+                mCurrentSubtype = getCurrentInputMethodSubtype();
             }
         }
 
@@ -3007,8 +3043,8 @@
             mContext = context;
             mPm = context.getPackageManager();
             mImms = imms;
-            mSystemLocaleStr =
-                    imms.mLastSystemLocale != null ? imms.mLastSystemLocale.toString() : "";
+            final Locale locale = context.getResources().getConfiguration().locale;
+            mSystemLocaleStr = locale != null ? locale.toString() : "";
         }
 
         private final TreeMap<InputMethodInfo, List<InputMethodSubtype>> mSortedImmis =
diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java
index 4f1f76f..bc946b5 100644
--- a/services/java/com/android/server/input/InputManagerService.java
+++ b/services/java/com/android/server/input/InputManagerService.java
@@ -16,8 +16,7 @@
 
 package com.android.server.input;
 
-import com.android.internal.os.AtomicFile;
-import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.R;
 import com.android.internal.util.XmlUtils;
 import com.android.server.Watchdog;
 
@@ -26,6 +25,9 @@
 import org.xmlpull.v1.XmlSerializer;
 
 import android.Manifest;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.content.BroadcastReceiver;
@@ -70,23 +72,20 @@
 import android.view.Surface;
 import android.view.ViewConfiguration;
 import android.view.WindowManagerPolicy;
+import android.widget.Toast;
 
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.FileReader;
 import java.io.IOException;
-import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
-import java.util.Map;
+import java.util.HashSet;
 
-import libcore.io.IoUtils;
 import libcore.io.Streams;
 import libcore.util.Objects;
 
@@ -100,6 +99,10 @@
     private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml";
 
     private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1;
+    private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 2;
+    private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 3;
+    private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4;
+    private static final int MSG_RELOAD_DEVICE_ALIASES = 5;
 
     // Pointer to native input manager service object.
     private final int mPtr;
@@ -109,6 +112,7 @@
     private final InputManagerHandler mHandler;
     private boolean mSystemReady;
     private BluetoothService mBluetoothService;
+    private NotificationManager mNotificationManager;
 
     // Persistent data store.  Must be locked each time during use.
     private final PersistentDataStore mDataStore = new PersistentDataStore();
@@ -122,6 +126,11 @@
     private final ArrayList<InputDevicesChangedListenerRecord>
             mTempInputDevicesChangedListenersToNotify =
                     new ArrayList<InputDevicesChangedListenerRecord>(); // handler thread only
+    private final ArrayList<InputDevice>
+            mTempFullKeyboards = new ArrayList<InputDevice>(); // handler thread only
+    private boolean mKeyboardLayoutNotificationShown;
+    private PendingIntent mKeyboardLayoutIntent;
+    private Toast mSwitchedKeyboardLayoutToast;
 
     // State for vibrator tokens.
     private Object mVibratorLock = new Object();
@@ -235,6 +244,8 @@
             Slog.d(TAG, "System ready.");
         }
         mBluetoothService = bluetoothService;
+        mNotificationManager = (NotificationManager)mContext.getSystemService(
+                Context.NOTIFICATION_SERVICE);
         mSystemReady = true;
 
         IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
@@ -244,10 +255,7 @@
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Packages changed, reloading keyboard layouts.");
-                }
-                reloadKeyboardLayouts();
+                updateKeyboardLayouts();
             }
         }, filter, null, mHandler);
 
@@ -255,22 +263,25 @@
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Bluetooth alias changed, reloading device names.");
-                }
                 reloadDeviceAliases();
             }
         }, filter, null, mHandler);
 
-        reloadKeyboardLayouts();
-        reloadDeviceAliases();
+        mHandler.sendEmptyMessage(MSG_RELOAD_DEVICE_ALIASES);
+        mHandler.sendEmptyMessage(MSG_UPDATE_KEYBOARD_LAYOUTS);
     }
 
     private void reloadKeyboardLayouts() {
+        if (DEBUG) {
+            Slog.d(TAG, "Reloading keyboard layouts.");
+        }
         nativeReloadKeyboardLayouts(mPtr);
     }
 
     private void reloadDeviceAliases() {
+        if (DEBUG) {
+            Slog.d(TAG, "Reloading device names.");
+        }
         nativeReloadDeviceAliases(mPtr);
     }
 
@@ -558,9 +569,11 @@
     }
 
     // Must be called on handler.
-    private void deliverInputDevicesChanged() {
+    private void deliverInputDevicesChanged(InputDevice[] oldInputDevices) {
+        // Scan for changes.
+        int numFullKeyboardsAdded = 0;
         mTempInputDevicesChangedListenersToNotify.clear();
-
+        mTempFullKeyboards.clear();
         final int numListeners;
         final int[] deviceIdAndGeneration;
         synchronized (mInputDevicesLock) {
@@ -581,13 +594,126 @@
                 final InputDevice inputDevice = mInputDevices[i];
                 deviceIdAndGeneration[i * 2] = inputDevice.getId();
                 deviceIdAndGeneration[i * 2 + 1] = inputDevice.getGeneration();
+
+                if (isFullKeyboard(inputDevice)) {
+                    if (!containsInputDeviceWithDescriptor(oldInputDevices,
+                            inputDevice.getDescriptor())) {
+                        mTempFullKeyboards.add(numFullKeyboardsAdded++, inputDevice);
+                    } else {
+                        mTempFullKeyboards.add(inputDevice);
+                    }
+                }
             }
         }
 
+        // Notify listeners.
         for (int i = 0; i < numListeners; i++) {
             mTempInputDevicesChangedListenersToNotify.get(i).notifyInputDevicesChanged(
                     deviceIdAndGeneration);
         }
+        mTempInputDevicesChangedListenersToNotify.clear();
+
+        // Check for missing keyboard layouts.
+        if (mNotificationManager != null) {
+            final int numFullKeyboards = mTempFullKeyboards.size();
+            boolean missingLayoutForExternalKeyboard = false;
+            boolean missingLayoutForExternalKeyboardAdded = false;
+            synchronized (mDataStore) {
+                for (int i = 0; i < numFullKeyboards; i++) {
+                    final InputDevice inputDevice = mTempFullKeyboards.get(i);
+                    if (mDataStore.getCurrentKeyboardLayout(inputDevice.getDescriptor()) == null) {
+                        missingLayoutForExternalKeyboard = true;
+                        if (i < numFullKeyboardsAdded) {
+                            missingLayoutForExternalKeyboardAdded = true;
+                        }
+                    }
+                }
+            }
+            if (missingLayoutForExternalKeyboard) {
+                if (missingLayoutForExternalKeyboardAdded) {
+                    showMissingKeyboardLayoutNotification();
+                }
+            } else if (mKeyboardLayoutNotificationShown) {
+                hideMissingKeyboardLayoutNotification();
+            }
+        }
+        mTempFullKeyboards.clear();
+    }
+
+    // Must be called on handler.
+    private void showMissingKeyboardLayoutNotification() {
+        if (!mKeyboardLayoutNotificationShown) {
+            if (mKeyboardLayoutIntent == null) {
+                final Intent intent = new Intent("android.settings.INPUT_METHOD_SETTINGS");
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                        | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+                        | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                mKeyboardLayoutIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+            }
+
+            Resources r = mContext.getResources();
+            Notification notification = new Notification.Builder(mContext)
+                    .setContentTitle(r.getString(
+                            R.string.select_keyboard_layout_notification_title))
+                    .setContentText(r.getString(
+                            R.string.select_keyboard_layout_notification_message))
+                    .setContentIntent(mKeyboardLayoutIntent)
+                    .setSmallIcon(R.drawable.ic_settings_language)
+                    .setPriority(Notification.PRIORITY_LOW)
+                    .build();
+            mNotificationManager.notify(R.string.select_keyboard_layout_notification_title,
+                    notification);
+            mKeyboardLayoutNotificationShown = true;
+        }
+    }
+
+    // Must be called on handler.
+    private void hideMissingKeyboardLayoutNotification() {
+        if (mKeyboardLayoutNotificationShown) {
+            mKeyboardLayoutNotificationShown = false;
+            mNotificationManager.cancel(R.string.select_keyboard_layout_notification_title);
+        }
+    }
+
+    // Must be called on handler.
+    private void updateKeyboardLayouts() {
+        // Scan all input devices state for keyboard layouts that have been uninstalled.
+        final HashSet<String> availableKeyboardLayouts = new HashSet<String>();
+        visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
+            @Override
+            public void visitKeyboardLayout(Resources resources,
+                    String descriptor, String label, String collection, int keyboardLayoutResId) {
+                availableKeyboardLayouts.add(descriptor);
+            }
+        });
+        synchronized (mDataStore) {
+            try {
+                mDataStore.removeUninstalledKeyboardLayouts(availableKeyboardLayouts);
+            } finally {
+                mDataStore.saveIfNeeded();
+            }
+        }
+
+        // Reload keyboard layouts.
+        reloadKeyboardLayouts();
+    }
+
+    private static boolean isFullKeyboard(InputDevice inputDevice) {
+        return !inputDevice.isVirtual()
+                && (inputDevice.getSources() & InputDevice.SOURCE_KEYBOARD) != 0
+                && inputDevice.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC;
+    }
+
+    private static boolean containsInputDeviceWithDescriptor(InputDevice[] inputDevices,
+            String descriptor) {
+        final int numDevices = inputDevices.length;
+        for (int i = 0; i < numDevices; i++) {
+            final InputDevice inputDevice = inputDevices[i];
+            if (inputDevice.getDescriptor().equals(descriptor)) {
+                return true;
+            }
+        }
+        return false;
     }
 
     @Override // Binder call
@@ -720,43 +846,147 @@
     }
 
     @Override // Binder call
-    public String getKeyboardLayoutForInputDevice(String inputDeviceDescriptor) {
+    public String getCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor) {
         if (inputDeviceDescriptor == null) {
             throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
         }
 
         synchronized (mDataStore) {
-            return mDataStore.getKeyboardLayout(inputDeviceDescriptor);
+            return mDataStore.getCurrentKeyboardLayout(inputDeviceDescriptor);
         }
     }
 
     @Override // Binder call
-    public void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+    public void setCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
             String keyboardLayoutDescriptor) {
         if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
-                "setKeyboardLayoutForInputDevice()")) {
+                "setCurrentKeyboardLayoutForInputDevice()")) {
             throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
         }
-
         if (inputDeviceDescriptor == null) {
             throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
         }
+        if (keyboardLayoutDescriptor == null) {
+            throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+        }
 
-        final boolean changed;
         synchronized (mDataStore) {
             try {
-                changed = mDataStore.setKeyboardLayout(
-                        inputDeviceDescriptor, keyboardLayoutDescriptor);
+                if (mDataStore.setCurrentKeyboardLayout(
+                        inputDeviceDescriptor, keyboardLayoutDescriptor)) {
+                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+                }
             } finally {
                 mDataStore.saveIfNeeded();
             }
         }
+    }
 
-        if (changed) {
-            if (DEBUG) {
-                Slog.d(TAG, "Keyboard layout changed, reloading keyboard layouts.");
+    @Override // Binder call
+    public String[] getKeyboardLayoutsForInputDevice(String inputDeviceDescriptor) {
+        if (inputDeviceDescriptor == null) {
+            throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+        }
+
+        synchronized (mDataStore) {
+            return mDataStore.getKeyboardLayouts(inputDeviceDescriptor);
+        }
+    }
+
+    @Override // Binder call
+    public void addKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+            String keyboardLayoutDescriptor) {
+        if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
+                "addKeyboardLayoutForInputDevice()")) {
+            throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
+        }
+        if (inputDeviceDescriptor == null) {
+            throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+        }
+        if (keyboardLayoutDescriptor == null) {
+            throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+        }
+
+        synchronized (mDataStore) {
+            try {
+                String oldLayout = mDataStore.getCurrentKeyboardLayout(inputDeviceDescriptor);
+                if (mDataStore.addKeyboardLayout(inputDeviceDescriptor, keyboardLayoutDescriptor)
+                        && !Objects.equal(oldLayout,
+                                mDataStore.getCurrentKeyboardLayout(inputDeviceDescriptor))) {
+                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+                }
+            } finally {
+                mDataStore.saveIfNeeded();
             }
-            reloadKeyboardLayouts();
+        }
+    }
+
+    @Override // Binder call
+    public void removeKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+            String keyboardLayoutDescriptor) {
+        if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
+                "removeKeyboardLayoutForInputDevice()")) {
+            throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
+        }
+        if (inputDeviceDescriptor == null) {
+            throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+        }
+        if (keyboardLayoutDescriptor == null) {
+            throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+        }
+
+        synchronized (mDataStore) {
+            try {
+                String oldLayout = mDataStore.getCurrentKeyboardLayout(inputDeviceDescriptor);
+                if (mDataStore.removeKeyboardLayout(inputDeviceDescriptor,
+                        keyboardLayoutDescriptor)
+                        && !Objects.equal(oldLayout,
+                                mDataStore.getCurrentKeyboardLayout(inputDeviceDescriptor))) {
+                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+                }
+            } finally {
+                mDataStore.saveIfNeeded();
+            }
+        }
+    }
+
+    public void switchKeyboardLayout(int deviceId, int direction) {
+        mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
+    }
+
+    // Must be called on handler.
+    private void handleSwitchKeyboardLayout(int deviceId, int direction) {
+        final InputDevice device = getInputDevice(deviceId);
+        final String inputDeviceDescriptor = device.getDescriptor();
+        if (device != null) {
+            final boolean changed;
+            final String keyboardLayoutDescriptor;
+            synchronized (mDataStore) {
+                try {
+                    changed = mDataStore.switchKeyboardLayout(inputDeviceDescriptor, direction);
+                    keyboardLayoutDescriptor = mDataStore.getCurrentKeyboardLayout(
+                            inputDeviceDescriptor);
+                } finally {
+                    mDataStore.saveIfNeeded();
+                }
+            }
+
+            if (changed) {
+                if (mSwitchedKeyboardLayoutToast != null) {
+                    mSwitchedKeyboardLayoutToast.cancel();
+                    mSwitchedKeyboardLayoutToast = null;
+                }
+                if (keyboardLayoutDescriptor != null) {
+                    KeyboardLayout keyboardLayout = getKeyboardLayout(keyboardLayoutDescriptor);
+                    if (keyboardLayout != null) {
+                        mSwitchedKeyboardLayoutToast = Toast.makeText(
+                                mContext, keyboardLayout.getLabel(), Toast.LENGTH_SHORT);
+                        mSwitchedKeyboardLayoutToast.show();
+                    }
+                }
+
+                reloadKeyboardLayouts();
+            }
         }
     }
 
@@ -978,12 +1208,13 @@
     // Native callback.
     private void notifyInputDevicesChanged(InputDevice[] inputDevices) {
         synchronized (mInputDevicesLock) {
-            mInputDevices = inputDevices;
-
             if (!mInputDevicesChangedPending) {
                 mInputDevicesChangedPending = true;
-                mHandler.sendEmptyMessage(MSG_DELIVER_INPUT_DEVICES_CHANGED);
+                mHandler.obtainMessage(MSG_DELIVER_INPUT_DEVICES_CHANGED,
+                        mInputDevices).sendToTarget();
             }
+
+            mInputDevices = inputDevices;
         }
     }
 
@@ -1132,7 +1363,8 @@
             return null;
         }
 
-        String keyboardLayoutDescriptor = getKeyboardLayoutForInputDevice(inputDeviceDescriptor);
+        String keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(
+                inputDeviceDescriptor);
         if (keyboardLayoutDescriptor == null) {
             return null;
         }
@@ -1203,7 +1435,19 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_DELIVER_INPUT_DEVICES_CHANGED:
-                    deliverInputDevicesChanged();
+                    deliverInputDevicesChanged((InputDevice[])msg.obj);
+                    break;
+                case MSG_SWITCH_KEYBOARD_LAYOUT:
+                    handleSwitchKeyboardLayout(msg.arg1, msg.arg2);
+                    break;
+                case MSG_RELOAD_KEYBOARD_LAYOUTS:
+                    reloadKeyboardLayouts();
+                    break;
+                case MSG_UPDATE_KEYBOARD_LAYOUTS:
+                    updateKeyboardLayouts();
+                    break;
+                case MSG_RELOAD_DEVICE_ALIASES:
+                    reloadDeviceAliases();
                     break;
             }
         }
@@ -1316,186 +1560,4 @@
             onVibratorTokenDied(this);
         }
     }
-
-    /**
-     * Manages persistent state recorded by the input manager service as an XML file.
-     * Caller must acquire lock on the data store before accessing it.
-     *
-     * File format:
-     * <code>
-     * &lt;input-mananger-state>
-     *   &lt;input-devices>
-     *     &lt;input-device descriptor="xxxxx" keyboard-layout="yyyyy" />
-     *   &gt;input-devices>
-     * &gt;/input-manager-state>
-     * </code>
-     */
-    private static final class PersistentDataStore {
-        // Input device state by descriptor.
-        private final HashMap<String, InputDeviceState> mInputDevices =
-                new HashMap<String, InputDeviceState>();
-        private final AtomicFile mAtomicFile;
-
-        // True if the data has been loaded.
-        private boolean mLoaded;
-
-        // True if there are changes to be saved.
-        private boolean mDirty;
-
-        public PersistentDataStore() {
-            mAtomicFile = new AtomicFile(new File("/data/system/input-manager-state.xml"));
-        }
-
-        public void saveIfNeeded() {
-            if (mDirty) {
-                save();
-                mDirty = false;
-            }
-        }
-
-        public String getKeyboardLayout(String inputDeviceDescriptor) {
-            InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
-            return state != null ? state.keyboardLayoutDescriptor : null;
-        }
-
-        public boolean setKeyboardLayout(String inputDeviceDescriptor,
-                String keyboardLayoutDescriptor) {
-            InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
-            if (!Objects.equal(state.keyboardLayoutDescriptor, keyboardLayoutDescriptor)) {
-                state.keyboardLayoutDescriptor = keyboardLayoutDescriptor;
-                setDirty();
-                return true;
-            }
-            return false;
-        }
-
-        private InputDeviceState getInputDeviceState(String inputDeviceDescriptor,
-                boolean createIfAbsent) {
-            loadIfNeeded();
-            InputDeviceState state = mInputDevices.get(inputDeviceDescriptor);
-            if (state == null && createIfAbsent) {
-                state = new InputDeviceState();
-                mInputDevices.put(inputDeviceDescriptor, state);
-                setDirty();
-            }
-            return state;
-        }
-
-        private void loadIfNeeded() {
-            if (!mLoaded) {
-                load();
-                mLoaded = true;
-            }
-        }
-
-        private void setDirty() {
-            mDirty = true;
-        }
-
-        private void clearState() {
-            mInputDevices.clear();
-        }
-
-        private void load() {
-            clearState();
-
-            final InputStream is;
-            try {
-                is = mAtomicFile.openRead();
-            } catch (FileNotFoundException ex) {
-                return;
-            }
-
-            XmlPullParser parser;
-            try {
-                parser = Xml.newPullParser();
-                parser.setInput(new BufferedInputStream(is), null);
-                loadFromXml(parser);
-            } catch (IOException ex) {
-                Slog.w(TAG, "Failed to load input manager persistent store data.", ex);
-                clearState();
-            } catch (XmlPullParserException ex) {
-                Slog.w(TAG, "Failed to load input manager persistent store data.", ex);
-                clearState();
-            } finally {
-                IoUtils.closeQuietly(is);
-            }
-        }
-
-        private void save() {
-            final FileOutputStream os;
-            try {
-                os = mAtomicFile.startWrite();
-                boolean success = false;
-                try {
-                    XmlSerializer serializer = new FastXmlSerializer();
-                    serializer.setOutput(new BufferedOutputStream(os), "utf-8");
-                    saveToXml(serializer);
-                    serializer.flush();
-                    success = true;
-                } finally {
-                    if (success) {
-                        mAtomicFile.finishWrite(os);
-                    } else {
-                        mAtomicFile.failWrite(os);
-                    }
-                }
-            } catch (IOException ex) {
-                Slog.w(TAG, "Failed to save input manager persistent store data.", ex);
-            }
-        }
-
-        private void loadFromXml(XmlPullParser parser)
-                throws IOException, XmlPullParserException {
-            XmlUtils.beginDocument(parser, "input-manager-state");
-            final int outerDepth = parser.getDepth();
-            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
-                if (parser.getName().equals("input-devices")) {
-                    loadInputDevicesFromXml(parser);
-                }
-            }
-        }
-
-        private void loadInputDevicesFromXml(XmlPullParser parser)
-                throws IOException, XmlPullParserException {
-            final int outerDepth = parser.getDepth();
-            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
-                if (parser.getName().equals("input-device")) {
-                    String descriptor = parser.getAttributeValue(null, "descriptor");
-                    if (descriptor == null) {
-                        throw new XmlPullParserException(
-                                "Missing descriptor attribute on input-device");
-                    }
-                    InputDeviceState state = new InputDeviceState();
-                    state.keyboardLayoutDescriptor =
-                            parser.getAttributeValue(null, "keyboard-layout");
-                    mInputDevices.put(descriptor, state);
-                }
-            }
-        }
-
-        private void saveToXml(XmlSerializer serializer) throws IOException {
-            serializer.startDocument(null, true);
-            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
-            serializer.startTag(null, "input-manager-state");
-            serializer.startTag(null, "input-devices");
-            for (Map.Entry<String, InputDeviceState> entry : mInputDevices.entrySet()) {
-                final String descriptor = entry.getKey();
-                final InputDeviceState state = entry.getValue();
-                serializer.startTag(null, "input-device");
-                serializer.attribute(null, "descriptor", descriptor);
-                if (state.keyboardLayoutDescriptor != null) {
-                    serializer.attribute(null, "keyboard-layout", state.keyboardLayoutDescriptor);
-                }
-                serializer.endTag(null, "input-device");
-            }
-            serializer.endTag(null, "input-devices");
-            serializer.endTag(null, "input-manager-state");
-            serializer.endDocument();
-        }
-    }
-
-    private static final class InputDeviceState {
-        public String keyboardLayoutDescriptor;
-    }
 }
diff --git a/services/java/com/android/server/input/PersistentDataStore.java b/services/java/com/android/server/input/PersistentDataStore.java
new file mode 100644
index 0000000..fbe3e8b
--- /dev/null
+++ b/services/java/com/android/server/input/PersistentDataStore.java
@@ -0,0 +1,416 @@
+/*
+ * 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 com.android.server.input;
+
+import com.android.internal.os.AtomicFile;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.util.Slog;
+import android.util.Xml;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import libcore.io.IoUtils;
+import libcore.util.Objects;
+
+/**
+ * Manages persistent state recorded by the input manager service as an XML file.
+ * Caller must acquire lock on the data store before accessing it.
+ *
+ * File format:
+ * <code>
+ * &lt;input-mananger-state>
+ *   &lt;input-devices>
+ *     &lt;input-device descriptor="xxxxx" keyboard-layout="yyyyy" />
+ *   &gt;input-devices>
+ * &gt;/input-manager-state>
+ * </code>
+ */
+final class PersistentDataStore {
+    static final String TAG = "InputManager";
+
+    // Input device state by descriptor.
+    private final HashMap<String, InputDeviceState> mInputDevices =
+            new HashMap<String, InputDeviceState>();
+    private final AtomicFile mAtomicFile;
+
+    // True if the data has been loaded.
+    private boolean mLoaded;
+
+    // True if there are changes to be saved.
+    private boolean mDirty;
+
+    public PersistentDataStore() {
+        mAtomicFile = new AtomicFile(new File("/data/system/input-manager-state.xml"));
+    }
+
+    public void saveIfNeeded() {
+        if (mDirty) {
+            save();
+            mDirty = false;
+        }
+    }
+
+    public String getCurrentKeyboardLayout(String inputDeviceDescriptor) {
+        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
+        return state != null ? state.getCurrentKeyboardLayout() : null;
+    }
+
+    public boolean setCurrentKeyboardLayout(String inputDeviceDescriptor,
+            String keyboardLayoutDescriptor) {
+        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
+        if (state.setCurrentKeyboardLayout(keyboardLayoutDescriptor)) {
+            setDirty();
+            return true;
+        }
+        return false;
+    }
+
+    public String[] getKeyboardLayouts(String inputDeviceDescriptor) {
+        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
+        if (state == null) {
+            return (String[])ArrayUtils.emptyArray(String.class);
+        }
+        return state.getKeyboardLayouts();
+    }
+
+    public boolean addKeyboardLayout(String inputDeviceDescriptor,
+            String keyboardLayoutDescriptor) {
+        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
+        if (state.addKeyboardLayout(keyboardLayoutDescriptor)) {
+            setDirty();
+            return true;
+        }
+        return false;
+    }
+
+    public boolean removeKeyboardLayout(String inputDeviceDescriptor,
+            String keyboardLayoutDescriptor) {
+        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
+        if (state.removeKeyboardLayout(keyboardLayoutDescriptor)) {
+            setDirty();
+            return true;
+        }
+        return false;
+    }
+
+    public boolean switchKeyboardLayout(String inputDeviceDescriptor, int direction) {
+        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
+        if (state != null && state.switchKeyboardLayout(direction)) {
+            setDirty();
+            return true;
+        }
+        return false;
+    }
+
+    public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
+        boolean changed = false;
+        for (InputDeviceState state : mInputDevices.values()) {
+            if (state.removeUninstalledKeyboardLayouts(availableKeyboardLayouts)) {
+                changed = true;
+            }
+        }
+        if (changed) {
+            setDirty();
+            return true;
+        }
+        return false;
+    }
+
+    private InputDeviceState getInputDeviceState(String inputDeviceDescriptor,
+            boolean createIfAbsent) {
+        loadIfNeeded();
+        InputDeviceState state = mInputDevices.get(inputDeviceDescriptor);
+        if (state == null && createIfAbsent) {
+            state = new InputDeviceState();
+            mInputDevices.put(inputDeviceDescriptor, state);
+            setDirty();
+        }
+        return state;
+    }
+
+    private void loadIfNeeded() {
+        if (!mLoaded) {
+            load();
+            mLoaded = true;
+        }
+    }
+
+    private void setDirty() {
+        mDirty = true;
+    }
+
+    private void clearState() {
+        mInputDevices.clear();
+    }
+
+    private void load() {
+        clearState();
+
+        final InputStream is;
+        try {
+            is = mAtomicFile.openRead();
+        } catch (FileNotFoundException ex) {
+            return;
+        }
+
+        XmlPullParser parser;
+        try {
+            parser = Xml.newPullParser();
+            parser.setInput(new BufferedInputStream(is), null);
+            loadFromXml(parser);
+        } catch (IOException ex) {
+            Slog.w(InputManagerService.TAG, "Failed to load input manager persistent store data.", ex);
+            clearState();
+        } catch (XmlPullParserException ex) {
+            Slog.w(InputManagerService.TAG, "Failed to load input manager persistent store data.", ex);
+            clearState();
+        } finally {
+            IoUtils.closeQuietly(is);
+        }
+    }
+
+    private void save() {
+        final FileOutputStream os;
+        try {
+            os = mAtomicFile.startWrite();
+            boolean success = false;
+            try {
+                XmlSerializer serializer = new FastXmlSerializer();
+                serializer.setOutput(new BufferedOutputStream(os), "utf-8");
+                saveToXml(serializer);
+                serializer.flush();
+                success = true;
+            } finally {
+                if (success) {
+                    mAtomicFile.finishWrite(os);
+                } else {
+                    mAtomicFile.failWrite(os);
+                }
+            }
+        } catch (IOException ex) {
+            Slog.w(InputManagerService.TAG, "Failed to save input manager persistent store data.", ex);
+        }
+    }
+
+    private void loadFromXml(XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        XmlUtils.beginDocument(parser, "input-manager-state");
+        final int outerDepth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+            if (parser.getName().equals("input-devices")) {
+                loadInputDevicesFromXml(parser);
+            }
+        }
+    }
+
+    private void loadInputDevicesFromXml(XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        final int outerDepth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+            if (parser.getName().equals("input-device")) {
+                String descriptor = parser.getAttributeValue(null, "descriptor");
+                if (descriptor == null) {
+                    throw new XmlPullParserException(
+                            "Missing descriptor attribute on input-device.");
+                }
+                if (mInputDevices.containsKey(descriptor)) {
+                    throw new XmlPullParserException("Found duplicate input device.");
+                }
+
+                InputDeviceState state = new InputDeviceState();
+                state.loadFromXml(parser);
+                mInputDevices.put(descriptor, state);
+            }
+        }
+    }
+
+    private void saveToXml(XmlSerializer serializer) throws IOException {
+        serializer.startDocument(null, true);
+        serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+        serializer.startTag(null, "input-manager-state");
+        serializer.startTag(null, "input-devices");
+        for (Map.Entry<String, InputDeviceState> entry : mInputDevices.entrySet()) {
+            final String descriptor = entry.getKey();
+            final InputDeviceState state = entry.getValue();
+            serializer.startTag(null, "input-device");
+            serializer.attribute(null, "descriptor", descriptor);
+            state.saveToXml(serializer);
+            serializer.endTag(null, "input-device");
+        }
+        serializer.endTag(null, "input-devices");
+        serializer.endTag(null, "input-manager-state");
+        serializer.endDocument();
+    }
+
+    private static final class InputDeviceState {
+        private String mCurrentKeyboardLayout;
+        private ArrayList<String> mKeyboardLayouts = new ArrayList<String>();
+
+        public String getCurrentKeyboardLayout() {
+            return mCurrentKeyboardLayout;
+        }
+
+        public boolean setCurrentKeyboardLayout(String keyboardLayout) {
+            if (Objects.equal(mCurrentKeyboardLayout, keyboardLayout)) {
+                return false;
+            }
+            addKeyboardLayout(keyboardLayout);
+            mCurrentKeyboardLayout = keyboardLayout;
+            return true;
+        }
+
+        public String[] getKeyboardLayouts() {
+            if (mKeyboardLayouts.isEmpty()) {
+                return (String[])ArrayUtils.emptyArray(String.class);
+            }
+            return mKeyboardLayouts.toArray(new String[mKeyboardLayouts.size()]);
+        }
+
+        public boolean addKeyboardLayout(String keyboardLayout) {
+            int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
+            if (index >= 0) {
+                return false;
+            }
+            mKeyboardLayouts.add(-index - 1, keyboardLayout);
+            if (mCurrentKeyboardLayout == null) {
+                mCurrentKeyboardLayout = keyboardLayout;
+            }
+            return true;
+        }
+
+        public boolean removeKeyboardLayout(String keyboardLayout) {
+            int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
+            if (index < 0) {
+                return false;
+            }
+            mKeyboardLayouts.remove(index);
+            updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, index);
+            return true;
+        }
+
+        private void updateCurrentKeyboardLayoutIfRemoved(
+                String removedKeyboardLayout, int removedIndex) {
+            if (Objects.equal(mCurrentKeyboardLayout, removedKeyboardLayout)) {
+                if (!mKeyboardLayouts.isEmpty()) {
+                    int index = removedIndex;
+                    if (index == mKeyboardLayouts.size()) {
+                        index = 0;
+                    }
+                    mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
+                } else {
+                    mCurrentKeyboardLayout = null;
+                }
+            }
+        }
+
+        public boolean switchKeyboardLayout(int direction) {
+            final int size = mKeyboardLayouts.size();
+            if (size < 2) {
+                return false;
+            }
+            int index = Collections.binarySearch(mKeyboardLayouts, mCurrentKeyboardLayout);
+            assert index >= 0;
+            if (direction > 0) {
+                index = (index + 1) % size;
+            } else {
+                index = (index + size - 1) % size;
+            }
+            mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
+            return true;
+        }
+
+        public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
+            boolean changed = false;
+            for (int i = mKeyboardLayouts.size(); i-- > 0; ) {
+                String keyboardLayout = mKeyboardLayouts.get(i);
+                if (!availableKeyboardLayouts.contains(keyboardLayout)) {
+                    Slog.i(TAG, "Removing uninstalled keyboard layout " + keyboardLayout);
+                    mKeyboardLayouts.remove(i);
+                    updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, i);
+                    changed = true;
+                }
+            }
+            return changed;
+        }
+
+        public void loadFromXml(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            final int outerDepth = parser.getDepth();
+            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+                if (parser.getName().equals("keyboard-layout")) {
+                    String descriptor = parser.getAttributeValue(null, "descriptor");
+                    if (descriptor == null) {
+                        throw new XmlPullParserException(
+                                "Missing descriptor attribute on keyboard-layout.");
+                    }
+                    String current = parser.getAttributeValue(null, "current");
+                    if (mKeyboardLayouts.contains(descriptor)) {
+                        throw new XmlPullParserException(
+                                "Found duplicate keyboard layout.");
+                    }
+
+                    mKeyboardLayouts.add(descriptor);
+                    if (current != null && current.equals("true")) {
+                        if (mCurrentKeyboardLayout != null) {
+                            throw new XmlPullParserException(
+                                    "Found multiple current keyboard layouts.");
+                        }
+                        mCurrentKeyboardLayout = descriptor;
+                    }
+                }
+            }
+
+            // Maintain invariant that layouts are sorted.
+            Collections.sort(mKeyboardLayouts);
+
+            // Maintain invariant that there is always a current keyboard layout unless
+            // there are none installed.
+            if (mCurrentKeyboardLayout == null && !mKeyboardLayouts.isEmpty()) {
+                mCurrentKeyboardLayout = mKeyboardLayouts.get(0);
+            }
+        }
+
+        public void saveToXml(XmlSerializer serializer) throws IOException {
+            for (String layout : mKeyboardLayouts) {
+                serializer.startTag(null, "keyboard-layout");
+                serializer.attribute(null, "descriptor", layout);
+                if (layout.equals(mCurrentKeyboardLayout)) {
+                    serializer.attribute(null, "current", "true");
+                }
+                serializer.endTag(null, "keyboard-layout");
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java
index 480992b..cf3a5d2 100644
--- a/services/java/com/android/server/wm/WindowAnimator.java
+++ b/services/java/com/android/server/wm/WindowAnimator.java
@@ -538,8 +538,16 @@
         if (mDimAnimator == null) {
             mDimAnimator = new DimAnimator(mService.mFxSession);
         }
-        mService.mH.sendMessage(mService.mH.obtainMessage(SET_DIM_PARAMETERS,
-                new DimAnimator.Parameters(winAnimator, width, height, target)));
+        // Only set dim params on the highest dimmed layer.
+        final WindowStateAnimator dimWinAnimator = mDimParams == null
+                ? null : mDimParams.mDimWinAnimator;
+        // Don't turn on for an unshown surface, or for any layer but the highest dimmed one.
+        if (winAnimator.mSurfaceShown &&
+                (dimWinAnimator == null || !dimWinAnimator.mSurfaceShown
+                || dimWinAnimator.mAnimLayer < winAnimator.mAnimLayer)) {
+            mService.mH.sendMessage(mService.mH.obtainMessage(SET_DIM_PARAMETERS,
+                    new DimAnimator.Parameters(winAnimator, width, height, target)));
+        }
     }
 
     // TODO(cmautner): Move into Handler
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 076ba9a..885ec96 100755
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -5007,6 +5007,12 @@
 
     // Called by window manager policy.  Not exposed externally.
     @Override
+    public void switchKeyboardLayout(int deviceId, int direction) {
+        mInputManager.switchKeyboardLayout(deviceId, direction);
+    }
+
+    // Called by window manager policy.  Not exposed externally.
+    @Override
     public void shutdown() {
         ShutdownThread.shutdown(mContext, true);
     }
diff --git a/telephony/java/com/android/internal/telephony/ServiceStateTracker.java b/telephony/java/com/android/internal/telephony/ServiceStateTracker.java
index 69dd666..e4cfb23 100644
--- a/telephony/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -146,6 +146,7 @@
         "ci", // Cote d'Ivoire
         "eh", // Western Sahara
         "fo", // Faroe Islands, Denmark
+        "gb", // United Kingdom of Great Britain and Northern Ireland
         "gh", // Ghana
         "gm", // Gambia
         "gn", // Guinea
@@ -161,7 +162,6 @@
         "sn", // Senegal
         "st", // Sao Tome and Principe
         "tg", // Togo
-        "uk", // U.K
     };
 
     /** Reason for registration denial. */
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index b9ec30c..a69adc1 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -2089,6 +2089,23 @@
     keep->add(rule, location);
 }
 
+void
+addProguardKeepMethodRule(ProguardKeepSet* keep, const String8& memberName,
+        const char* pkg, const String8& srcName, int line)
+{
+    String8 rule("-keepclassmembers class * { *** ");
+    rule += memberName;
+    rule += "(...); }";
+
+    String8 location("onClick ");
+    location += srcName;
+    char lineno[20];
+    sprintf(lineno, ":%d", line);
+    location += lineno;
+
+    keep->add(rule, location);
+}
+
 status_t
 writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& assets)
 {
@@ -2251,6 +2268,13 @@
                 }
             }
         }
+        ssize_t attrIndex = tree.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, "onClick");
+        if (attrIndex >= 0) {
+            size_t len;
+            addProguardKeepMethodRule(keep,
+                                String8(tree.getAttributeStringValue(attrIndex, &len)), NULL,
+                                layoutFile->getPrintableSource(), tree.getLineNumber());
+        }
     }
 
     return NO_ERROR;
@@ -2289,6 +2313,9 @@
         } else if ((dirName == String8("xml")) || (strncmp(dirName.string(), "xml-", 4) == 0)) {
             startTag = "PreferenceScreen";
             tagAttrPairs = &kXmlTagAttrPairs;
+        } else if ((dirName == String8("menu")) || (strncmp(dirName.string(), "menu-", 5) == 0)) {
+            startTag = "menu";
+            tagAttrPairs = NULL;
         } else {
             continue;
         }