Merge "Revert "Fix build: Revert "Record hyphens from Minikin and draw them"""
diff --git a/api/system-current.txt b/api/system-current.txt
index 874307d..9513ab6 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -27888,6 +27888,126 @@
     field public static final java.lang.String SIZE = "_size";
   }
 
+  public abstract class SearchIndexableData {
+    ctor public SearchIndexableData();
+    ctor public SearchIndexableData(android.content.Context);
+    field public java.lang.String className;
+    field public android.content.Context context;
+    field public boolean enabled;
+    field public int iconResId;
+    field public java.lang.String intentAction;
+    field public java.lang.String intentTargetClass;
+    field public java.lang.String intentTargetPackage;
+    field public java.lang.String key;
+    field public java.util.Locale locale;
+    field public java.lang.String packageName;
+    field public int rank;
+    field public int userId;
+  }
+
+  public class SearchIndexableResource extends android.provider.SearchIndexableData {
+    ctor public SearchIndexableResource(int, int, java.lang.String, int);
+    ctor public SearchIndexableResource(android.content.Context);
+    field public int xmlResId;
+  }
+
+  public class SearchIndexablesContract {
+    ctor public SearchIndexablesContract();
+    field public static final int COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE = 0; // 0x0
+    field public static final int COLUMN_INDEX_RAW_CLASS_NAME = 7; // 0x7
+    field public static final int COLUMN_INDEX_RAW_ENTRIES = 4; // 0x4
+    field public static final int COLUMN_INDEX_RAW_ICON_RESID = 8; // 0x8
+    field public static final int COLUMN_INDEX_RAW_INTENT_ACTION = 9; // 0x9
+    field public static final int COLUMN_INDEX_RAW_INTENT_TARGET_CLASS = 11; // 0xb
+    field public static final int COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE = 10; // 0xa
+    field public static final int COLUMN_INDEX_RAW_KEY = 12; // 0xc
+    field public static final int COLUMN_INDEX_RAW_KEYWORDS = 5; // 0x5
+    field public static final int COLUMN_INDEX_RAW_RANK = 0; // 0x0
+    field public static final int COLUMN_INDEX_RAW_SCREEN_TITLE = 6; // 0x6
+    field public static final int COLUMN_INDEX_RAW_SUMMARY_OFF = 3; // 0x3
+    field public static final int COLUMN_INDEX_RAW_SUMMARY_ON = 2; // 0x2
+    field public static final int COLUMN_INDEX_RAW_TITLE = 1; // 0x1
+    field public static final int COLUMN_INDEX_RAW_USER_ID = 13; // 0xd
+    field public static final int COLUMN_INDEX_XML_RES_CLASS_NAME = 2; // 0x2
+    field public static final int COLUMN_INDEX_XML_RES_ICON_RESID = 3; // 0x3
+    field public static final int COLUMN_INDEX_XML_RES_INTENT_ACTION = 4; // 0x4
+    field public static final int COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS = 6; // 0x6
+    field public static final int COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE = 5; // 0x5
+    field public static final int COLUMN_INDEX_XML_RES_RANK = 0; // 0x0
+    field public static final int COLUMN_INDEX_XML_RES_RESID = 1; // 0x1
+    field public static final java.lang.String INDEXABLES_RAW = "indexables_raw";
+    field public static final java.lang.String[] INDEXABLES_RAW_COLUMNS;
+    field public static final java.lang.String INDEXABLES_RAW_PATH = "settings/indexables_raw";
+    field public static final java.lang.String INDEXABLES_XML_RES = "indexables_xml_res";
+    field public static final java.lang.String[] INDEXABLES_XML_RES_COLUMNS;
+    field public static final java.lang.String INDEXABLES_XML_RES_PATH = "settings/indexables_xml_res";
+    field public static final java.lang.String NON_INDEXABLES_KEYS = "non_indexables_key";
+    field public static final java.lang.String[] NON_INDEXABLES_KEYS_COLUMNS;
+    field public static final java.lang.String NON_INDEXABLES_KEYS_PATH = "settings/non_indexables_key";
+    field public static final java.lang.String PROVIDER_INTERFACE = "android.content.action.SEARCH_INDEXABLES_PROVIDER";
+  }
+
+  private static class SearchIndexablesContract.BaseColumns {
+    field public static final java.lang.String COLUMN_CLASS_NAME = "className";
+    field public static final java.lang.String COLUMN_ICON_RESID = "iconResId";
+    field public static final java.lang.String COLUMN_INTENT_ACTION = "intentAction";
+    field public static final java.lang.String COLUMN_INTENT_TARGET_CLASS = "intentTargetClass";
+    field public static final java.lang.String COLUMN_INTENT_TARGET_PACKAGE = "intentTargetPackage";
+    field public static final java.lang.String COLUMN_RANK = "rank";
+  }
+
+  public static final class SearchIndexablesContract.NonIndexableKey extends android.provider.SearchIndexablesContract.BaseColumns {
+    field public static final java.lang.String COLUMN_CLASS_NAME = "className";
+    field public static final java.lang.String COLUMN_ICON_RESID = "iconResId";
+    field public static final java.lang.String COLUMN_INTENT_ACTION = "intentAction";
+    field public static final java.lang.String COLUMN_INTENT_TARGET_CLASS = "intentTargetClass";
+    field public static final java.lang.String COLUMN_INTENT_TARGET_PACKAGE = "intentTargetPackage";
+    field public static final java.lang.String COLUMN_KEY_VALUE = "key";
+    field public static final java.lang.String COLUMN_RANK = "rank";
+    field public static final java.lang.String MIME_TYPE = "vnd.android.cursor.dir/non_indexables_key";
+  }
+
+  public static final class SearchIndexablesContract.RawData extends android.provider.SearchIndexablesContract.BaseColumns {
+    field public static final java.lang.String COLUMN_CLASS_NAME = "className";
+    field public static final java.lang.String COLUMN_ENTRIES = "entries";
+    field public static final java.lang.String COLUMN_ICON_RESID = "iconResId";
+    field public static final java.lang.String COLUMN_INTENT_ACTION = "intentAction";
+    field public static final java.lang.String COLUMN_INTENT_TARGET_CLASS = "intentTargetClass";
+    field public static final java.lang.String COLUMN_INTENT_TARGET_PACKAGE = "intentTargetPackage";
+    field public static final java.lang.String COLUMN_KEY = "key";
+    field public static final java.lang.String COLUMN_KEYWORDS = "keywords";
+    field public static final java.lang.String COLUMN_RANK = "rank";
+    field public static final java.lang.String COLUMN_SCREEN_TITLE = "screenTitle";
+    field public static final java.lang.String COLUMN_SUMMARY_OFF = "summaryOff";
+    field public static final java.lang.String COLUMN_SUMMARY_ON = "summaryOn";
+    field public static final java.lang.String COLUMN_TITLE = "title";
+    field public static final java.lang.String COLUMN_USER_ID = "user_id";
+    field public static final java.lang.String MIME_TYPE = "vnd.android.cursor.dir/indexables_raw";
+  }
+
+  public static final class SearchIndexablesContract.XmlResource extends android.provider.SearchIndexablesContract.BaseColumns {
+    field public static final java.lang.String COLUMN_CLASS_NAME = "className";
+    field public static final java.lang.String COLUMN_ICON_RESID = "iconResId";
+    field public static final java.lang.String COLUMN_INTENT_ACTION = "intentAction";
+    field public static final java.lang.String COLUMN_INTENT_TARGET_CLASS = "intentTargetClass";
+    field public static final java.lang.String COLUMN_INTENT_TARGET_PACKAGE = "intentTargetPackage";
+    field public static final java.lang.String COLUMN_RANK = "rank";
+    field public static final java.lang.String COLUMN_XML_RESID = "xmlResId";
+    field public static final java.lang.String MIME_TYPE = "vnd.android.cursor.dir/indexables_xml_res";
+  }
+
+  public abstract class SearchIndexablesProvider extends android.content.ContentProvider {
+    ctor public SearchIndexablesProvider();
+    method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
+    method public java.lang.String getType(android.net.Uri);
+    method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
+    method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
+    method public abstract android.database.Cursor queryNonIndexableKeys(java.lang.String[]);
+    method public abstract android.database.Cursor queryRawData(java.lang.String[]);
+    method public abstract android.database.Cursor queryXmlResources(java.lang.String[]);
+    method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
+  }
+
   public class SearchRecentSuggestions {
     ctor public SearchRecentSuggestions(android.content.Context, java.lang.String, int);
     method public void clearHistory();
diff --git a/core/java/android/provider/SearchIndexableData.java b/core/java/android/provider/SearchIndexableData.java
index 7b9d1ea..5e0a76d 100644
--- a/core/java/android/provider/SearchIndexableData.java
+++ b/core/java/android/provider/SearchIndexableData.java
@@ -16,6 +16,7 @@
 
 package android.provider;
 
+import android.annotation.SystemApi;
 import android.content.Context;
 
 import java.util.Locale;
@@ -27,6 +28,7 @@
  *
  * @hide
  */
+@SystemApi
 public abstract class SearchIndexableData {
 
     /**
diff --git a/core/java/android/provider/SearchIndexableResource.java b/core/java/android/provider/SearchIndexableResource.java
index c807df2..1eb1734 100644
--- a/core/java/android/provider/SearchIndexableResource.java
+++ b/core/java/android/provider/SearchIndexableResource.java
@@ -16,6 +16,7 @@
 
 package android.provider;
 
+import android.annotation.SystemApi;
 import android.content.Context;
 
 /**
@@ -31,6 +32,7 @@
  *
  * @hide
  */
+@SystemApi
 public class SearchIndexableResource extends SearchIndexableData {
 
     /**
diff --git a/core/java/android/provider/SearchIndexablesContract.java b/core/java/android/provider/SearchIndexablesContract.java
index 1b5f72a..88326ab 100644
--- a/core/java/android/provider/SearchIndexablesContract.java
+++ b/core/java/android/provider/SearchIndexablesContract.java
@@ -16,6 +16,7 @@
 
 package android.provider;
 
+import android.annotation.SystemApi;
 import android.content.ContentResolver;
 
 /**
@@ -23,6 +24,7 @@
  *
  * @hide
  */
+@SystemApi
 public class SearchIndexablesContract {
 
     /**
diff --git a/core/java/android/provider/SearchIndexablesProvider.java b/core/java/android/provider/SearchIndexablesProvider.java
index 9c8f6d0..3120e54 100644
--- a/core/java/android/provider/SearchIndexablesProvider.java
+++ b/core/java/android/provider/SearchIndexablesProvider.java
@@ -16,6 +16,7 @@
 
 package android.provider;
 
+import android.annotation.SystemApi;
 import android.content.ContentProvider;
 import android.content.ContentValues;
 import android.content.Context;
@@ -61,6 +62,7 @@
  *
  * @hide
  */
+@SystemApi
 public abstract class SearchIndexablesProvider extends ContentProvider {
     private static final String TAG = "IndexablesProvider";
 
diff --git a/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/AndroidKeyStore.java
index f3eb317..1d16ca1 100644
--- a/keystore/java/android/security/AndroidKeyStore.java
+++ b/keystore/java/android/security/AndroidKeyStore.java
@@ -494,6 +494,19 @@
             args.addInt(KeymasterDefs.KM_TAG_DIGEST,
                     KeyStoreKeyConstraints.Digest.toKeymaster(digest));
         }
+        if (keyAlgorithm == KeyStoreKeyConstraints.Algorithm.HMAC) {
+            if (digest == null) {
+                throw new IllegalStateException("Digest algorithm must be specified for key"
+                        + " algorithm " + keyAlgorithmString);
+            }
+            Integer digestOutputSizeBytes =
+                    KeyStoreKeyConstraints.Digest.getOutputSizeBytes(digest);
+            if (digestOutputSizeBytes != null) {
+                // TODO: Remove MAC length constraint once Keymaster API no longer requires it.
+                // TODO: Switch to bits instead of bytes, once this is fixed in Keymaster
+                args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, digestOutputSizeBytes);
+            }
+        }
 
         @KeyStoreKeyConstraints.PurposeEnum int purposes = (params.getPurposes() != null)
                 ? params.getPurposes()
diff --git a/keystore/java/android/security/AndroidKeyStoreProvider.java b/keystore/java/android/security/AndroidKeyStoreProvider.java
index 598bcd8..7313c48 100644
--- a/keystore/java/android/security/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/AndroidKeyStoreProvider.java
@@ -39,5 +39,9 @@
         // javax.crypto.KeyGenerator
         put("KeyGenerator.AES", KeyStoreKeyGeneratorSpi.AES.class.getName());
         put("KeyGenerator.HmacSHA256", KeyStoreKeyGeneratorSpi.HmacSHA256.class.getName());
+
+        // javax.crypto.Mac
+        put("Mac.HmacSHA256", KeyStoreHmacSpi.HmacSHA256.class.getName());
+        put("Mac.HmacSHA256 SupportedKeyClasses", KeyStoreSecretKey.class.getName());
     }
 }
diff --git a/keystore/java/android/security/KeyStoreConnectException.java b/keystore/java/android/security/KeyStoreConnectException.java
new file mode 100644
index 0000000..4c465a4
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreConnectException.java
@@ -0,0 +1,12 @@
+package android.security;
+
+/**
+ * Indicates a communications error with keystore service.
+ *
+ * @hide
+ */
+public class KeyStoreConnectException extends CryptoOperationException {
+    public KeyStoreConnectException() {
+        super("Failed to communicate with keystore service");
+    }
+}
diff --git a/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java
new file mode 100644
index 0000000..a37ddce
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreCryptoOperationChunkedStreamer.java
@@ -0,0 +1,228 @@
+package android.security;
+
+import android.security.keymaster.OperationResult;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Helper for streaming a crypto operation's input and output via {@link KeyStore} service's
+ * {@code update} and {@code finish} operations.
+ *
+ * <p>The helper abstracts away to issues that need to be solved in most code that uses KeyStore's
+ * update and finish operations. Firstly, KeyStore's update and finish operations can consume only a
+ * limited amount of data in one go because the operations are marshalled via Binder. Secondly, the
+ * update operation may consume less data than provided, in which case the caller has to buffer
+ * the remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and
+ * {@link #doFinal(byte[], int, int) doFinal} operations which can be used to conveniently implement
+ * various JCA crypto primitives.
+ *
+ * <p>KeyStore operation through which data is streamed is abstracted away as
+ * {@link KeyStoreOperation} to avoid having this class deal with operation tokens and occasional
+ * additional parameters to update and final operations.
+ *
+ * @hide
+ */
+public class KeyStoreCryptoOperationChunkedStreamer {
+    public interface KeyStoreOperation {
+        /**
+         * Returns the result of the KeyStore update operation or null if keystore couldn't be
+         * reached.
+         */
+        OperationResult update(byte[] input);
+
+        /**
+         * Returns the result of the KeyStore finish operation or null if keystore couldn't be
+         * reached.
+         */
+        OperationResult finish(byte[] input);
+    }
+
+    // Binder buffer is about 1MB, but it's shared between all active transactions of the process.
+    // Thus, it's safer to use a much smaller upper bound.
+    private static final int DEFAULT_MAX_CHUNK_SIZE = 64 * 1024;
+    private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+
+    private final KeyStoreOperation mKeyStoreOperation;
+    private final int mMaxChunkSize;
+
+    private byte[] mBuffered = EMPTY_BYTE_ARRAY;
+    private int mBufferedOffset;
+    private int mBufferedLength;
+
+    public KeyStoreCryptoOperationChunkedStreamer(KeyStoreOperation operation) {
+        this(operation, DEFAULT_MAX_CHUNK_SIZE);
+    }
+
+    public KeyStoreCryptoOperationChunkedStreamer(KeyStoreOperation operation, int maxChunkSize) {
+        mKeyStoreOperation = operation;
+        mMaxChunkSize = maxChunkSize;
+    }
+
+    public byte[] update(byte[] input, int inputOffset, int inputLength) throws KeymasterException {
+        if (inputLength == 0) {
+            // No input provided
+            return EMPTY_BYTE_ARRAY;
+        }
+
+        ByteArrayOutputStream bufferedOutput = null;
+
+        while (inputLength > 0) {
+            byte[] chunk;
+            int inputBytesInChunk;
+            if ((mBufferedLength + inputLength) > mMaxChunkSize) {
+                // Too much input for one chunk -- extract one max-sized chunk and feed it into the
+                // update operation.
+                chunk = new byte[mMaxChunkSize];
+                System.arraycopy(mBuffered, mBufferedOffset, chunk, 0, mBufferedLength);
+                inputBytesInChunk = chunk.length - mBufferedLength;
+                System.arraycopy(input, inputOffset, chunk, mBufferedLength, inputBytesInChunk);
+            } else {
+                // All of available input fits into one chunk.
+                if ((mBufferedLength == 0) && (inputOffset == 0)
+                        && (inputLength == input.length)) {
+                    // Nothing buffered and all of input array needs to be fed into the update
+                    // operation.
+                    chunk = input;
+                    inputBytesInChunk = input.length;
+                } else {
+                    // Need to combine buffered data with input data into one array.
+                    chunk = new byte[mBufferedLength + inputLength];
+                    inputBytesInChunk = inputLength;
+                    System.arraycopy(mBuffered, mBufferedOffset, chunk, 0, mBufferedLength);
+                    System.arraycopy(input, inputOffset, chunk, mBufferedLength, inputLength);
+                }
+            }
+            // Update input array references to reflect that some of its bytes are now in mBuffered.
+            inputOffset += inputBytesInChunk;
+            inputLength -= inputBytesInChunk;
+
+            OperationResult opResult = mKeyStoreOperation.update(chunk);
+            if (opResult == null) {
+                throw new KeyStoreConnectException();
+            } else if (opResult.resultCode != KeyStore.NO_ERROR) {
+                throw KeymasterUtils.getExceptionForKeymasterError(opResult.resultCode);
+            }
+
+            if (opResult.inputConsumed == chunk.length) {
+                // The whole chunk was consumed
+                mBuffered = EMPTY_BYTE_ARRAY;
+                mBufferedOffset = 0;
+                mBufferedLength = 0;
+            } else if (opResult.inputConsumed == 0) {
+                // Nothing was consumed. More input needed.
+                if (inputLength > 0) {
+                    // More input is available, but it wasn't included into the previous chunk
+                    // because the chunk reached its maximum permitted size.
+                    // Shouldn't have happened.
+                    throw new CryptoOperationException("Nothing consumed from max-sized chunk: "
+                            + chunk.length + " bytes");
+                }
+                mBuffered = chunk;
+                mBufferedOffset = 0;
+                mBufferedLength = chunk.length;
+            } else if (opResult.inputConsumed < chunk.length) {
+                // The chunk was consumed only partially -- buffer the rest of the chunk
+                mBuffered = chunk;
+                mBufferedOffset = opResult.inputConsumed;
+                mBufferedLength = chunk.length - opResult.inputConsumed;
+            } else {
+                throw new CryptoOperationException("Consumed more than provided: "
+                        + opResult.inputConsumed + ", provided: " + chunk.length);
+            }
+
+            if ((opResult.output != null) && (opResult.output.length > 0)) {
+                if (inputLength > 0) {
+                    // More output might be produced in this loop -- buffer the current output
+                    if (bufferedOutput == null) {
+                        bufferedOutput = new ByteArrayOutputStream();
+                        try {
+                            bufferedOutput.write(opResult.output);
+                        } catch (IOException e) {
+                            throw new CryptoOperationException("Failed to buffer output", e);
+                        }
+                    }
+                } else {
+                    // No more output will be produced in this loop
+                    if (bufferedOutput == null) {
+                        // No previously buffered output
+                        return opResult.output;
+                    } else {
+                        // There was some previously buffered output
+                        try {
+                            bufferedOutput.write(opResult.output);
+                        } catch (IOException e) {
+                            throw new CryptoOperationException("Failed to buffer output", e);
+                        }
+                        return bufferedOutput.toByteArray();
+                    }
+                }
+            }
+        }
+
+        if (bufferedOutput == null) {
+            // No output produced
+            return EMPTY_BYTE_ARRAY;
+        } else {
+            return bufferedOutput.toByteArray();
+        }
+    }
+
+    public byte[] doFinal(byte[] input, int inputOffset, int inputLength)
+            throws KeymasterException {
+        if (inputLength == 0) {
+            // No input provided -- simplify the rest of the code
+            input = EMPTY_BYTE_ARRAY;
+            inputOffset = 0;
+        }
+
+        byte[] updateOutput = null;
+        if ((mBufferedLength + inputLength) > mMaxChunkSize) {
+            updateOutput = update(input, inputOffset, inputLength);
+            inputOffset += inputLength;
+            inputLength = 0;
+        }
+        // All of available input fits into one chunk.
+
+        byte[] finalChunk;
+        if ((mBufferedLength == 0) && (inputOffset == 0)
+                && (inputLength == input.length)) {
+            // Nothing buffered and all of input array needs to be fed into the finish operation.
+            finalChunk = input;
+        } else {
+            // Need to combine buffered data with input data into one array.
+            finalChunk = new byte[mBufferedLength + inputLength];
+            System.arraycopy(mBuffered, mBufferedOffset, finalChunk, 0, mBufferedLength);
+            System.arraycopy(input, inputOffset, finalChunk, mBufferedLength, inputLength);
+        }
+        mBuffered = EMPTY_BYTE_ARRAY;
+        mBufferedLength = 0;
+        mBufferedOffset = 0;
+
+        OperationResult opResult = mKeyStoreOperation.finish(finalChunk);
+        if (opResult == null) {
+            throw new KeyStoreConnectException();
+        } else if (opResult.resultCode != KeyStore.NO_ERROR) {
+            throw KeymasterUtils.getExceptionForKeymasterError(opResult.resultCode);
+        }
+
+        if (opResult.inputConsumed != finalChunk.length) {
+            throw new CryptoOperationException("Unexpected number of bytes consumed by finish: "
+                    + opResult.inputConsumed + " instead of " + finalChunk.length);
+        }
+
+        // Return the concatenation of the output of update and finish.
+        byte[] result;
+        byte[] finishOutput = opResult.output;
+        if ((updateOutput == null) || (updateOutput.length == 0)) {
+            result = finishOutput;
+        } else if ((finishOutput == null) || (finishOutput.length == 0)) {
+            result = updateOutput;
+        } else {
+            result = new byte[updateOutput.length + finishOutput.length];
+            System.arraycopy(updateOutput, 0, result, 0, updateOutput.length);
+            System.arraycopy(finishOutput, 0, result, updateOutput.length, finishOutput.length);
+        }
+        return (result != null) ? result : EMPTY_BYTE_ARRAY;
+    }
+}
diff --git a/keystore/java/android/security/KeyStoreHmacSpi.java b/keystore/java/android/security/KeyStoreHmacSpi.java
new file mode 100644
index 0000000..3080d7b
--- /dev/null
+++ b/keystore/java/android/security/KeyStoreHmacSpi.java
@@ -0,0 +1,174 @@
+package android.security;
+
+import android.os.IBinder;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterDefs;
+import android.security.keymaster.OperationResult;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.MacSpi;
+
+/**
+ * {@link MacSpi} which provides HMAC implementations backed by Android KeyStore.
+ *
+ * @hide
+ */
+public abstract class KeyStoreHmacSpi extends MacSpi {
+
+    public static class HmacSHA256 extends KeyStoreHmacSpi {
+        public HmacSHA256() {
+            super(KeyStoreKeyConstraints.Digest.SHA256, 256 / 8);
+        }
+    }
+
+    private final KeyStore mKeyStore = KeyStore.getInstance();
+    private final @KeyStoreKeyConstraints.DigestEnum int mDigest;
+    private final int mMacSizeBytes;
+
+    private String mKeyAliasInKeyStore;
+
+    // The fields below are reset by the engineReset operation.
+    private KeyStoreCryptoOperationChunkedStreamer mChunkedStreamer;
+    private IBinder mOperationToken;
+
+    protected KeyStoreHmacSpi(@KeyStoreKeyConstraints.DigestEnum int digest, int macSizeBytes) {
+        mDigest = digest;
+        mMacSizeBytes = macSizeBytes;
+    }
+
+    @Override
+    protected int engineGetMacLength() {
+        return mMacSizeBytes;
+    }
+
+    @Override
+    protected void engineInit(Key key, AlgorithmParameterSpec params) throws InvalidKeyException,
+            InvalidAlgorithmParameterException {
+        if (key == null) {
+            throw new InvalidKeyException("key == null");
+        } else if (!(key instanceof KeyStoreSecretKey)) {
+            throw new InvalidKeyException(
+                    "Only Android KeyStore secret keys supported. Key: " + key);
+        }
+
+        if (params != null) {
+            throw new InvalidAlgorithmParameterException(
+                    "Unsupported algorithm parameters: " + params);
+        }
+
+        mKeyAliasInKeyStore = ((KeyStoreSecretKey) key).getAlias();
+        engineReset();
+    }
+
+    @Override
+    protected void engineReset() {
+        IBinder operationToken = mOperationToken;
+        if (operationToken != null) {
+            mOperationToken = null;
+            mKeyStore.abort(operationToken);
+        }
+        mChunkedStreamer = null;
+
+        KeymasterArguments keymasterArgs = new KeymasterArguments();
+        keymasterArgs.addInt(KeymasterDefs.KM_TAG_DIGEST, mDigest);
+
+        OperationResult opResult = mKeyStore.begin(mKeyAliasInKeyStore,
+                KeymasterDefs.KM_PURPOSE_SIGN,
+                true,
+                keymasterArgs,
+                null,
+                new KeymasterArguments());
+        if (opResult == null) {
+            throw new KeyStoreConnectException();
+        } else if (opResult.resultCode != KeyStore.NO_ERROR) {
+            throw new CryptoOperationException("Failed to start keystore operation",
+                    KeymasterUtils.getExceptionForKeymasterError(opResult.resultCode));
+        }
+        mOperationToken = opResult.token;
+        if (mOperationToken == null) {
+            throw new CryptoOperationException("Keystore returned null operation token");
+        }
+        mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer(
+                new KeyStoreStreamingConsumer(mKeyStore, mOperationToken));
+    }
+
+    @Override
+    protected void engineUpdate(byte input) {
+        engineUpdate(new byte[] {input}, 0, 1);
+    }
+
+    @Override
+    protected void engineUpdate(byte[] input, int offset, int len) {
+        if (mChunkedStreamer == null) {
+            throw new IllegalStateException("Not initialized");
+        }
+
+        byte[] output;
+        try {
+            output = mChunkedStreamer.update(input, offset, len);
+        } catch (KeymasterException e) {
+            throw new CryptoOperationException("Keystore operation failed", e);
+        }
+        if ((output != null) && (output.length != 0)) {
+            throw new CryptoOperationException("Update operation unexpectedly produced output");
+        }
+    }
+
+    @Override
+    protected byte[] engineDoFinal() {
+        if (mChunkedStreamer == null) {
+            throw new IllegalStateException("Not initialized");
+        }
+
+        byte[] result;
+        try {
+            result = mChunkedStreamer.doFinal(null, 0, 0);
+        } catch (KeymasterException e) {
+            throw new CryptoOperationException("Keystore operation failed", e);
+        }
+
+        engineReset();
+        return result;
+    }
+
+    @Override
+    public void finalize() throws Throwable {
+        try {
+            IBinder operationToken = mOperationToken;
+            if (operationToken != null) {
+                mOperationToken = null;
+                mKeyStore.abort(operationToken);
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * KeyStore-backed consumer of {@code MacSpi}'s chunked stream.
+     */
+    private static class KeyStoreStreamingConsumer
+            implements KeyStoreCryptoOperationChunkedStreamer.KeyStoreOperation {
+        private final KeyStore mKeyStore;
+        private final IBinder mOperationToken;
+
+        private KeyStoreStreamingConsumer(KeyStore keyStore, IBinder operationToken) {
+            mKeyStore = keyStore;
+            mOperationToken = operationToken;
+        }
+
+        @Override
+        public OperationResult update(byte[] input) {
+            return mKeyStore.update(mOperationToken, null, input);
+        }
+
+        @Override
+        public OperationResult finish(byte[] input) {
+            return mKeyStore.finish(mOperationToken, null, input);
+        }
+    }
+}
diff --git a/keystore/java/android/security/KeyStoreKeyConstraints.java b/keystore/java/android/security/KeyStoreKeyConstraints.java
index 47bb1cc..b5e2436 100644
--- a/keystore/java/android/security/KeyStoreKeyConstraints.java
+++ b/keystore/java/android/security/KeyStoreKeyConstraints.java
@@ -401,6 +401,20 @@
                     throw new IllegalArgumentException("Unknown digest: " + digest);
             }
         }
+
+        /**
+         * @hide
+         */
+        public static Integer getOutputSizeBytes(@DigestEnum int digest) {
+            switch (digest) {
+                case NONE:
+                    return null;
+                case SHA256:
+                    return 256 / 8;
+                default:
+                    throw new IllegalArgumentException("Unknown digest: " + digest);
+            }
+        }
     }
 
     @Retention(RetentionPolicy.SOURCE)
diff --git a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
index 86950dd..f1f9436 100644
--- a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
@@ -28,7 +28,8 @@
         public HmacSHA256() {
             super(KeyStoreKeyConstraints.Algorithm.HMAC,
                     KeyStoreKeyConstraints.Digest.SHA256,
-                    256);
+                    KeyStoreKeyConstraints.Digest.getOutputSizeBytes(
+                            KeyStoreKeyConstraints.Digest.SHA256) * 8);
         }
     }
 
