Merge changes If7388e15,Ica73e67a,Ia4d4362e into nyc-dev

* changes:
  Assume package failed to compile unless proven otherwise
  Refactor return values of performDexOpt
  Update packages in post-boot background job
diff --git a/api/current.txt b/api/current.txt
index 3444597..fbbb2e3 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3578,6 +3578,7 @@
     method public final deprecated void removeDialog(int);
     method public void reportFullyDrawn();
     method public android.view.DropPermissions requestDropPermissions(android.view.DragEvent);
+    method public final void requestKeyboardShortcutsHelper();
     method public final void requestPermissions(java.lang.String[], int);
     method public boolean requestVisibleBehind(boolean);
     method public final boolean requestWindowFeature(int);
diff --git a/api/system-current.txt b/api/system-current.txt
index a4927f2..d91f5d1 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3696,6 +3696,7 @@
     method public final deprecated void removeDialog(int);
     method public void reportFullyDrawn();
     method public android.view.DropPermissions requestDropPermissions(android.view.DragEvent);
+    method public final void requestKeyboardShortcutsHelper();
     method public final void requestPermissions(java.lang.String[], int);
     method public boolean requestVisibleBehind(boolean);
     method public final boolean requestWindowFeature(int);
diff --git a/api/test-current.txt b/api/test-current.txt
index 4179948..bad9733 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3578,6 +3578,7 @@
     method public final deprecated void removeDialog(int);
     method public void reportFullyDrawn();
     method public android.view.DropPermissions requestDropPermissions(android.view.DragEvent);
+    method public final void requestKeyboardShortcutsHelper();
     method public final void requestPermissions(java.lang.String[], int);
     method public boolean requestVisibleBehind(boolean);
     method public final boolean requestWindowFeature(int);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 672a706..7652766 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1676,6 +1676,17 @@
     public void onProvideAssistContent(AssistContent outContent) {
     }
 
+    /**
+     * Request the Keyboard Shortcuts screen to show up. If it succeeds, this will trigger
+     * {@link #onProvideKeyboardShortcuts} to retrieve the shortcuts for the foreground activity.
+     */
+    public final void requestKeyboardShortcutsHelper() {
+        Intent intent = new Intent(Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS);
+        intent.setComponent(new ComponentName("com.android.systemui",
+                "com.android.systemui.statusbar.KeyboardShortcutsReceiver"));
+        sendBroadcast(intent);
+    }
+
     @Override
     public void onProvideKeyboardShortcuts(
             List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 207b70a..7e67e8d 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1404,6 +1404,16 @@
     public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
 
     /**
+     * Activity Action: Start the Keyboard Shortcuts Helper screen.
+     * <p>Input: Nothing.
+     * <p>Output: Nothing.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_SHOW_KEYBOARD_SHORTCUTS =
+            "android.intent.action.SHOW_KEYBOARD_SHORTCUTS";
+
+    /**
      * Activity Action: Show settings for managing network data usage of a
      * specific application. Applications should define an activity that offers
      * options to control data usage.
diff --git a/core/java/android/view/inputmethod/CursorAnchorInfo.java b/core/java/android/view/inputmethod/CursorAnchorInfo.java
index fd73432..24739bf 100644
--- a/core/java/android/view/inputmethod/CursorAnchorInfo.java
+++ b/core/java/android/view/inputmethod/CursorAnchorInfo.java
@@ -16,6 +16,7 @@
 
 package android.view.inputmethod;
 
+import android.annotation.NonNull;
 import android.graphics.Matrix;
 import android.graphics.RectF;
 import android.os.Parcel;
@@ -25,6 +26,7 @@
 import android.text.TextUtils;
 import android.view.inputmethod.SparseRectFArray.SparseRectFArrayBuilder;
 
+import java.util.Arrays;
 import java.util.Objects;
 
 /**
@@ -36,6 +38,11 @@
  */
 public final class CursorAnchorInfo implements Parcelable {
     /**
+     * The pre-computed hash code.
+     */
+    private final int mHashCode;
+
+    /**
      * The index of the first character of the selected text (inclusive). {@code -1} when there is
      * no text selection.
      */
@@ -100,7 +107,8 @@
      * Transformation matrix that is applied to any positional information of this class to
      * transform local coordinates into screen coordinates.
      */
-    private final Matrix mMatrix;
+    @NonNull
+    private final float[] mMatrixValues;
 
     /**
      * Flag for {@link #getInsertionMarkerFlags()} and {@link #getCharacterBoundsFlags(int)}: the
@@ -121,6 +129,7 @@
     public static final int FLAG_IS_RTL = 0x04;
 
     public CursorAnchorInfo(final Parcel source) {
+        mHashCode = source.readInt();
         mSelectionStart = source.readInt();
         mSelectionEnd = source.readInt();
         mComposingTextStart = source.readInt();
@@ -131,8 +140,7 @@
         mInsertionMarkerBaseline = source.readFloat();
         mInsertionMarkerBottom = source.readFloat();
         mCharacterBoundsArray = source.readParcelable(SparseRectFArray.class.getClassLoader());
-        mMatrix = new Matrix();
-        mMatrix.setValues(source.createFloatArray());
+        mMatrixValues = source.createFloatArray();
     }
 
     /**
@@ -143,6 +151,7 @@
      */
     @Override
     public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mHashCode);
         dest.writeInt(mSelectionStart);
         dest.writeInt(mSelectionEnd);
         dest.writeInt(mComposingTextStart);
@@ -153,27 +162,12 @@
         dest.writeFloat(mInsertionMarkerBaseline);
         dest.writeFloat(mInsertionMarkerBottom);
         dest.writeParcelable(mCharacterBoundsArray, flags);
-        final float[] matrixArray = new float[9];
-        mMatrix.getValues(matrixArray);
-        dest.writeFloatArray(matrixArray);
+        dest.writeFloatArray(mMatrixValues);
     }
 
     @Override
     public int hashCode(){
-        final float floatHash = mInsertionMarkerHorizontal + mInsertionMarkerTop
-                + mInsertionMarkerBaseline + mInsertionMarkerBottom;
-        int hash = floatHash > 0 ? (int) floatHash : (int)(-floatHash);
-        hash *= 31;
-        hash += mInsertionMarkerFlags;
-        hash *= 31;
-        hash += mSelectionStart + mSelectionEnd + mComposingTextStart;
-        hash *= 31;
-        hash += Objects.hashCode(mComposingText);
-        hash *= 31;
-        hash += Objects.hashCode(mCharacterBoundsArray);
-        hash *= 31;
-        hash += Objects.hashCode(mMatrix);
-        return hash;
+        return mHashCode;
     }
 
     /**
@@ -202,13 +196,13 @@
         if (hashCode() != that.hashCode()) {
             return false;
         }
+
+        // Check fields that are not covered by hashCode() first.
+
         if (mSelectionStart != that.mSelectionStart || mSelectionEnd != that.mSelectionEnd) {
             return false;
         }
-        if (mComposingTextStart != that.mComposingTextStart
-                || !Objects.equals(mComposingText, that.mComposingText)) {
-            return false;
-        }
+
         if (mInsertionMarkerFlags != that.mInsertionMarkerFlags
                 || !areSameFloatImpl(mInsertionMarkerHorizontal, that.mInsertionMarkerHorizontal)
                 || !areSameFloatImpl(mInsertionMarkerTop, that.mInsertionMarkerTop)
@@ -216,18 +210,35 @@
                 || !areSameFloatImpl(mInsertionMarkerBottom, that.mInsertionMarkerBottom)) {
             return false;
         }
+
         if (!Objects.equals(mCharacterBoundsArray, that.mCharacterBoundsArray)) {
             return false;
         }
-        if (!Objects.equals(mMatrix, that.mMatrix)) {
+
+        // Following fields are (partially) covered by hashCode().
+
+        if (mComposingTextStart != that.mComposingTextStart
+                || !Objects.equals(mComposingText, that.mComposingText)) {
             return false;
         }
+
+        // We do not use Arrays.equals(float[], float[]) to keep the previous behavior regarding
+        // NaN, 0.0f, and -0.0f.
+        if (mMatrixValues.length != that.mMatrixValues.length) {
+            return false;
+        }
+        for (int i = 0; i < mMatrixValues.length; ++i) {
+            if (mMatrixValues[i] != that.mMatrixValues[i]) {
+                return false;
+            }
+        }
         return true;
     }
 
     @Override
     public String toString() {
-        return "SelectionInfo{mSelection=" + mSelectionStart + "," + mSelectionEnd
+        return "CursorAnchorInfo{mHashCode=" + mHashCode
+                + " mSelection=" + mSelectionStart + "," + mSelectionEnd
                 + " mComposingTextStart=" + mComposingTextStart
                 + " mComposingText=" + Objects.toString(mComposingText)
                 + " mInsertionMarkerFlags=" + mInsertionMarkerFlags
@@ -236,7 +247,7 @@
                 + " mInsertionMarkerBaseline=" + mInsertionMarkerBaseline
                 + " mInsertionMarkerBottom=" + mInsertionMarkerBottom
                 + " mCharacterBoundsArray=" + Objects.toString(mCharacterBoundsArray)
-                + " mMatrix=" + Objects.toString(mMatrix)
+                + " mMatrix=" + Arrays.toString(mMatrixValues)
                 + "}";
     }
 
@@ -254,7 +265,7 @@
         private float mInsertionMarkerBottom = Float.NaN;
         private int mInsertionMarkerFlags = 0;
         private SparseRectFArrayBuilder mCharacterBoundsArrayBuilder = null;
-        private final Matrix mMatrix = new Matrix(Matrix.IDENTITY_MATRIX);
+        private float[] mMatrixValues = null;
         private boolean mMatrixInitialized = false;
 
         /**
@@ -349,7 +360,10 @@
          * is interpreted as an identity matrix.
          */
         public Builder setMatrix(final Matrix matrix) {
-            mMatrix.set(matrix != null ? matrix : Matrix.IDENTITY_MATRIX);
+            if (mMatrixValues == null) {
+                mMatrixValues = new float[9];
+            }
+            (matrix != null ? matrix : Matrix.IDENTITY_MATRIX).getValues(mMatrixValues);
             mMatrixInitialized = true;
             return this;
         }