@@ -76,6 +77,19 @@
             args.addInt(KeymasterDefs.KM_TAG_DIGEST,
                     KeyStoreKeyConstraints.Digest.toKeymaster(mDigest));
         }
+        if (mAlgorithm == KeyStoreKeyConstraints.Algorithm.HMAC) {
+            if (mDigest == null) {
+                throw new IllegalStateException("Digest algorithm must be specified for key"
+                        + " algorithm " + KeyStoreKeyConstraints.Algorithm.toString(mAlgorithm));
+            }
+            Integer digestOutputSizeBytes =
+                    KeyStoreKeyConstraints.Digest.getOutputSizeBytes(mDigest);
+            if (digestOutputSizeBytes != null) {
+                // TODO: Remove MAC length constraint once Keymaster API no longer requires it.
+                // TODO: Switch to bits instead of bytes, once this is fixed in Keymaster
+                args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, digestOutputSizeBytes);
+            }
+        }
         int keySizeBits = (spec.getKeySize() != null) ? spec.getKeySize() : mDefaultKeySizeBits;
         args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keySizeBits);
         @KeyStoreKeyConstraints.PurposeEnum int purposes = (spec.getPurposes() != null)
diff --git a/keystore/java/android/security/KeymasterException.java b/keystore/java/android/security/KeymasterException.java
index 4ff7115..bc8198f 100644
--- a/keystore/java/android/security/KeymasterException.java
+++ b/keystore/java/android/security/KeymasterException.java
@@ -7,7 +7,14 @@
  */
 public class KeymasterException extends Exception {
 
-    public KeymasterException(String message) {
+    private final int mErrorCode;
+
+    public KeymasterException(int errorCode, String message) {
         super(message);
+        mErrorCode = errorCode;
+    }
+
+    public int getErrorCode() {
+        return mErrorCode;
     }
 }
diff --git a/keystore/java/android/security/KeymasterUtils.java b/keystore/java/android/security/KeymasterUtils.java
index e6e88c7..4f17586 100644
--- a/keystore/java/android/security/KeymasterUtils.java
+++ b/keystore/java/android/security/KeymasterUtils.java
@@ -13,9 +13,11 @@
             case KeymasterDefs.KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT:
                 // The name of this parameter significantly differs between Keymaster and framework
                 // APIs. Use the framework wording to make life easier for developers.
-                return new KeymasterException("Invalid user authentication validity duration");
+                return new KeymasterException(keymasterErrorCode,
+                        "Invalid user authentication validity duration");
             default:
-                return new KeymasterException(KeymasterDefs.getErrorMessage(keymasterErrorCode));
+                return new KeymasterException(keymasterErrorCode,
+                        KeymasterDefs.getErrorMessage(keymasterErrorCode));
         }
     }
 }