@@ -391,7 +405,6 @@
             mInsertionMarkerTop = Float.NaN;
             mInsertionMarkerBaseline = Float.NaN;
             mInsertionMarkerBottom = Float.NaN;
-            mMatrix.set(Matrix.IDENTITY_MATRIX);
             mMatrixInitialized = false;
             if (mCharacterBoundsArrayBuilder != null) {
                 mCharacterBoundsArrayBuilder.reset();
@@ -411,7 +424,18 @@
         mInsertionMarkerBottom = builder.mInsertionMarkerBottom;
         mCharacterBoundsArray = builder.mCharacterBoundsArrayBuilder != null ?
                 builder.mCharacterBoundsArrayBuilder.build() : null;
-        mMatrix = new Matrix(builder.mMatrix);
+        mMatrixValues = new float[9];
+        if (builder.mMatrixInitialized) {
+            System.arraycopy(builder.mMatrixValues, 0, mMatrixValues, 0, 9);
+        } else {
+            Matrix.IDENTITY_MATRIX.getValues(mMatrixValues);
+        }
+
+        // To keep hash function simple, we only use some complex objects for hash.
+        int hash = Objects.hashCode(mComposingText);
+        hash *= 31;
+        hash += Arrays.hashCode(mMatrixValues);
+        mHashCode = hash;
     }
 
     /**
@@ -527,7 +551,9 @@
      * @return a new instance (copy) of the transformation matrix.
      */
     public Matrix getMatrix() {
-        return new Matrix(mMatrix);
+        final Matrix matrix = new Matrix();
+        matrix.setValues(mMatrixValues);
+        return matrix;
     }
 
     /**
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 9e2442c..f5854f5 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -488,5 +488,12 @@
                 <action android:name="com.android.systemui.action.CLEAR_TUNER" />
             </intent-filter>
         </receiver>
+
+        <receiver
+            android:name=".statusbar.KeyboardShortcutsReceiver">
+            <intent-filter>
+                <action android:name="android.intent.action.SHOW_KEYBOARD_SHORTCUTS" />
+            </intent-filter>
+        </receiver>
     </application>
 </manifest>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index fff1491..0c079e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -58,11 +58,15 @@
 import com.android.systemui.recents.Recents;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 
 import static android.content.Context.LAYOUT_INFLATER_SERVICE;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
 
+import com.google.android.collect.Lists;
+
 /**
  * Contains functionality for handling keyboard shortcuts.
  */
@@ -76,11 +80,32 @@
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final Context mContext;
     private final IPackageManager mPackageManager;
-    private final OnClickListener dialogCloseListener =  new DialogInterface.OnClickListener() {
+    private final OnClickListener mDialogCloseListener = new DialogInterface.OnClickListener() {
         public void onClick(DialogInterface dialog, int id) {
             dismissKeyboardShortcutsDialog();
         }
     };
+    private final Comparator<KeyboardShortcutInfo> mApplicationItemsComparator =
+            new Comparator<KeyboardShortcutInfo>() {
+                @Override
+                public int compare(KeyboardShortcutInfo ksh1, KeyboardShortcutInfo ksh2) {
+                    boolean ksh1ShouldBeLast = ksh1.getLabel() == null
+                            || ksh1.getLabel().toString().isEmpty();
+                    boolean ksh2ShouldBeLast = ksh2.getLabel() == null
+                            || ksh2.getLabel().toString().isEmpty();
+                    if (ksh1ShouldBeLast && ksh2ShouldBeLast) {
+                        return 0;
+                    }
+                    if (ksh1ShouldBeLast) {
+                        return 1;
+                    }
+                    if (ksh2ShouldBeLast) {
+                        return -1;
+                    }
+                    return (ksh1.getLabel().toString()).compareToIgnoreCase(
+                            ksh2.getLabel().toString());
+                }
+            };
 
     private Dialog mKeyboardShortcutsDialog;
     private KeyCharacterMap mKeyCharacterMap;
@@ -255,10 +280,7 @@
     }
 
     public void toggleKeyboardShortcuts(int deviceId) {
-        InputDevice inputDevice = InputManager.getInstance().getInputDevice(deviceId);
-        if (inputDevice != null) {
-            mKeyCharacterMap = inputDevice.getKeyCharacterMap();
-        }
+        retrieveKeyCharacterMap(deviceId);
         if (mKeyboardShortcutsDialog == null) {
             Recents.getSystemServices().requestKeyboardShortcuts(mContext,
                 new KeyboardShortcutsReceiver() {
@@ -278,6 +300,35 @@
         }
     }
 
+    /**
+     * Retrieves a {@link KeyCharacterMap} and assigns it to mKeyCharacterMap. If the given id is an
+     * existing device, that device's map is used. Otherwise, it checks first all available devices
+     * and if there is a full keyboard it uses that map, otherwise falls back to the Virtual
+     * Keyboard with its default map.
+     */
+    private void retrieveKeyCharacterMap(int deviceId) {
+        final InputManager inputManager = InputManager.getInstance();
+        if (deviceId != -1) {
+            final InputDevice inputDevice = inputManager.getInputDevice(deviceId);
+            if (inputDevice != null) {
+                mKeyCharacterMap = inputDevice.getKeyCharacterMap();
+                return;
+            }
+        }
+        final int[] deviceIds = inputManager.getInputDeviceIds();
+        for (int i = 0; i < deviceIds.length; ++i) {
+            final InputDevice inputDevice = inputManager.getInputDevice(deviceIds[i]);
+            // -1 is the Virtual Keyboard, with the default key map. Use that one only as last
+            // resort.
+            if (inputDevice.getId() != -1 && inputDevice.isFullKeyboard()) {
+                mKeyCharacterMap = inputDevice.getKeyCharacterMap();
+                return;
+            }
+        }
+        final InputDevice inputDevice = inputManager.getInputDevice(-1);
+        mKeyCharacterMap = inputDevice.getKeyCharacterMap();
+    }
+
     public void dismissKeyboardShortcutsDialog() {
         if (mKeyboardShortcutsDialog != null) {
             mKeyboardShortcutsDialog.dismiss();
@@ -320,9 +371,7 @@
 
     private KeyboardShortcutGroup getDefaultApplicationShortcuts() {
         final int userId = mContext.getUserId();
-        final KeyboardShortcutGroup applicationGroup = new KeyboardShortcutGroup(
-                mContext.getString(R.string.keyboard_shortcut_group_applications),
-                true);
+        List<KeyboardShortcutInfo> keyboardShortcutInfoAppItems = Lists.newArrayList();
 
         // Assist.
         final AssistUtils assistUtils = new AssistUtils(mContext);
@@ -340,7 +389,7 @@
                     assistPackageInfo.applicationInfo.packageName,
                     assistPackageInfo.applicationInfo.icon);
 
-            applicationGroup.addItem(new KeyboardShortcutInfo(
+            keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
                     mContext.getString(R.string.keyboard_shortcut_group_applications_assist),
                     assistIcon,
                     KeyEvent.KEYCODE_UNKNOWN,
@@ -350,7 +399,7 @@
         // Browser.
         final Icon browserIcon = getIconForIntentCategory(Intent.CATEGORY_APP_BROWSER, userId);
         if (browserIcon != null) {
-            applicationGroup.addItem(new KeyboardShortcutInfo(
+            keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
                     mContext.getString(R.string.keyboard_shortcut_group_applications_browser),
                     browserIcon,
                     KeyEvent.KEYCODE_B,
@@ -361,7 +410,7 @@
         // Contacts.
         final Icon contactsIcon = getIconForIntentCategory(Intent.CATEGORY_APP_CONTACTS, userId);
         if (contactsIcon != null) {
-            applicationGroup.addItem(new KeyboardShortcutInfo(
+            keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
                     mContext.getString(R.string.keyboard_shortcut_group_applications_contacts),
                     contactsIcon,
                     KeyEvent.KEYCODE_C,
@@ -371,7 +420,7 @@
         // Email.
         final Icon emailIcon = getIconForIntentCategory(Intent.CATEGORY_APP_EMAIL, userId);
         if (emailIcon != null) {
-            applicationGroup.addItem(new KeyboardShortcutInfo(
+            keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
                     mContext.getString(R.string.keyboard_shortcut_group_applications_email),
                     emailIcon,
                     KeyEvent.KEYCODE_E,
@@ -381,7 +430,7 @@
         // Messaging.
         final Icon messagingIcon = getIconForIntentCategory(Intent.CATEGORY_APP_MESSAGING, userId);
         if (messagingIcon != null) {
-            applicationGroup.addItem(new KeyboardShortcutInfo(
+            keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
                     mContext.getString(R.string.keyboard_shortcut_group_applications_im),
                     messagingIcon,
                     KeyEvent.KEYCODE_T,
@@ -391,7 +440,7 @@
         // Music.
         final Icon musicIcon = getIconForIntentCategory(Intent.CATEGORY_APP_MUSIC, userId);
         if (musicIcon != null) {
-            applicationGroup.addItem(new KeyboardShortcutInfo(
+            keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
                     mContext.getString(R.string.keyboard_shortcut_group_applications_music),
                     musicIcon,
                     KeyEvent.KEYCODE_P,
@@ -401,14 +450,24 @@
         // Calendar.
         final Icon calendarIcon = getIconForIntentCategory(Intent.CATEGORY_APP_CALENDAR, userId);
         if (calendarIcon != null) {
-            applicationGroup.addItem(new KeyboardShortcutInfo(
+            keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
                     mContext.getString(R.string.keyboard_shortcut_group_applications_calendar),
                     calendarIcon,
                     KeyEvent.KEYCODE_L,
                     KeyEvent.META_META_ON));
         }
 
-        return applicationGroup.getItems().size() == 0 ? null : applicationGroup;
+        final int itemsSize = keyboardShortcutInfoAppItems.size();
+        if (itemsSize == 0) {
+            return null;
+        }
+
+        // Sorts by label, case insensitive with nulls and/or empty labels last.
+        Collections.sort(keyboardShortcutInfoAppItems, mApplicationItemsComparator);
+        return new KeyboardShortcutGroup(
+                mContext.getString(R.string.keyboard_shortcut_group_applications),
+                keyboardShortcutInfoAppItems,
+                true);
     }
 
     private Icon getIconForIntentCategory(String intentCategory, int userId) {
@@ -459,7 +518,7 @@
         populateKeyboardShortcuts((LinearLayout) keyboardShortcutsView.findViewById(
                 R.id.keyboard_shortcuts_container), keyboardShortcutGroups);
         dialogBuilder.setView(keyboardShortcutsView);
-        dialogBuilder.setPositiveButton(R.string.quick_settings_done, dialogCloseListener);
+        dialogBuilder.setPositiveButton(R.string.quick_settings_done, mDialogCloseListener);
         mKeyboardShortcutsDialog = dialogBuilder.create();
         mKeyboardShortcutsDialog.setCanceledOnTouchOutside(true);
         Window keyboardShortcutsWindow = mKeyboardShortcutsDialog.getWindow();
@@ -488,12 +547,6 @@
             final int itemsSize = group.getItems().size();
             for (int j = 0; j < itemsSize; j++) {
                 KeyboardShortcutInfo info = group.getItems().get(j);
-                if (info.getKeycode() != KeyEvent.KEYCODE_UNKNOWN
-                        && !KeyCharacterMap.deviceHasKey(info.getKeycode())) {
-                    // The user can't achieve this shortcut, so skipping.
-                    Log.w(TAG, "Keyboard Shortcut contains key not on device, skipping.");
-                    continue;
-                }
                 List<StringOrDrawable> shortcutKeys = getHumanReadableShortcutKeys(info);
                 if (shortcutKeys == null) {
                     // Ignore shortcuts we can't display keys for.
@@ -585,9 +638,7 @@
             if (info.getKeycode() == KeyEvent.KEYCODE_UNKNOWN) {
                 return shortcutKeys;
             }
-            // TODO: Have a generic map for when we don't have the device's.
-            char displayLabel = mKeyCharacterMap == null
-                    ? 0 : mKeyCharacterMap.getDisplayLabel(info.getKeycode());
+            char displayLabel = mKeyCharacterMap.getDisplayLabel(info.getKeycode());
             if (displayLabel != 0) {
                 displayLabelString = String.valueOf(displayLabel);
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java
new file mode 100644
index 0000000..5d22faf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 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.systemui.statusbar;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Receiver for the Keyboard Shortcuts Helper.
+ */
+public class KeyboardShortcutsReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(intent.getAction())) {
+            final KeyboardShortcuts keyboardShortcuts = new KeyboardShortcuts(context);
+            keyboardShortcuts.toggleKeyboardShortcuts(-1 /* deviceId unknown */);
+        }
+    }
+}