diff --git a/packages/Keyguard/src/com/android/keyguard/EmergencyButton.java b/packages/Keyguard/src/com/android/keyguard/EmergencyButton.java
index 3627e3e..7d5bf6b 100644
--- a/packages/Keyguard/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/Keyguard/src/com/android/keyguard/EmergencyButton.java
@@ -36,7 +36,10 @@
  * allows the user to return to the call.
  */
 public class EmergencyButton extends Button {
-    private static final String ACTION_EMERGENCY_DIAL = "com.android.phone.EmergencyDialer.DIAL";
+    private static final Intent INTENT_EMERGENCY_DIAL = new Intent()
+            .setAction("com.android.phone.EmergencyDialer.DIAL")
+            .setPackage("com.android.phone")
+            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
 
     KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
 
@@ -112,12 +115,9 @@
                 mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall();
             }
         } else {
-            final boolean bypassHandler = true;
-            KeyguardUpdateMonitor.getInstance(mContext).reportEmergencyCallAction(bypassHandler);
-            Intent intent = new Intent(ACTION_EMERGENCY_DIAL);
-            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                    | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-            getContext().startActivityAsUser(intent,
+            KeyguardUpdateMonitor.getInstance(mContext).reportEmergencyCallAction(
+                    true /* bypassHandler */);
+            getContext().startActivityAsUser(INTENT_EMERGENCY_DIAL,
                     new UserHandle(mLockPatternUtils.getCurrentUser()));
         }
     }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index be6550c..87cf06e 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1398,6 +1398,14 @@
                     public void onDebug() {
                         // no-op
                     }
+                    @Override
+                    public void onDown() {
+                        mOrientationListener.onTouchStart();
+                    }
+                    @Override
+                    public void onUpOrCancel() {
+                        mOrientationListener.onTouchEnd();
+                    }
                 });
         mImmersiveModeConfirmation = new ImmersiveModeConfirmation(mContext);
         mWindowManagerFuncs.registerPointerEventListener(mSystemGestures);
diff --git a/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java
index cfa631f..627b328 100644
--- a/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java
+++ b/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java
@@ -75,6 +75,7 @@
                 mDebugFireable = true;
                 mDownPointers = 0;
                 captureDown(event, 0);
+                mCallbacks.onDown();
                 break;
             case MotionEvent.ACTION_POINTER_DOWN:
                 captureDown(event, event.getActionIndex());
@@ -106,6 +107,7 @@
             case MotionEvent.ACTION_CANCEL:
                 mSwipeFireable = false;
                 mDebugFireable = false;
+                mCallbacks.onUpOrCancel();
                 break;
             default:
                 if (DEBUG) Slog.d(TAG, "Ignoring " + event);
@@ -192,6 +194,8 @@
         void onSwipeFromTop();
         void onSwipeFromBottom();
         void onSwipeFromRight();
+        void onDown();
+        void onUpOrCancel();
         void onDebug();
     }
 }
diff --git a/services/core/java/com/android/server/policy/WindowOrientationListener.java b/services/core/java/com/android/server/policy/WindowOrientationListener.java
index 0118127..a33ee4c 100644
--- a/services/core/java/com/android/server/policy/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/policy/WindowOrientationListener.java
@@ -22,6 +22,7 @@
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.os.Handler;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.util.Log;
 import android.util.Slog;
@@ -133,6 +134,20 @@
         }
     }
 
+    public void onTouchStart() {
+        synchronized (mLock) {
+            mSensorEventListener.onTouchStartLocked();
+        }
+    }
+
+    public void onTouchEnd() {
+        long whenElapsedNanos = SystemClock.elapsedRealtimeNanos();
+
+        synchronized (mLock) {
+            mSensorEventListener.onTouchEndLocked(whenElapsedNanos);
+        }
+    }
+
     /**
      * Sets the current rotation.
      *
@@ -269,6 +284,11 @@
         private static final long PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS =
                 500 * NANOS_PER_MS;
 
+        // The minimum amount of time that must have elapsed since the screen was last touched
+        // before the proposed rotation can change.
+        private static final long PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS =
+                500 * NANOS_PER_MS;
+
         // If the tilt angle remains greater than the specified angle for a minimum of
         // the specified time, then the device is deemed to be lying flat
         // (just chillin' on a table).
@@ -398,6 +418,10 @@
         private long mAccelerationTimestampNanos;
         private boolean mAccelerating;
 
+        // Timestamp when the last touch to the touch screen ended
+        private long mTouchEndedTimestampNanos = Long.MIN_VALUE;
+        private boolean mTouched;
+
         // Whether we are locked into an overhead usage mode.
         private boolean mOverhead;
 
@@ -422,6 +446,7 @@
             pw.println(prefix + "mSwinging=" + mSwinging);
             pw.println(prefix + "mAccelerating=" + mAccelerating);
             pw.println(prefix + "mOverhead=" + mOverhead);
+            pw.println(prefix + "mTouched=" + mTouched);
         }
 
         @Override
@@ -601,6 +626,7 @@
                             + ", isFlat=" + isFlat
                             + ", isSwinging=" + isSwinging
                             + ", isOverhead=" + mOverhead
+                            + ", isTouched=" + mTouched
                             + ", timeUntilSettledMS=" + remainingMS(now,
                                     mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS)
                             + ", timeUntilAccelerationDelayExpiredMS=" + remainingMS(now,
@@ -608,7 +634,9 @@
                             + ", timeUntilFlatDelayExpiredMS=" + remainingMS(now,
                                     mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS)
                             + ", timeUntilSwingDelayExpiredMS=" + remainingMS(now,
-                                    mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS));
+                                    mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS)
+                            + ", timeUntilTouchDelayExpiredMS=" + remainingMS(now,
+                                    mTouchEndedTimestampNanos + PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS));
                 }
             }
 
@@ -710,6 +738,12 @@
                 return false;
             }
 
+            // The last touch must have ended sufficiently long ago.
+            if (mTouched || now < mTouchEndedTimestampNanos
+                    + PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS) {
+                return false;
+            }
+
             // Looks good!
             return true;
         }
@@ -796,5 +830,14 @@
         private float remainingMS(long now, long until) {
             return now >= until ? 0 : (until - now) * 0.000001f;
         }
+
+        private void onTouchStartLocked() {
+            mTouched = true;
+        }
+
+        private void onTouchEndLocked(long whenElapsedNanos) {
+            mTouched = false;
+            mTouchEndedTimestampNanos = whenElapsedNanos;
+        }
     }
 }