Merge "Adding per UID WiFi power distribution."
diff --git a/api/current.txt b/api/current.txt
index b2c3509..bf55b7c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -25842,6 +25842,8 @@
     method public static java.lang.String getVersion(android.content.Context);
     field public static final java.lang.String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
     field public static final java.lang.String ACTION_IMAGE_CAPTURE_SECURE = "android.media.action.IMAGE_CAPTURE_SECURE";
+    field public static final java.lang.String ACTION_STILL_IMAGE_CAMERA_COOLDOWN = "android.media.action.STILL_IMAGE_CAMERA_COOLDOWN";
+    field public static final java.lang.String ACTION_STILL_IMAGE_CAMERA_PREWARM = "android.media.action.STILL_IMAGE_CAMERA_PREWARM";
     field public static final java.lang.String ACTION_VIDEO_CAPTURE = "android.media.action.VIDEO_CAPTURE";
     field public static final java.lang.String AUTHORITY = "media";
     field public static final java.lang.String EXTRA_DURATION_LIMIT = "android.intent.extra.durationLimit";
@@ -27637,7 +27639,7 @@
     method public android.renderscript.ScriptGroup2.Closure addInvoke(android.renderscript.Script.InvokeID, java.lang.Object...);
     method public android.renderscript.ScriptGroup2.Closure addKernel(android.renderscript.Script.KernelID, android.renderscript.Type, java.lang.Object[], java.util.Map<android.renderscript.Script.FieldID, java.lang.Object>);
     method public android.renderscript.ScriptGroup2.Closure addKernel(android.renderscript.Script.KernelID, android.renderscript.Type, java.lang.Object...);
-    method public android.renderscript.ScriptGroup2 create(android.renderscript.ScriptGroup2.Future...);
+    method public android.renderscript.ScriptGroup2 create(java.lang.String, android.renderscript.ScriptGroup2.Future...);
   }
 
   public static class ScriptGroup2.Closure extends android.renderscript.BaseObj {
diff --git a/api/system-current.txt b/api/system-current.txt
index de930bf..1152de5 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -27725,6 +27725,8 @@
     method public static java.lang.String getVersion(android.content.Context);
     field public static final java.lang.String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
     field public static final java.lang.String ACTION_IMAGE_CAPTURE_SECURE = "android.media.action.IMAGE_CAPTURE_SECURE";
+    field public static final java.lang.String ACTION_STILL_IMAGE_CAMERA_COOLDOWN = "android.media.action.STILL_IMAGE_CAMERA_COOLDOWN";
+    field public static final java.lang.String ACTION_STILL_IMAGE_CAMERA_PREWARM = "android.media.action.STILL_IMAGE_CAMERA_PREWARM";
     field public static final java.lang.String ACTION_VIDEO_CAPTURE = "android.media.action.VIDEO_CAPTURE";
     field public static final java.lang.String AUTHORITY = "media";
     field public static final java.lang.String EXTRA_DURATION_LIMIT = "android.intent.extra.durationLimit";
@@ -29623,7 +29625,7 @@
     method public android.renderscript.ScriptGroup2.Closure addInvoke(android.renderscript.Script.InvokeID, java.lang.Object...);
     method public android.renderscript.ScriptGroup2.Closure addKernel(android.renderscript.Script.KernelID, android.renderscript.Type, java.lang.Object[], java.util.Map<android.renderscript.Script.FieldID, java.lang.Object>);
     method public android.renderscript.ScriptGroup2.Closure addKernel(android.renderscript.Script.KernelID, android.renderscript.Type, java.lang.Object...);
-    method public android.renderscript.ScriptGroup2 create(android.renderscript.ScriptGroup2.Future...);
+    method public android.renderscript.ScriptGroup2 create(java.lang.String, android.renderscript.ScriptGroup2.Future...);
   }
 
   public static class ScriptGroup2.Closure extends android.renderscript.BaseObj {
diff --git a/core/java/android/animation/TimeAnimator.java b/core/java/android/animation/TimeAnimator.java
index 1738ade..1ba68df 100644
--- a/core/java/android/animation/TimeAnimator.java
+++ b/core/java/android/animation/TimeAnimator.java
@@ -51,6 +51,7 @@
     public void setCurrentPlayTime(long playTime) {
         long currentTime = AnimationUtils.currentAnimationTimeMillis();
         mStartTime = Math.max(mStartTime, currentTime - playTime);
+        mStartTimeCommitted = true; // do not allow start time to be compensated for jank
         animationFrame(currentTime);
     }
 
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 85dc832..2386007 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -20,6 +20,7 @@
 import android.os.Looper;
 import android.os.Trace;
 import android.util.AndroidRuntimeException;
+import android.util.Log;
 import android.view.Choreographer;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.AnimationUtils;
@@ -64,6 +65,8 @@
  */
 @SuppressWarnings("unchecked")
 public class ValueAnimator extends Animator {
+    private static final String TAG = "ValueAnimator";
+    private static final boolean DEBUG = false;
 
     /**
      * Internal constants
@@ -85,12 +88,30 @@
      * to clone() to make deep copies of them.
      */
 
-    // The first time that the animation's animateFrame() method is called. This time is used to
-    // determine elapsed time (and therefore the elapsed fraction) in subsequent calls
-    // to animateFrame()
+    /**
+     * The first time that the animation's animateFrame() method is called. This time is used to
+     * determine elapsed time (and therefore the elapsed fraction) in subsequent calls
+     * to animateFrame().
+     *
+     * Whenever mStartTime is set, you must also update mStartTimeCommitted.
+     */
     long mStartTime;
 
     /**
+     * When true, the start time has been firmly committed as a chosen reference point in
+     * time by which the progress of the animation will be evaluated.  When false, the
+     * start time may be updated when the first animation frame is committed so as
+     * to compensate for jank that may have occurred between when the start time was
+     * initialized and when the frame was actually drawn.
+     *
+     * This flag is generally set to false during the first frame of the animation
+     * when the animation playing state transitions from STOPPED to RUNNING or
+     * resumes after having been paused.  This flag is set to true when the start time
+     * is firmly committed and should not be further compensated for jank.
+     */
+    boolean mStartTimeCommitted;
+
+    /**
      * Set when setCurrentPlayTime() is called. If negative, animation is not currently seeked
      * to a value.
      */
@@ -528,6 +549,7 @@
      * value makes it easier to compose statements together that construct and then set the
      * duration, as in <code>ValueAnimator.ofInt(0, 10).setDuration(500).start()</code>.
      */
+    @Override
     public ValueAnimator setDuration(long duration) {
         if (duration < 0) {
             throw new IllegalArgumentException("Animators cannot have negative duration: " +
@@ -547,6 +569,7 @@
      *
      * @return The length of the animation, in milliseconds.
      */
+    @Override
     public long getDuration() {
         return mUnscaledDuration;
     }
@@ -608,6 +631,7 @@
         long seekTime = (long) (mDuration * fraction);
         long currentTime = AnimationUtils.currentAnimationTimeMillis();
         mStartTime = currentTime - seekTime;
+        mStartTimeCommitted = true; // do not allow start time to be compensated for jank
         if (mPlayingState != RUNNING) {
             mSeekFraction = fraction;
             mPlayingState = SEEKED;
@@ -644,7 +668,7 @@
      * @hide
      */
     @SuppressWarnings("unchecked")
-    protected static class AnimationHandler implements Runnable {
+    protected static class AnimationHandler {
         // The per-thread list of all active animations
         /** @hide */
         protected final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>();
@@ -667,6 +691,7 @@
 
         private final Choreographer mChoreographer;
         private boolean mAnimationScheduled;
+        private long mLastFrameTime;
 
         private AnimationHandler() {
             mChoreographer = Choreographer.getInstance();
@@ -679,7 +704,9 @@
             scheduleAnimation();
         }
 
-        private void doAnimationFrame(long frameTime) {
+        void doAnimationFrame(long frameTime) {
+            mLastFrameTime = frameTime;
+
             // mPendingAnimations holds any animations that have requested to be started
             // We're going to clear mPendingAnimations, but starting animation may
             // cause more to be added to the pending list (for example, if one animation
@@ -700,6 +727,7 @@
                     }
                 }
             }
+
             // Next, process animations currently sitting on the delayed queue, adding
             // them to the active animations if they are ready
             int numDelayedAnims = mDelayedAnims.size();
@@ -740,6 +768,9 @@
                 mEndingAnims.clear();
             }
 
+            // Schedule final commit for the frame.
+            mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, mCommit, null);
+
             // If there are still active or delayed animations, schedule a future call to
             // onAnimate to process the next frame of the animations.
             if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {
@@ -747,19 +778,37 @@
             }
         }
 
-        // Called by the Choreographer.
-        @Override
-        public void run() {
-            mAnimationScheduled = false;
-            doAnimationFrame(mChoreographer.getFrameTime());
+        void commitAnimationFrame(long frameTime) {
+            final long adjustment = frameTime - mLastFrameTime;
+            final int numAnims = mAnimations.size();
+            for (int i = 0; i < numAnims; ++i) {
+                mAnimations.get(i).commitAnimationFrame(adjustment);
+            }
         }
 
         private void scheduleAnimation() {
             if (!mAnimationScheduled) {
-                mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
+                mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimate, null);
                 mAnimationScheduled = true;
             }
         }
+
+        // Called by the Choreographer.
+        final Runnable mAnimate = new Runnable() {
+            @Override
+            public void run() {
+                mAnimationScheduled = false;
+                doAnimationFrame(mChoreographer.getFrameTime());
+            }
+        };
+
+        // Called by the Choreographer.
+        final Runnable mCommit = new Runnable() {
+            @Override
+            public void run() {
+                commitAnimationFrame(mChoreographer.getFrameTime());
+            }
+        };
     }
 
     /**
@@ -768,6 +817,7 @@
      *
      * @return the number of milliseconds to delay running the animation
      */
+    @Override
     public long getStartDelay() {
         return mUnscaledStartDelay;
     }
@@ -778,6 +828,7 @@
 
      * @param startDelay The amount of the delay, in milliseconds
      */
+    @Override
     public void setStartDelay(long startDelay) {
         this.mStartDelay = (long)(startDelay * sDurationScale);
         mUnscaledStartDelay = startDelay;
@@ -1148,6 +1199,7 @@
             long currentPlayTime = currentTime - mStartTime;
             long timeLeft = mDuration - currentPlayTime;
             mStartTime = currentTime - timeLeft;
+            mStartTimeCommitted = true; // do not allow start time to be compensated for jank
             mReversing = !mReversing;
         } else if (mStarted) {
             end();
@@ -1254,9 +1306,9 @@
         }
         long deltaTime = currentTime - mDelayStartTime;
         if (deltaTime > mStartDelay) {
-            // startDelay ended - start the anim and record the
-            // mStartTime appropriately
-            mStartTime = currentTime - (deltaTime - mStartDelay);
+            // startDelay ended - start the anim and record the mStartTime appropriately
+            mStartTime = mDelayStartTime + mStartDelay;
+            mStartTimeCommitted = true; // do not allow start time to be compensated for jank
             mPlayingState = RUNNING;
             return true;
         }
@@ -1264,6 +1316,22 @@
     }
 
     /**
+     * Applies an adjustment to the animation to compensate for jank between when
+     * the animation first ran and when the frame was drawn.
+     */
+    void commitAnimationFrame(long adjustment) {
+        if (!mStartTimeCommitted) {
+            mStartTimeCommitted = true;
+            if (mPlayingState == RUNNING && adjustment > 0) {
+                mStartTime += adjustment;
+                if (DEBUG) {
+                    Log.d(TAG, "Adjusted start time by " + adjustment + " ms: " + toString());
+                }
+            }
+        }
+    }
+
+    /**
      * This internal function processes a single animation frame for a given animation. The
      * currentTime parameter is the timing pulse sent by the handler, used to calculate the
      * elapsed duration, and therefore
@@ -1303,6 +1371,8 @@
                     mCurrentIteration += (int) fraction;
                     fraction = fraction % 1f;
                     mStartTime += mDuration;
+                    // Note: We do not need to update the value of mStartTimeCommitted here
+                    // since we just added a duration offset.
                 } else {
                     done = true;
                     fraction = Math.min(fraction, 1.0f);
@@ -1334,6 +1404,7 @@
                 mStartTime = frameTime - seekTime;
                 mSeekFraction = -1;
             }
+            mStartTimeCommitted = false; // allow start time to be compensated for jank
         }
         if (mPaused) {
             if (mPauseTime < 0) {
@@ -1345,6 +1416,7 @@
             if (mPauseTime > 0) {
                 // Offset by the duration that the animation was paused
                 mStartTime += (frameTime - mPauseTime);
+                mStartTimeCommitted = false; // allow start time to be compensated for jank
             }
         }
         // The frame time might be before the start time during the first frame of
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 81b1583..ed05321 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -94,7 +94,7 @@
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
-import android.renderscript.RenderScript;
+import android.renderscript.RenderScriptCacheDir;
 import android.security.AndroidKeyStoreProvider;
 
 import com.android.internal.app.IVoiceInteractor;
@@ -3214,7 +3214,7 @@
                 if (cv == null) {
                     mThumbnailCanvas = cv = new Canvas();
                 }
-    
+
                 cv.setBitmap(thumbnail);
                 if (!r.activity.onCreateThumbnail(thumbnail, cv)) {
                     mAvailThumbnailBitmap = thumbnail;
@@ -3516,12 +3516,12 @@
 
     private void handleWindowVisibility(IBinder token, boolean show) {
         ActivityClientRecord r = mActivities.get(token);
-        
+
         if (r == null) {
             Log.w(TAG, "handleWindowVisibility: no activity for token " + token);
             return;
         }
-        
+
         if (!show && !r.stopped) {
             performStopActivityInner(r, null, show, false);
         } else if (show && r.stopped) {
@@ -3953,10 +3953,10 @@
                 }
             }
         }
-        
+
         if (DEBUG_CONFIGURATION) Slog.v(TAG, "Relaunching activity "
                 + tmp.token + ": changedConfig=" + changedConfig);
-        
+
         // If there was a pending configuration change, execute it first.
         if (changedConfig != null) {
             mCurDefaultDisplayDpi = changedConfig.densityDpi;
@@ -4154,7 +4154,7 @@
             if (config == null) {
                 return;
             }
-            
+
             if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: "
                     + config);
 
@@ -4292,7 +4292,7 @@
         ApplicationPackageManager.handlePackageBroadcast(cmd, packages,
                 hasPkgInfo);
     }
-        
+
     final void handleLowMemory() {
         ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(true, null);
 
@@ -4339,10 +4339,10 @@
             String[] packages = getPackageManager().getPackagesForUid(uid);
 
             // If there are several packages in this application we won't
-            // initialize the graphics disk caches 
+            // initialize the graphics disk caches
             if (packages != null && packages.length == 1) {
                 HardwareRenderer.setupDiskCache(cacheDir);
-                RenderScript.setupDiskCache(cacheDir);
+                RenderScriptCacheDir.setupDiskCache(cacheDir);
             }
         } catch (RemoteException e) {
             // Ignore
@@ -5267,7 +5267,7 @@
                         if (mPendingConfiguration == null ||
                                 mPendingConfiguration.isOtherSeqNewer(newConfig)) {
                             mPendingConfiguration = newConfig;
-                            
+
                             sendMessage(H.CONFIGURATION_CHANGED, newConfig);
                         }
                     }
diff --git a/core/java/android/inputmethodservice/ExtractEditLayout.java b/core/java/android/inputmethodservice/ExtractEditLayout.java
index 5696839..e902443 100644
--- a/core/java/android/inputmethodservice/ExtractEditLayout.java
+++ b/core/java/android/inputmethodservice/ExtractEditLayout.java
@@ -163,6 +163,8 @@
             mCallback.onDestroyActionMode(this);
             mCallback = null;
 
+            mMenu.close();
+
             mExtractActionButton.setVisibility(VISIBLE);
             mEditButton.setVisibility(INVISIBLE);
 
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 5afbd6d..7565654b 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -226,6 +226,35 @@
     public static final String INTENT_ACTION_STILL_IMAGE_CAMERA = "android.media.action.STILL_IMAGE_CAMERA";
 
     /**
+     * The name of the Intent action used to indicate that a camera launch might be imminent. This
+     * broadcast should be targeted to the package that is receiving
+     * {@link #INTENT_ACTION_STILL_IMAGE_CAMERA} or
+     * {@link #INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE}, depending on the context. If such
+     * intent would launch the resolver activity, this broadcast should not be sent at all.
+     * <p>
+     * A receiver of this broadcast should do the absolute minimum amount of work to initialize the
+     * camera in order to reduce startup time in likely case that shortly after an actual camera
+     * launch intent would be sent.
+     * <p>
+     * In case the actual intent will not be fired, the target package will receive
+     * {@link #ACTION_STILL_IMAGE_CAMERA_COOLDOWN}. However, it is recommended that the receiver
+     * also implements a timeout to close the camera after receiving this intent, as there is no
+     * guarantee that {@link #ACTION_STILL_IMAGE_CAMERA_COOLDOWN} will be delivered.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_STILL_IMAGE_CAMERA_PREWARM = "android.media.action.STILL_IMAGE_CAMERA_PREWARM";
+
+    /**
+     * The name of the Intent action used to indicate that an imminent camera launch has been
+     * cancelled by the user. This broadcast should be targeted to the package that has received
+     * {@link #ACTION_STILL_IMAGE_CAMERA_PREWARM}.
+     * <p>
+     * A receiver of this broadcast should close the camera immediately.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_STILL_IMAGE_CAMERA_COOLDOWN = "android.media.action.STILL_IMAGE_CAMERA_COOLDOWN";
+
+    /**
      * The name of the Intent action used to launch a camera in still image mode
      * for use when the device is secured (e.g. with a pin, password, pattern,
      * or face unlock). Applications responding to this intent must not expose
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index e94a312..5e2accd8 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -171,6 +171,10 @@
     public static final int KM_KEY_FORMAT_PKCS12 = 2;
     public static final int KM_KEY_FORMAT_RAW = 3;
 
+    // User authenticators.
+    public static final int HW_AUTH_PASSWORD = 1 << 0;
+    public static final int HW_AUTH_FINGERPRINT = 1 << 1;
+
     // Error codes.
     public static final int KM_ERROR_OK = 0;
     public static final int KM_ERROR_ROOT_OF_TRUST_ALREADY_SET = -1;
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index c8149d9..79a8489 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -71,7 +71,12 @@
  */
 public final class Choreographer {
     private static final String TAG = "Choreographer";
-    private static final boolean DEBUG = false;
+
+    // Prints debug messages about jank which was detected (low volume).
+    private static final boolean DEBUG_JANK = false;
+
+    // Prints debug messages about every frame and callback registered (high volume).
+    private static final boolean DEBUG_FRAMES = false;
 
     // The default amount of time in ms between animation frames.
     // When vsync is not enabled, we want to have some idea of how long we should
@@ -139,6 +144,7 @@
     private boolean mCallbacksRunning;
     private long mLastFrameTimeNanos;
     private long mFrameIntervalNanos;
+    private boolean mDebugPrintNextFrameTimeDelta;
 
     /**
      * Contains information about the current frame for jank-tracking,
@@ -166,13 +172,25 @@
     public static final int CALLBACK_ANIMATION = 1;
 
     /**
-     * Callback type: Traversal callback.  Handles layout and draw.  Runs last
+     * Callback type: Traversal callback.  Handles layout and draw.  Runs
      * after all other asynchronous messages have been handled.
      * @hide
      */
     public static final int CALLBACK_TRAVERSAL = 2;
 
-    private static final int CALLBACK_LAST = CALLBACK_TRAVERSAL;
+    /**
+     * Callback type: Commit callback.  Handles post-draw operations for the frame.
+     * Runs after traversal completes.  The {@link #getFrameTime() frame time} reported
+     * during this callback may be updated to reflect delays that occurred while
+     * traversals were in progress in case heavy layout operations caused some frames
+     * to be skipped.  The frame time reported during this callback provides a better
+     * estimate of the start time of the frame in which animations (and other updates
+     * to the view hierarchy state) actually took effect.
+     * @hide
+     */
+    public static final int CALLBACK_COMMIT = 3;
+
+    private static final int CALLBACK_LAST = CALLBACK_COMMIT;
 
     private Choreographer(Looper looper) {
         mLooper = looper;
@@ -332,7 +350,7 @@
 
     private void postCallbackDelayedInternal(int callbackType,
             Object action, Object token, long delayMillis) {
-        if (DEBUG) {
+        if (DEBUG_FRAMES) {
             Log.d(TAG, "PostCallback: type=" + callbackType
                     + ", action=" + action + ", token=" + token
                     + ", delayMillis=" + delayMillis);
@@ -376,7 +394,7 @@
     }
 
     private void removeCallbacksInternal(int callbackType, Object action, Object token) {
-        if (DEBUG) {
+        if (DEBUG_FRAMES) {
             Log.d(TAG, "RemoveCallbacks: type=" + callbackType
                     + ", action=" + action + ", token=" + token);
         }
@@ -492,7 +510,7 @@
         if (!mFrameScheduled) {
             mFrameScheduled = true;
             if (USE_VSYNC) {
-                if (DEBUG) {
+                if (DEBUG_FRAMES) {
                     Log.d(TAG, "Scheduling next frame on vsync.");
                 }
 
@@ -509,7 +527,7 @@
             } else {
                 final long nextFrameTime = Math.max(
                         mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
-                if (DEBUG) {
+                if (DEBUG_FRAMES) {
                     Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
                 }
                 Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
@@ -526,6 +544,12 @@
                 return; // no work to do
             }
 
+            if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
+                mDebugPrintNextFrameTimeDelta = false;
+                Log.d(TAG, "Frame time delta: "
+                        + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
+            }
+
             long intendedFrameTimeNanos = frameTimeNanos;
             startNanos = System.nanoTime();
             final long jitterNanos = startNanos - frameTimeNanos;
@@ -536,7 +560,7 @@
                             + "The application may be doing too much work on its main thread.");
                 }
                 final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
-                if (DEBUG) {
+                if (DEBUG_JANK) {
                     Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
                             + "which is more than the frame interval of "
                             + (mFrameIntervalNanos * 0.000001f) + " ms!  "
@@ -547,7 +571,7 @@
             }
 
             if (frameTimeNanos < mLastFrameTimeNanos) {
-                if (DEBUG) {
+                if (DEBUG_JANK) {
                     Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "
                             + "previously skipped frame.  Waiting for next vsync.");
                 }
@@ -569,7 +593,9 @@
         mFrameInfo.markPerformTraversalsStart();
         doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
 
-        if (DEBUG) {
+        doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
+
+        if (DEBUG_FRAMES) {
             final long endNanos = System.nanoTime();
             Log.d(TAG, "Frame " + frame + ": Finished, took "
                     + (endNanos - startNanos) * 0.000001f + " ms, latency "
@@ -583,16 +609,43 @@
             // We use "now" to determine when callbacks become due because it's possible
             // for earlier processing phases in a frame to post callbacks that should run
             // in a following phase, such as an input event that causes an animation to start.
-            final long now = SystemClock.uptimeMillis();
-            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now);
+            final long now = System.nanoTime();
+            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
+                    now / TimeUtils.NANOS_PER_MS);
             if (callbacks == null) {
                 return;
             }
             mCallbacksRunning = true;
+
+            // Update the frame time if necessary when committing the frame.
+            // We only update the frame time if we are more than 2 frames late reaching
+            // the commit phase.  This ensures that the frame time which is observed by the
+            // callbacks will always increase from one frame to the next and never repeat.
+            // We never want the next frame's starting frame time to end up being less than
+            // or equal to the previous frame's commit frame time.  Keep in mind that the
+            // next frame has most likely already been scheduled by now so we play it
+            // safe by ensuring the commit time is always at least one frame behind.
+            if (callbackType == Choreographer.CALLBACK_COMMIT) {
+                final long jitterNanos = now - frameTimeNanos;
+                if (jitterNanos >= 2 * mFrameIntervalNanos) {
+                    final long lastFrameOffset = jitterNanos % mFrameIntervalNanos
+                            + mFrameIntervalNanos;
+                    if (DEBUG_JANK) {
+                        Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f)
+                                + " ms which is more than twice the frame interval of "
+                                + (mFrameIntervalNanos * 0.000001f) + " ms!  "
+                                + "Setting frame time to " + (lastFrameOffset * 0.000001f)
+                                + " ms in the past.");
+                        mDebugPrintNextFrameTimeDelta = true;
+                    }
+                    frameTimeNanos = now - lastFrameOffset;
+                    mLastFrameTimeNanos = frameTimeNanos;
+                }
+            }
         }
         try {
             for (CallbackRecord c = callbacks; c != null; c = c.next) {
-                if (DEBUG) {
+                if (DEBUG_FRAMES) {
                     Log.d(TAG, "RunCallback: type=" + callbackType
                             + ", action=" + c.action + ", token=" + c.token
                             + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 16353e8..205d35e 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -1810,9 +1810,7 @@
         }
         if (mRefreshProgressRunnable != null) {
             removeCallbacks(mRefreshProgressRunnable);
-        }
-        if (mRefreshProgressRunnable != null && mRefreshIsPosted) {
-            removeCallbacks(mRefreshProgressRunnable);
+            mRefreshIsPosted = false;
         }
         if (mAccessibilityEventSender != null) {
             removeCallbacks(mAccessibilityEventSender);
diff --git a/core/java/android/widget/RadialTimePickerView.java b/core/java/android/widget/RadialTimePickerView.java
index 52e1728..9571109 100644
--- a/core/java/android/widget/RadialTimePickerView.java
+++ b/core/java/android/widget/RadialTimePickerView.java
@@ -79,10 +79,10 @@
     // Transparent alpha level
     private static final int ALPHA_TRANSPARENT = 0;
 
-    private static final int HOURS_IN_DAY = 24;
-    private static final int MINUTES_IN_HOUR = 60;
-    private static final int DEGREES_FOR_ONE_HOUR = 360 / HOURS_IN_DAY;
-    private static final int DEGREES_FOR_ONE_MINUTE = 360 / MINUTES_IN_HOUR;
+    private static final int HOURS_IN_CIRCLE = 12;
+    private static final int MINUTES_IN_CIRCLE = 60;
+    private static final int DEGREES_FOR_ONE_HOUR = 360 / HOURS_IN_CIRCLE;
+    private static final int DEGREES_FOR_ONE_MINUTE = 360 / MINUTES_IN_CIRCLE;
 
     private static final int[] HOURS_NUMBERS = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
     private static final int[] HOURS_NUMBERS_24 = {0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23};
@@ -536,7 +536,7 @@
     }
 
     private void setCurrentMinuteInternal(int minute, boolean callback) {
-        mSelectionDegrees[MINUTES] = (minute % MINUTES_IN_HOUR) * DEGREES_FOR_ONE_MINUTE;
+        mSelectionDegrees[MINUTES] = (minute % MINUTES_IN_CIRCLE) * DEGREES_FOR_ONE_MINUTE;
 
         invalidate();
 
@@ -1140,8 +1140,8 @@
 
                     // If the touched minute is closer to the current minute
                     // than it is to the snapped minute, return current.
-                    final int currentOffset = getCircularDiff(current, touched, MINUTES_IN_HOUR);
-                    final int snappedOffset = getCircularDiff(snapped, touched, MINUTES_IN_HOUR);
+                    final int currentOffset = getCircularDiff(current, touched, MINUTES_IN_CIRCLE);
+                    final int snappedOffset = getCircularDiff(snapped, touched, MINUTES_IN_CIRCLE);
                     final int minute;
                     if (currentOffset < snappedOffset) {
                         minute = current;
@@ -1181,7 +1181,7 @@
                 }
             } else {
                 final int current = getCurrentMinute();
-                for (int i = 0; i < MINUTES_IN_HOUR; i += MINUTE_INCREMENT) {
+                for (int i = 0; i < MINUTES_IN_CIRCLE; i += MINUTE_INCREMENT) {
                     virtualViewIds.add(makeId(TYPE_MINUTE, i));
 
                     // If the current minute falls between two increments,
@@ -1239,7 +1239,7 @@
                 if (value < current && nextValue > current) {
                     // The current value is between two snap values.
                     return makeId(type, current);
-                } else if (nextValue < MINUTES_IN_HOUR) {
+                } else if (nextValue < MINUTES_IN_CIRCLE) {
                     return makeId(type, nextValue);
                 }
             }
diff --git a/docs/html/about/dashboards/index.jd b/docs/html/about/dashboards/index.jd
index cfb65a5..52f086e 100644
--- a/docs/html/about/dashboards/index.jd
+++ b/docs/html/about/dashboards/index.jd
@@ -57,7 +57,7 @@
 </div>
 
 
-<p style="clear:both"><em>Data collected during a 7-day period ending on March 2, 2015.
+<p style="clear:both"><em>Data collected during a 7-day period ending on April 6, 2015.
 <br/>Any versions with less than 0.1% distribution are not shown.</em>
 </p>
 
@@ -88,7 +88,7 @@
 </div>
 
 
-<p style="clear:both"><em>Data collected during a 7-day period ending on March 2, 2015.
+<p style="clear:both"><em>Data collected during a 7-day period ending on April 6, 2015.
 
 <br/>Any screen configurations with less than 0.1% distribution are not shown.</em></p>
 
@@ -108,8 +108,7 @@
 
 
 <img alt="" style="float:right"
-src="//chart.googleapis.com/chart?chl=GL%202.0%7CGL%203.0&chf=bg%2Cs%2C00000000&chd=t%3A67.5%2C32.5&chco=c4df9b%2C6fad0c&cht=p&chs=400x250" />
-
+src="//chart.googleapis.com/chart?chl=GL%202.0%7CGL%203.0%7CGL%203.1&chf=bg%2Cs%2C00000000&chd=t%3A65.9%2C33.8%2C0.3&chco=c4df9b%2C6fad0c&cht=p&chs=400x250">
 
 <p>To declare which version of OpenGL ES your application requires, you should use the {@code
 android:glEsVersion} attribute of the <a
@@ -127,17 +126,21 @@
 </tr>
 <tr>
 <td>2.0</td>
-<td>67.5%</td>
+<td>65.9%</td>
 </tr>
 <tr>
 <td>3.0</td>
-<td>32.5%</td>
+<td>33.8%</td>
+</tr>
+<tr>
+<td>3.1</td>
+<td>0.3%</td>
 </tr>
 </table>
 
 
 
-<p style="clear:both"><em>Data collected during a 7-day period ending on March 2, 2015</em></p>
+<p style="clear:both"><em>Data collected during a 7-day period ending on April 6, 2015</em></p>
 
 
 
@@ -155,7 +158,7 @@
 var VERSION_DATA =
 [
   {
-    "chart": "//chart.googleapis.com/chart?chl=Froyo%7CGingerbread%7CIce%20Cream%20Sandwich%7CJelly%20Bean%7CKitKat%7CLollipop&chco=c4df9b%2C6fad0c&chd=t%3A0.4%2C6.9%2C5.9%2C42.6%2C40.9%2C3.3&chf=bg%2Cs%2C00000000&chs=500x250&cht=p",
+    "chart": "//chart.googleapis.com/chart?chl=Froyo%7CGingerbread%7CIce%20Cream%20Sandwich%7CJelly%20Bean%7CKitKat%7CLollipop&chf=bg%2Cs%2C00000000&chd=t%3A0.4%2C6.4%2C5.7%2C40.7%2C41.4%2C5.4&chco=c4df9b%2C6fad0c&chs=500x250&cht=p",
     "data": [
       {
         "api": 8,
@@ -165,37 +168,42 @@
       {
         "api": 10,
         "name": "Gingerbread",
-        "perc": "6.9"
+        "perc": "6.4"
       },
       {
         "api": 15,
         "name": "Ice Cream Sandwich",
-        "perc": "5.9"
+        "perc": "5.7"
       },
       {
         "api": 16,
         "name": "Jelly Bean",
-        "perc": "17.3"
+        "perc": "16.5"
       },
       {
         "api": 17,
         "name": "Jelly Bean",
-        "perc": "19.4"
+        "perc": "18.6"
       },
       {
         "api": 18,
         "name": "Jelly Bean",
-        "perc": "5.9"
+        "perc": "5.6"
       },
       {
         "api": 19,
         "name": "KitKat",
-        "perc": "40.9"
+        "perc": "41.4"
       },
       {
         "api": 21,
         "name": "Lollipop",
-        "perc": "3.3"
+        "perc": "5.0"
+      },
+      {
+        "api": 22,
+        "name": "Lollipop",
+        "perc": "0.4"
       }
     ]
   }
@@ -208,29 +216,29 @@
     "data": {
       "Large": {
         "hdpi": "0.6",
-        "ldpi": "0.5",
-        "mdpi": "5.1",
-        "tvdpi": "2.3",
+        "ldpi": "0.4",
+        "mdpi": "4.8",
+        "tvdpi": "2.2",
         "xhdpi": "0.6"
       },
       "Normal": {
-        "hdpi": "38.7",
-        "mdpi": "8.4",
+        "hdpi": "39.3",
+        "mdpi": "8.1",
         "tvdpi": "0.1",
-        "xhdpi": "18.9",
-        "xxhdpi": "15.8"
+        "xhdpi": "19.5",
+        "xxhdpi": "15.9"
       },
       "Small": {
-        "ldpi": "4.6"
+        "ldpi": "4.4"
       },
       "Xlarge": {
         "hdpi": "0.3",
-        "mdpi": "3.5",
+        "mdpi": "3.2",
         "xhdpi": "0.6"
       }
     },
-    "densitychart": "//chart.googleapis.com/chart?chl=ldpi%7Cmdpi%7Ctvdpi%7Chdpi%7Cxhdpi%7Cxxhdpi&chco=c4df9b%2C6fad0c&chd=t%3A5.1%2C17.0%2C2.4%2C39.6%2C20.1%2C15.8&chf=bg%2Cs%2C00000000&chs=400x250&cht=p",
-    "layoutchart": "//chart.googleapis.com/chart?chl=Xlarge%7CLarge%7CNormal%7CSmall&chco=c4df9b%2C6fad0c&chd=t%3A4.4%2C9.1%2C81.9%2C4.6&chf=bg%2Cs%2C00000000&chs=400x250&cht=p"
+    "densitychart": "//chart.googleapis.com/chart?chl=ldpi%7Cmdpi%7Ctvdpi%7Chdpi%7Cxhdpi%7Cxxhdpi&chf=bg%2Cs%2C00000000&chd=t%3A4.8%2C16.1%2C2.3%2C40.2%2C20.7%2C15.9&chco=c4df9b%2C6fad0c&chs=400x250&cht=p",
+    "layoutchart": "//chart.googleapis.com/chart?chl=Xlarge%7CLarge%7CNormal%7CSmall&chf=bg%2Cs%2C00000000&chd=t%3A4.1%2C8.6%2C82.9%2C4.4&chco=c4df9b%2C6fad0c&chs=400x250&cht=p"
   }
 ];
 
@@ -312,6 +320,11 @@
     "api":21,
     "link":"<a href='/about/versions/android-5.0.html'>5.0</a>",
     "codename":"Lollipop"
+  },
+  {
+    "api":22,
+    "link":"<a href='/about/versions/android-5.1.html'>5.1</a>",
+    "codename":"Lollipop"
   }
 ];
 
diff --git a/docs/html/google/play-services/setup.jd b/docs/html/google/play-services/setup.jd
index 3f71d04..e75235e 100644
--- a/docs/html/google/play-services/setup.jd
+++ b/docs/html/google/play-services/setup.jd
@@ -82,14 +82,6 @@
 <img src="{@docRoot}images/tools/sync-project.png" style="vertical-align:bottom;margin:0;height:19px" />
 in the toolbar.
   </li>
-  <li>Open your app's manifest file and add the following tag as a child of the <a
-href="{@docRoot}guide/topics/manifest/application-element.html">{@code &lt;application>}</a>
-element:
-<pre>
-&lt;meta-data android:name="com.google.android.gms.version"
-           android:value="&#64;integer/google_play_services_version" />
-</pre>
-  </li>
 </ol>
 
 <p>You can now begin developing features with the
diff --git a/docs/html/google/play/billing/billing_integrate.jd b/docs/html/google/play/billing/billing_integrate.jd
index e3cacf9..eb58af4 100644
--- a/docs/html/google/play/billing/billing_integrate.jd
+++ b/docs/html/google/play/billing/billing_integrate.jd
@@ -34,7 +34,7 @@
   <h2>See also</h2>
   <ol>
     <li><a href="{@docRoot}training/in-app-billing/index.html">Selling In-app Products</a></li>
-  </ol>  
+  </ol>
 </div>
 </div>
 
@@ -42,26 +42,26 @@
 
 <p class="note"><strong>Note:</strong> To see a complete implementation and learn how to test your application, see the <a href="{@docRoot}training/in-app-billing/index.html">Selling In-app Products</a> training class. The training class provides a complete sample In-app Billing application, including convenience classes to handle key tasks related to setting up your connection, sending billing requests and processing responses from Google Play, and managing background threading so that you can make In-app Billing calls from your main activity.</p>
 
-<p>Before you start, be sure that you read the <a href="{@docRoot}google/play/billing/billing_overview.html">In-app Billing Overview</a> to familiarize yourself with 
+<p>Before you start, be sure that you read the <a href="{@docRoot}google/play/billing/billing_overview.html">In-app Billing Overview</a> to familiarize yourself with
 concepts that will make it easier for you to implement In-app Billing.</p>
 
-<p>To implement In-app Billing in your application, you need to do the 
+<p>To implement In-app Billing in your application, you need to do the
 following:</p>
 <ol>
   <li>Add the In-app Billing library to your project.</li>
   <li>Update your {@code AndroidManifest.xml} file.</li>
-  <li>Create a {@code ServiceConnection} and bind it to 
+  <li>Create a {@code ServiceConnection} and bind it to
 {@code IInAppBillingService}.</li>
-  <li>Send In-app Billing requests from your application to 
+  <li>Send In-app Billing requests from your application to
 {@code IInAppBillingService}.</li>
   <li>Handle In-app Billing responses from Google Play.</li>
 </ol>
 
 <h2 id="billing-add-aidl">Adding the AIDL file to your project</h2>
 
-<p>{@code IInAppBillingService.aidl} is an Android Interface Definition 
-Language (AIDL) file that defines the interface to the In-app Billing Version 
-3 service. You will use this interface to make billing requests by invoking IPC 
+<p>{@code IInAppBillingService.aidl} is an Android Interface Definition
+Language (AIDL) file that defines the interface to the In-app Billing Version
+3 service. You will use this interface to make billing requests by invoking IPC
 method calls.</p>
 <p>To get the AIDL file:</p>
 <ol>
@@ -76,28 +76,28 @@
 <ol>
 <li>Copy the {@code IInAppBillingService.aidl} file to your Android project.
   <ul>
-  <li>If you are using Eclipse: 
+  <li>If you are using Eclipse:
      <ol type="a">
-        <li>If you are starting from an existing Android project, open the project 
-in Eclipse. If you are creating a new Android project from scratch, click 
-<strong>File</strong> &gt; <strong>New</strong> &gt; <strong>Android Application 
-Project</strong>, then follow the instructions in the <strong>New Android 
+        <li>If you are starting from an existing Android project, open the project
+in Eclipse. If you are creating a new Android project from scratch, click
+<strong>File</strong> &gt; <strong>New</strong> &gt; <strong>Android Application
+Project</strong>, then follow the instructions in the <strong>New Android
 Application</strong> wizard to create a new project in your workspace.</li>
-	<li>In the {@code /src} directory, click <strong>File</strong> &gt; 
+	<li>In the {@code /src} directory, click <strong>File</strong> &gt;
 <strong>New</strong> &gt; <strong>Package</strong>, then create a package named {@code com.android.vending.billing}.</li>
-	<li>Copy the {@code IInAppBillingService.aidl} file from {@code &lt;sdk&gt;/extras/google/play_billing/} and paste it into the {@code src/com.android.vending.billing/} 
+	<li>Copy the {@code IInAppBillingService.aidl} file from {@code &lt;sdk&gt;/extras/google/play_billing/} and paste it into the {@code src/com.android.vending.billing/}
 folder in your workspace.</li>
      </ol>
   </li>
-  <li>If you are developing in a non-Eclipse environment: Create the following 
-directory {@code /src/com/android/vending/billing} and copy the 
-{@code IInAppBillingService.aidl} file into this directory. Put the AIDL file 
+  <li>If you are developing in a non-Eclipse environment: Create the following
+directory {@code /src/com/android/vending/billing} and copy the
+{@code IInAppBillingService.aidl} file into this directory. Put the AIDL file
 into your project and use the Ant tool to build your project so that the
 <code>IInAppBillingService.java</code> file gets generated.</li>
   </ul>
 </li>
-<li>Build your application. You should see a generated file named 
-{@code IInAppBillingService.java} in the {@code /gen} directory of your 
+<li>Build your application. You should see a generated file named
+{@code IInAppBillingService.java} in the {@code /gen} directory of your
 project.</li>
 </ol>
 
@@ -135,7 +135,7 @@
    }
 
    &#64;Override
-   public void onServiceConnected(ComponentName name, 
+   public void onServiceConnected(ComponentName name,
       IBinder service) {
        mService = IInAppBillingService.Stub.asInterface(service);
    }
@@ -162,7 +162,7 @@
     super.onDestroy();
     if (mService != null) {
         unbindService(mServiceConn);
-    }	
+    }
 }
 </pre>
 
@@ -185,13 +185,13 @@
 </pre>
 <p>To retrieve this information from Google Play, call the {@code getSkuDetails} method on the In-app Billing Version 3 API, and pass the method the In-app Billing API version (“3”), the package name of your calling app, the purchase type (“inapp”), and the {@link android.os.Bundle} that you created.</p>
 <pre>
-Bundle skuDetails = mService.getSkuDetails(3, 
+Bundle skuDetails = mService.getSkuDetails(3,
    getPackageName(), "inapp", querySkus);
 </pre>
 <p>If the request is successful, the returned {@link android.os.Bundle}has a response code of {@code BILLING_RESPONSE_RESULT_OK} (0).</p>
 <p class="note"><strong>Warning:</strong> Do not call the {@code getSkuDetails} method on the main thread. Calling this method triggers a network request which could block your main thread.  Instead, create a separate thread and call the {@code getSkuDetails} method from inside that thread.</p>
 
-<p>To see all the possible response codes from Google Play, see <a href="{@docRoot}google/play/billing/billing_reference.html#billing-codes">In-app Billing Reference</a>.</p>  
+<p>To see all the possible response codes from Google Play, see <a href="{@docRoot}google/play/billing/billing_reference.html#billing-codes">In-app Billing Reference</a>.</p>
 
 <p>The query results are stored in a String ArrayList with key {@code DETAILS_LIST}.  The purchase information is stored in the String in JSON format. To see the types of product detail information that are returned, see <a href="{@docRoot}google/play/billing/billing_reference.html#getSkuDetails">In-app Billing Reference</a>.</p>
 
@@ -201,7 +201,7 @@
 if (response == 0) {
    ArrayList&lt;String&gt; responseList
       = skuDetails.getStringArrayList("DETAILS_LIST");
-   
+
    for (String thisResponse : responseList) {
       JSONObject object = new JSONObject(thisResponse);
       String sku = object.getString("productId");
@@ -232,12 +232,12 @@
    1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0),
    Integer.valueOf(0));
 </pre>
-<p>Google Play sends a response to your {@link android.app.PendingIntent} to the {@link android.app.Activity#onActivityResult onActivityResult} method of your application. The {@link android.app.Activity#onActivityResult onActivityResult} method will have a result code of {@code Activity.RESULT_OK} (1) or {@code Activity.RESULT_CANCELED} (0). To see the types of order information that is returned in the response {@link android.content.Intent}, see <a href="{@docRoot}google/play/billing/billing_reference.html#getBuyIntent">In-app Billing Reference</a>.</p> 
+<p>Google Play sends a response to your {@link android.app.PendingIntent} to the {@link android.app.Activity#onActivityResult onActivityResult} method of your application. The {@link android.app.Activity#onActivityResult onActivityResult} method will have a result code of {@code Activity.RESULT_OK} (1) or {@code Activity.RESULT_CANCELED} (0). To see the types of order information that is returned in the response {@link android.content.Intent}, see <a href="{@docRoot}google/play/billing/billing_reference.html#getBuyIntent">In-app Billing Reference</a>.</p>
 
 <p>The purchase data for the order is a String in JSON format that is mapped to the {@code INAPP_PURCHASE_DATA} key in the response {@link android.content.Intent}, for example:
 <pre>
-'{ 
-   "orderId":"12999763169054705758.1371079406387615", 
+'{
+   "orderId":"12999763169054705758.1371079406387615",
    "packageName":"com.example.app",
    "productId":"exampleSku",
    "purchaseTime":1345678900000,
@@ -259,17 +259,17 @@
 <p>Continuing from the previous example, you get the response code, purchase data, and signature from the response {@link android.content.Intent}.</p>
 <pre>
 &#64;Override
-protected void onActivityResult(int requestCode, int resultCode, Intent data) {	
-   if (requestCode == 1001) {    	
+protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+   if (requestCode == 1001) {
       int responseCode = data.getIntExtra("RESPONSE_CODE", 0);
       String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
       String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");
-        
+
       if (resultCode == RESULT_OK) {
          try {
             JSONObject jo = new JSONObject(purchaseData);
             String sku = jo.getString("productId");
-            alert("You have bought the " + sku + ". Excellent choice, 
+            alert("You have bought the " + sku + ". Excellent choice,
                adventurer!");
           }
           catch (JSONException e) {
@@ -298,45 +298,45 @@
    ArrayList&lt;String&gt;  purchaseDataList =
       ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
    ArrayList&lt;String&gt;  signatureList =
-      ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE");
-   String continuationToken = 
+      ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE_LIST");
+   String continuationToken =
       ownedItems.getString("INAPP_CONTINUATION_TOKEN");
-   
+
    for (int i = 0; i < purchaseDataList.size(); ++i) {
       String purchaseData = purchaseDataList.get(i);
       String signature = signatureList.get(i);
       String sku = ownedSkus.get(i);
-  
+
       // do something with this purchase information
       // e.g. display the updated list of products owned by user
-   } 
+   }
 
-   // if continuationToken != null, call getPurchases again 
+   // if continuationToken != null, call getPurchases again
    // and pass in the token to retrieve more items
 }
 
 </pre>
 
 <h3 id="Consume">Consuming a Purchase</h3>
-<p>You can use the In-app Billing Version 3 API to track the ownership of 
-purchased in-app products in Google Play. Once an in-app product is purchased, 
-it is considered to be "owned" and cannot be purchased from Google Play. You 
-must send a consumption request for the in-app product before Google Play makes 
+<p>You can use the In-app Billing Version 3 API to track the ownership of
+purchased in-app products in Google Play. Once an in-app product is purchased,
+it is considered to be "owned" and cannot be purchased from Google Play. You
+must send a consumption request for the in-app product before Google Play makes
 it available for purchase again.</p>
-<p class="caution"><strong>Important</strong>: Managed in-app products are 
+<p class="caution"><strong>Important</strong>: Managed in-app products are
 consumable, but subscriptions are not.</p>
-<p>How you use the consumption mechanism in your app is up to you. Typically, 
-you would implement consumption for in-app products with temporary benefits that 
-users may want to purchase multiple times (for example, in-game currency or 
-equipment). You would typically not want to implement consumption for in-app 
-products that are purchased once and provide a permanent effect (for example, 
+<p>How you use the consumption mechanism in your app is up to you. Typically,
+you would implement consumption for in-app products with temporary benefits that
+users may want to purchase multiple times (for example, in-game currency or
+equipment). You would typically not want to implement consumption for in-app
+products that are purchased once and provide a permanent effect (for example,
 a premium upgrade).</p>
-<p>To record a purchase consumption, send the {@code consumePurchase} method to 
-the In-app Billing service and pass in the {@code purchaseToken} String value 
-that identifies the purchase to be removed. The {@code purchaseToken} is part 
-of the data returned in the {@code INAPP_PURCHASE_DATA} String by the Google 
-Play service following a successful purchase request. In this example, you are 
-recording the consumption of a product that is identified with the 
+<p>To record a purchase consumption, send the {@code consumePurchase} method to
+the In-app Billing service and pass in the {@code purchaseToken} String value
+that identifies the purchase to be removed. The {@code purchaseToken} is part
+of the data returned in the {@code INAPP_PURCHASE_DATA} String by the Google
+Play service following a successful purchase request. In this example, you are
+recording the consumption of a product that is identified with the
 {@code purchaseToken} in the {@code token} variable.</p>
 <pre>
 int response = mService.consumePurchase(3, getPackageName(), token);
@@ -346,10 +346,10 @@
 <p class="note"><strong>Security Recommendation:</strong> You must send a consumption request before provisioning the benefit of the consumable in-app purchase to the user. Make sure that you have received a successful consumption response from Google Play before you provision the item.</p>
 
 <h3 id="Subs">Implementing Subscriptions</h3>
-<p>Launching a purchase flow for a subscription is similar to launching the 
-purchase flow for a product, with the exception that the product type must be set 
-to "subs". The purchase result is delivered to your Activity's 
-{@link android.app.Activity#onActivityResult onActivityResult} method, exactly 
+<p>Launching a purchase flow for a subscription is similar to launching the
+purchase flow for a product, with the exception that the product type must be set
+to "subs". The purchase result is delivered to your Activity's
+{@link android.app.Activity#onActivityResult onActivityResult} method, exactly
 as in the case of in-app products.</p>
 <pre>
 Bundle bundle = mService.getBuyIntent(3, "com.example.myapp",
@@ -363,39 +363,39 @@
        Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0));
 }
 </pre>
-<p>To query for active subscriptions, use the {@code getPurchases} method, again 
+<p>To query for active subscriptions, use the {@code getPurchases} method, again
 with the product type parameter set to "subs".</p>
 <pre>
 Bundle activeSubs = mService.getPurchases(3, "com.example.myapp",
                    "subs", continueToken);
 </pre>
-<p>The call returns a {@code Bundle} with all the active subscriptions owned by 
-the user. Once a subscription expires without renewal, it will no longer appear 
+<p>The call returns a {@code Bundle} with all the active subscriptions owned by
+the user. Once a subscription expires without renewal, it will no longer appear
 in the returned {@code Bundle}.</p>
 
 <h2 id="billing-security">Securing Your Application</h2>
 
-<p>To help ensure the integrity of the transaction information that is sent to 
-your application, Google Play signs the JSON string that contains the response 
-data for a purchase order. Google Play uses the private key that is associated 
-with your application in the Developer Console to create this signature. The 
+<p>To help ensure the integrity of the transaction information that is sent to
+your application, Google Play signs the JSON string that contains the response
+data for a purchase order. Google Play uses the private key that is associated
+with your application in the Developer Console to create this signature. The
 Developer Console generates an RSA key pair for each application.<p>
 
-<p class="note"><strong>Note:</strong>To find the public key portion of this key 
-pair, open your application's details in the Developer Console, then click on 
-<strong>Services & APIs</strong>, and look at the field titled 
+<p class="note"><strong>Note:</strong>To find the public key portion of this key
+pair, open your application's details in the Developer Console, then click on
+<strong>Services & APIs</strong>, and look at the field titled
 <strong>Your License Key for This Application</strong>.</p>
 
-<p>The Base64-encoded RSA public key generated by Google Play is in binary 
-encoded, X.509 subjectPublicKeyInfo DER SEQUENCE format. It is the same public 
+<p>The Base64-encoded RSA public key generated by Google Play is in binary
+encoded, X.509 subjectPublicKeyInfo DER SEQUENCE format. It is the same public
 key that is used with Google Play licensing.</p>
 
-<p>When your application receives this signed response you can 
-use the public key portion of your RSA key pair to verify the signature. 
-By performing signature verification you can detect responses that have 
-been tampered with or that have been spoofed. You can perform this signature 
-verification step in your application; however, if your application connects 
-to a secure remote server then we recommend that you perform the signature 
+<p>When your application receives this signed response you can
+use the public key portion of your RSA key pair to verify the signature.
+By performing signature verification you can detect responses that have
+been tampered with or that have been spoofed. You can perform this signature
+verification step in your application; however, if your application connects
+to a secure remote server then we recommend that you perform the signature
 verification on that server.</p>
 
 <p>For more information about best practices for security and design, see <a
diff --git a/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/AndroidKeyStore.java
index 0bd1dbd..ed690de 100644
--- a/keystore/java/android/security/AndroidKeyStore.java
+++ b/keystore/java/android/security/AndroidKeyStore.java
@@ -466,74 +466,65 @@
             throw new KeyStoreException("Unsupported secret key algorithm: " + keyAlgorithmString);
         }
 
-        if ((params.getAlgorithm() != null) && (params.getAlgorithm() != keyAlgorithm)) {
-            throw new KeyStoreException("Key algorithm mismatch. Key: " + keyAlgorithmString
-                    + ", parameter spec: "
-                    + KeyStoreKeyConstraints.Algorithm.toString(params.getAlgorithm()));
-        }
-
         KeymasterArguments args = new KeymasterArguments();
         args.addInt(KeymasterDefs.KM_TAG_ALGORITHM,
                 KeyStoreKeyConstraints.Algorithm.toKeymaster(keyAlgorithm));
 
-        if (digest != null) {
-            // Digest available from JCA key algorithm
-            if (params.getDigest() != null) {
-                // Digest also specified in parameters -- check that these two match
-                if (digest != params.getDigest()) {
-                    throw new KeyStoreException("Key digest mismatch. Key: " + keyAlgorithmString
+        @KeyStoreKeyConstraints.DigestEnum int digests;
+        if (params.isDigestsSpecified()) {
+            // Digest(s) specified in parameters
+            if (digest != null) {
+                // Digest also specified in the JCA key algorithm name.
+                if ((params.getDigests() & digest) != digest) {
+                    throw new KeyStoreException("Key digest mismatch"
+                            + ". Key: " + keyAlgorithmString
                             + ", parameter spec: "
-                            + KeyStoreKeyConstraints.Digest.toString(params.getDigest()));
+                            + KeyStoreKeyConstraints.Digest.allToString(params.getDigests()));
                 }
             }
+            digests = params.getDigests();
         } else {
-            // Digest not available from JCA key algorithm
-            digest = params.getDigest();
+            // No digest specified in parameters
+            if (digest != null) {
+                // Digest specified in the JCA key algorithm name.
+                digests = digest;
+            } else {
+                digests = 0;
+            }
         }
-        if (digest != null) {
-            args.addInt(KeymasterDefs.KM_TAG_DIGEST,
-                    KeyStoreKeyConstraints.Digest.toKeymaster(digest));
+        for (int keymasterDigest : KeyStoreKeyConstraints.Digest.allToKeymaster(digests)) {
+            args.addInt(KeymasterDefs.KM_TAG_DIGEST, keymasterDigest);
+        }
+        if (digests != 0) {
+            // TODO: Remove MAC length constraint once Keymaster API no longer requires it.
+            // This code will blow up if mode than one digest is specified.
             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);
             }
         }
         if (keyAlgorithm == KeyStoreKeyConstraints.Algorithm.HMAC) {
-            if (digest == null) {
-                throw new IllegalStateException("Digest algorithm must be specified for key"
-                        + " algorithm " + keyAlgorithmString);
+            if (digests == 0) {
+                throw new KeyStoreException("At least one digest algorithm must be specified"
+                        + " for key algorithm " + keyAlgorithmString);
             }
         }
 
-        @KeyStoreKeyConstraints.PurposeEnum int purposes = (params.getPurposes() != null)
-                ? params.getPurposes()
-                : (KeyStoreKeyConstraints.Purpose.ENCRYPT
-                        | KeyStoreKeyConstraints.Purpose.DECRYPT
-                        | KeyStoreKeyConstraints.Purpose.SIGN
-                        | KeyStoreKeyConstraints.Purpose.VERIFY);
-        for (int keymasterPurpose :
-            KeyStoreKeyConstraints.Purpose.allToKeymaster(purposes)) {
+        int purposes = params.getPurposes();
+        for (int keymasterPurpose : KeyStoreKeyConstraints.Purpose.allToKeymaster(purposes)) {
             args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose);
         }
-        if (params.getBlockMode() != null) {
-            args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE,
-                    KeyStoreKeyConstraints.BlockMode.toKeymaster(params.getBlockMode()));
+        for (int keymasterBlockMode :
+            KeyStoreKeyConstraints.BlockMode.allToKeymaster(params.getBlockModes())) {
+            args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockMode);
         }
-        if (params.getPadding() != null) {
-            args.addInt(KeymasterDefs.KM_TAG_PADDING,
-                    KeyStoreKeyConstraints.Padding.toKeymaster(params.getPadding()));
+        for (int keymasterPadding :
+            KeyStoreKeyConstraints.Padding.allToKeymaster(params.getPaddings())) {
+            args.addInt(KeymasterDefs.KM_TAG_PADDING, keymasterPadding);
         }
-        if (params.getMaxUsesPerBoot() != null) {
-            args.addInt(KeymasterDefs.KM_TAG_MAX_USES_PER_BOOT, params.getMaxUsesPerBoot());
-        }
-        if (params.getMinSecondsBetweenOperations() != null) {
-            args.addInt(KeymasterDefs.KM_TAG_MIN_SECONDS_BETWEEN_OPS,
-                    params.getMinSecondsBetweenOperations());
-        }
-        if (params.getUserAuthenticators().isEmpty()) {
+        if (params.getUserAuthenticators() == 0) {
             args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
         } else {
             args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE,
@@ -544,7 +535,7 @@
             // TODO: Add the invalidate on fingerprint enrolled constraint once Keymaster supports
             // that.
         }
-        if (params.getUserAuthenticationValidityDurationSeconds() != null) {
+        if (params.getUserAuthenticationValidityDurationSeconds() != -1) {
             args.addInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT,
                     params.getUserAuthenticationValidityDurationSeconds());
         }
diff --git a/keystore/java/android/security/KeyGeneratorSpec.java b/keystore/java/android/security/KeyGeneratorSpec.java
index 7058383..0e490cd 100644
--- a/keystore/java/android/security/KeyGeneratorSpec.java
+++ b/keystore/java/android/security/KeyGeneratorSpec.java
@@ -19,12 +19,8 @@
 import android.content.Context;
 import android.text.TextUtils;
 
-import java.security.cert.Certificate;
 import java.security.spec.AlgorithmParameterSpec;
-import java.util.Collections;
 import java.util.Date;
-import java.util.HashSet;
-import java.util.Set;
 
 import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
@@ -33,13 +29,13 @@
  * {@link AlgorithmParameterSpec} for initializing a {@code KeyGenerator} that works with
  * <a href="{@docRoot}training/articles/keystore.html">Android KeyStore facility</a>.
  *
- * <p>The Android KeyStore facility is accessed through a {@link KeyGenerator} API
- * using the {@code AndroidKeyStore} provider. The {@code context} passed in may be used to pop up
- * some UI to ask the user to unlock or initialize the Android KeyStore facility.
+ * <p>The Android KeyStore facility is accessed through a {@link KeyGenerator} API using the
+ * {@code AndroidKeyStore} provider. The {@code context} passed in may be used to pop up some UI to
+ * ask the user to unlock or initialize the Android KeyStore facility.
  *
  * <p>After generation, the {@code keyStoreAlias} is used with the
  * {@link java.security.KeyStore#getEntry(String, java.security.KeyStore.ProtectionParameter)}
- * interface to retrieve the {@link SecretKey} and its associated {@link Certificate} chain.
+ * interface to retrieve the {@link SecretKey}.
  *
  * @hide
  */
@@ -52,13 +48,11 @@
     private final Date mKeyValidityStart;
     private final Date mKeyValidityForOriginationEnd;
     private final Date mKeyValidityForConsumptionEnd;
-    private final @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes;
-    private final @KeyStoreKeyConstraints.PaddingEnum Integer mPadding;
-    private final @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode;
-    private final Integer mMinSecondsBetweenOperations;
-    private final Integer mMaxUsesPerBoot;
-    private final Set<Integer> mUserAuthenticators;
-    private final Integer mUserAuthenticationValidityDurationSeconds;
+    private final @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
+    private final @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
+    private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+    private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
+    private final int mUserAuthenticationValidityDurationSeconds;
     private final boolean mInvalidatedOnNewFingerprintEnrolled;
 
     private KeyGeneratorSpec(
@@ -69,20 +63,18 @@
             Date keyValidityStart,
             Date keyValidityForOriginationEnd,
             Date keyValidityForConsumptionEnd,
-            @KeyStoreKeyConstraints.PurposeEnum Integer purposes,
-            @KeyStoreKeyConstraints.PaddingEnum Integer padding,
-            @KeyStoreKeyConstraints.BlockModeEnum Integer blockMode,
-            Integer minSecondsBetweenOperations,
-            Integer maxUsesPerBoot,
-            Set<Integer> userAuthenticators,
-            Integer userAuthenticationValidityDurationSeconds,
+            @KeyStoreKeyConstraints.PurposeEnum int purposes,
+            @KeyStoreKeyConstraints.PaddingEnum int paddings,
+            @KeyStoreKeyConstraints.BlockModeEnum int blockModes,
+            @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators,
+            int userAuthenticationValidityDurationSeconds,
             boolean invalidatedOnNewFingerprintEnrolled) {
         if (context == null) {
             throw new IllegalArgumentException("context == null");
         } else if (TextUtils.isEmpty(keyStoreAlias)) {
             throw new IllegalArgumentException("keyStoreAlias must not be empty");
-        } else if ((userAuthenticationValidityDurationSeconds != null)
-                && (userAuthenticationValidityDurationSeconds < 0)) {
+        } else if ((userAuthenticationValidityDurationSeconds < 0)
+                && (userAuthenticationValidityDurationSeconds != -1)) {
             throw new IllegalArgumentException(
                     "userAuthenticationValidityDurationSeconds must not be negative");
         }
@@ -95,13 +87,9 @@
         mKeyValidityForOriginationEnd = keyValidityForOriginationEnd;
         mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd;
         mPurposes = purposes;
-        mPadding = padding;
-        mBlockMode = blockMode;
-        mMinSecondsBetweenOperations = minSecondsBetweenOperations;
-        mMaxUsesPerBoot = maxUsesPerBoot;
-        mUserAuthenticators = (userAuthenticators != null)
-                ? new HashSet<Integer>(userAuthenticators)
-                : Collections.<Integer>emptySet();
+        mPaddings = paddings;
+        mBlockModes = blockModes;
+        mUserAuthenticators = userAuthenticators;
         mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
         mInvalidatedOnNewFingerprintEnrolled = invalidatedOnNewFingerprintEnrolled;
     }
@@ -148,8 +136,6 @@
      * Gets the time instant after which the key is no longer valid for decryption and verification.
      *
      * @return instant or {@code null} if not restricted.
-     *
-     * @hide
      */
     public Date getKeyValidityForConsumptionEnd() {
         return mKeyValidityForConsumptionEnd;
@@ -166,78 +152,43 @@
 
     /**
      * Gets the set of purposes for which the key can be used.
-     *
-     * @return set of purposes or {@code null} if the key can be used for any purpose.
      */
-    public @KeyStoreKeyConstraints.PurposeEnum Integer getPurposes() {
+    public @KeyStoreKeyConstraints.PurposeEnum int getPurposes() {
         return mPurposes;
     }
 
     /**
-     * Gets the padding scheme to which the key is restricted.
-     *
-     * @return padding scheme or {@code null} if the padding scheme is not restricted.
+     * Gets the set of padding schemes to which the key is restricted.
      */
-    public @KeyStoreKeyConstraints.PaddingEnum Integer getPadding() {
-        return mPadding;
+    public @KeyStoreKeyConstraints.PaddingEnum int getPaddings() {
+        return mPaddings;
     }
 
     /**
-     * Gets the block mode to which the key is restricted when used for encryption or decryption.
-     *
-     * @return block more or {@code null} if block mode is not restricted.
-     *
-     * @hide
+     * Gets the set of block modes to which the key is restricted.
      */
-    public @KeyStoreKeyConstraints.BlockModeEnum Integer getBlockMode() {
-        return mBlockMode;
+    public @KeyStoreKeyConstraints.BlockModeEnum int getBlockModes() {
+        return mBlockModes;
     }
 
     /**
-     * Gets the minimum number of seconds that must expire since the most recent use of the key
-     * before it can be used again.
+     * Gets the set of user authenticators which protect access to this key. The key can only be
+     * used iff the user has authenticated to at least one of these user authenticators.
      *
-     * @return number of seconds or {@code null} if there is no restriction on how frequently a key
-     *         can be used.
-     *
-     * @hide
+     * @return user authenticators or {@code 0} if the key can be used without user authentication.
      */
-    public Integer getMinSecondsBetweenOperations() {
-        return mMinSecondsBetweenOperations;
-    }
-
-    /**
-     * Gets the number of times the key can be used without rebooting the device.
-     *
-     * @return maximum number of times or {@code null} if there is no restriction.
-     * @hide
-     */
-    public Integer getMaxUsesPerBoot() {
-        return mMaxUsesPerBoot;
-    }
-
-    /**
-     * Gets the user authenticators which protect access to this key. The key can only be used iff
-     * the user has authenticated to at least one of these user authenticators.
-     *
-     * @return user authenticators or empty set if the key can be used without user authentication.
-     *
-     * @hide
-     */
-    public Set<Integer> getUserAuthenticators() {
-        return new HashSet<Integer>(mUserAuthenticators);
+    public @KeyStoreKeyConstraints.UserAuthenticatorEnum int getUserAuthenticators() {
+        return mUserAuthenticators;
     }
 
     /**
      * Gets the duration of time (seconds) for which this key can be used after the user
      * successfully authenticates to one of the associated user authenticators.
      *
-     * @return duration in seconds or {@code null} if not restricted. {@code 0} means authentication
+     * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication
      *         is required for every use of the key.
-     *
-     * @hide
      */
-    public Integer getUserAuthenticationValidityDurationSeconds() {
+    public int getUserAuthenticationValidityDurationSeconds() {
         return mUserAuthenticationValidityDurationSeconds;
     }
 
@@ -269,13 +220,11 @@
         private Date mKeyValidityStart;
         private Date mKeyValidityForOriginationEnd;
         private Date mKeyValidityForConsumptionEnd;
-        private @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes;
-        private @KeyStoreKeyConstraints.PaddingEnum Integer mPadding;
-        private @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode;
-        private Integer mMinSecondsBetweenOperations;
-        private Integer mMaxUsesPerBoot;
-        private Set<Integer> mUserAuthenticators;
-        private Integer mUserAuthenticationValidityDurationSeconds;
+        private @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
+        private @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
+        private @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+        private @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
+        private int mUserAuthenticationValidityDurationSeconds = -1;
         private boolean mInvalidatedOnNewFingerprintEnrolled;
 
         /**
@@ -335,8 +284,6 @@
          * <b>By default, the key is valid at any instant.
          *
          * @see #setKeyValidityEnd(Date)
-         *
-         * @hide
          */
         public Builder setKeyValidityStart(Date startDate) {
             mKeyValidityStart = startDate;
@@ -351,8 +298,6 @@
          * @see #setKeyValidityStart(Date)
          * @see #setKeyValidityForConsumptionEnd(Date)
          * @see #setKeyValidityForOriginationEnd(Date)
-         *
-         * @hide
          */
         public Builder setKeyValidityEnd(Date endDate) {
             setKeyValidityForOriginationEnd(endDate);
@@ -366,8 +311,6 @@
          * <b>By default, the key is valid at any instant.
          *
          * @see #setKeyValidityForConsumptionEnd(Date)
-         *
-         * @hide
          */
         public Builder setKeyValidityForOriginationEnd(Date endDate) {
             mKeyValidityForOriginationEnd = endDate;
@@ -381,8 +324,6 @@
          * <b>By default, the key is valid at any instant.
          *
          * @see #setKeyValidityForOriginationEnd(Date)
-         *
-         * @hide
          */
         public Builder setKeyValidityForConsumptionEnd(Date endDate) {
             mKeyValidityForConsumptionEnd = endDate;
@@ -390,11 +331,9 @@
         }
 
         /**
-         * Restricts the purposes for which the key can be used to the provided set of purposes.
+         * Restricts the key to being used only for the provided set of purposes.
          *
-         * <p>By default, the key can be used for encryption, decryption, signing, and verification.
-         *
-         * @hide
+         * <p>This restriction must be specified. There is no default.
          */
         public Builder setPurposes(@KeyStoreKeyConstraints.PurposeEnum int purposes) {
             mPurposes = purposes;
@@ -402,53 +341,24 @@
         }
 
         /**
-         * Restricts the key to being used only with the provided padding scheme. Attempts to use
+         * Restricts the key to being used only with the provided padding schemes. Attempts to use
          * the key with any other padding will be rejected.
          *
          * <p>This restriction must be specified for keys which are used for encryption/decryption.
-         *
-         * @hide
          */
-        public Builder setPadding(@KeyStoreKeyConstraints.PaddingEnum int padding) {
-            mPadding = padding;
+        public Builder setPaddings(@KeyStoreKeyConstraints.PaddingEnum int paddings) {
+            mPaddings = paddings;
             return this;
         }
 
         /**
-         * Restricts the key to being used only with the provided block mode when encrypting or
-         * decrypting. Attempts to use the key with any other block modes will be rejected.
+         * Restricts the key to being used only with the provided block modes. Attempts to use the
+         * key with any other block modes will be rejected.
          *
          * <p>This restriction must be specified for keys which are used for encryption/decryption.
-         *
-         * @hide
          */
-        public Builder setBlockMode(@KeyStoreKeyConstraints.BlockModeEnum int blockMode) {
-            mBlockMode = blockMode;
-            return this;
-        }
-
-        /**
-         * Sets the minimum number of seconds that must expire since the most recent use of the key
-         * before it can be used again.
-         *
-         * <p>By default, there is no restriction on how frequently a key can be used.
-         *
-         * @hide
-         */
-        public Builder setMinSecondsBetweenOperations(int seconds) {
-            mMinSecondsBetweenOperations = seconds;
-            return this;
-        }
-
-        /**
-         * Sets the maximum number of times a key can be used without rebooting the device.
-         *
-         * <p>By default, the key can be used for an unlimited number of times.
-         *
-         * @hide
-         */
-        public Builder setMaxUsesPerBoot(int count) {
-            mMaxUsesPerBoot = count;
+        public Builder setBlockModes(@KeyStoreKeyConstraints.BlockModeEnum int blockModes) {
+            mBlockModes = blockModes;
             return this;
         }
 
@@ -462,12 +372,10 @@
          *        without user authentication.
          *
          * @see #setUserAuthenticationValidityDurationSeconds(int)
-         *
-         * @hide
          */
-        public Builder setUserAuthenticators(Set<Integer> userAuthenticators) {
-            mUserAuthenticators =
-                    (userAuthenticators != null) ? new HashSet<Integer>(userAuthenticators) : null;
+        public Builder setUserAuthenticators(
+                @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators) {
+            mUserAuthenticators = userAuthenticators;
             return this;
         }
 
@@ -480,9 +388,7 @@
          * @param seconds duration in seconds or {@code 0} if the user needs to authenticate for
          *        every use of the key.
          *
-         * @see #setUserAuthenticators(Set)
-         *
-         * @hide
+         * @see #setUserAuthenticators(int)
          */
         public Builder setUserAuthenticationValidityDurationSeconds(int seconds) {
             mUserAuthenticationValidityDurationSeconds = seconds;
@@ -511,10 +417,18 @@
          * @throws IllegalArgumentException if a required field is missing or violates a constraint.
          */
         public KeyGeneratorSpec build() {
-            return new KeyGeneratorSpec(mContext, mKeystoreAlias, mFlags, mKeySize,
-                    mKeyValidityStart, mKeyValidityForOriginationEnd, mKeyValidityForConsumptionEnd,
-                    mPurposes, mPadding, mBlockMode, mMinSecondsBetweenOperations, mMaxUsesPerBoot,
-                    mUserAuthenticators, mUserAuthenticationValidityDurationSeconds,
+            return new KeyGeneratorSpec(mContext,
+                    mKeystoreAlias,
+                    mFlags,
+                    mKeySize,
+                    mKeyValidityStart,
+                    mKeyValidityForOriginationEnd,
+                    mKeyValidityForConsumptionEnd,
+                    mPurposes,
+                    mPaddings,
+                    mBlockModes,
+                    mUserAuthenticators,
+                    mUserAuthenticationValidityDurationSeconds,
                     mInvalidatedOnNewFingerprintEnrolled);
         }
     }
diff --git a/keystore/java/android/security/KeyPairGeneratorSpec.java b/keystore/java/android/security/KeyPairGeneratorSpec.java
index dd62e9a..52b7097 100644
--- a/keystore/java/android/security/KeyPairGeneratorSpec.java
+++ b/keystore/java/android/security/KeyPairGeneratorSpec.java
@@ -24,10 +24,7 @@
 import java.security.PrivateKey;
 import java.security.cert.Certificate;
 import java.security.spec.AlgorithmParameterSpec;
-import java.util.Collections;
 import java.util.Date;
-import java.util.HashSet;
-import java.util.Set;
 
 import javax.security.auth.x500.X500Principal;
 
@@ -81,21 +78,17 @@
 
     private final Date mKeyValidityForConsumptionEnd;
 
-    private final @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes;
+    private final @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
 
-    private final @KeyStoreKeyConstraints.DigestEnum Integer mDigest;
+    private final @KeyStoreKeyConstraints.DigestEnum int mDigests;
 
-    private final @KeyStoreKeyConstraints.PaddingEnum Integer mPadding;
+    private final @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
 
-    private final @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode;
+    private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
 
-    private final Integer mMinSecondsBetweenOperations;
+    private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
 
-    private final Integer mMaxUsesPerBoot;
-
-    private final Set<Integer> mUserAuthenticators;
-
-    private final Integer mUserAuthenticationValidityDurationSeconds;
+    private final int mUserAuthenticationValidityDurationSeconds;
 
     private final boolean mInvalidatedOnNewFingerprintEnrolled;
 
@@ -137,14 +130,12 @@
             Date keyValidityStart,
             Date keyValidityForOriginationEnd,
             Date keyValidityForConsumptionEnd,
-            @KeyStoreKeyConstraints.PurposeEnum Integer purposes,
-            @KeyStoreKeyConstraints.DigestEnum Integer digest,
-            @KeyStoreKeyConstraints.PaddingEnum Integer padding,
-            @KeyStoreKeyConstraints.BlockModeEnum Integer blockMode,
-            Integer minSecondsBetweenOperations,
-            Integer maxUsesPerBoot,
-            Set<Integer> userAuthenticators,
-            Integer userAuthenticationValidityDurationSeconds,
+            @KeyStoreKeyConstraints.PurposeEnum int purposes,
+            @KeyStoreKeyConstraints.DigestEnum int digests,
+            @KeyStoreKeyConstraints.PaddingEnum int paddings,
+            @KeyStoreKeyConstraints.BlockModeEnum int blockModes,
+            @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators,
+            int userAuthenticationValidityDurationSeconds,
             boolean invalidatedOnNewFingerprintEnrolled) {
         if (context == null) {
             throw new IllegalArgumentException("context == null");
@@ -160,8 +151,8 @@
             throw new IllegalArgumentException("endDate == null");
         } else if (endDate.before(startDate)) {
             throw new IllegalArgumentException("endDate < startDate");
-        } else if ((userAuthenticationValidityDurationSeconds != null)
-                && (userAuthenticationValidityDurationSeconds < 0)) {
+        } else if ((userAuthenticationValidityDurationSeconds < 0)
+                && (userAuthenticationValidityDurationSeconds != -1)) {
             throw new IllegalArgumentException(
                     "userAuthenticationValidityDurationSeconds must not be negative");
         }
@@ -180,14 +171,10 @@
         mKeyValidityForOriginationEnd = keyValidityForOriginationEnd;
         mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd;
         mPurposes = purposes;
-        mDigest = digest;
-        mPadding = padding;
-        mBlockMode = blockMode;
-        mMinSecondsBetweenOperations = minSecondsBetweenOperations;
-        mMaxUsesPerBoot = maxUsesPerBoot;
-        mUserAuthenticators = (userAuthenticators != null)
-                ? new HashSet<Integer>(userAuthenticators)
-                : Collections.<Integer>emptySet();
+        mDigests = digests;
+        mPaddings = paddings;
+        mBlockModes = blockModes;
+        mUserAuthenticators = userAuthenticators;
         mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
         mInvalidatedOnNewFingerprintEnrolled = invalidatedOnNewFingerprintEnrolled;
     }
@@ -200,8 +187,7 @@
             AlgorithmParameterSpec spec, X500Principal subjectDN, BigInteger serialNumber,
             Date startDate, Date endDate, int flags) {
         this(context, keyStoreAlias, keyType, keySize, spec, subjectDN, serialNumber, startDate,
-                endDate, flags, startDate, endDate, endDate, null, null, null, null, null, null,
-                null, null, false);
+                endDate, flags, startDate, endDate, endDate, 0, 0, 0, 0, 0, -1, false);
     }
 
     /**
@@ -327,90 +313,52 @@
     /**
      * Gets the set of purposes for which the key can be used.
      *
-     * @return set of purposes or {@code null} if the key can be used for any purpose.
-     *
      * @hide
      */
-    public @KeyStoreKeyConstraints.PurposeEnum Integer getPurposes() {
+    public @KeyStoreKeyConstraints.PurposeEnum int getPurposes() {
         return mPurposes;
     }
 
     /**
-     * Gets the digest to which the key is restricted.
-     *
-     * @return digest or {@code null} if the digest is not restricted.
+     * Gets the set of digests to which the key is restricted.
      *
      * @hide
      */
-    public @KeyStoreKeyConstraints.DigestEnum Integer getDigest() {
-        return mDigest;
+    public @KeyStoreKeyConstraints.DigestEnum int getDigests() {
+        return mDigests;
     }
 
     /**
-     * Gets the padding scheme to which the key is restricted.
-     *
-     * @return padding scheme or {@code null} if the padding scheme is not restricted.
+     * Gets the set of padding schemes to which the key is restricted.
      *
      * @hide
      */
-    public @KeyStoreKeyConstraints.PaddingEnum Integer getPadding() {
-        return mPadding;
+    public @KeyStoreKeyConstraints.PaddingEnum int getPaddings() {
+        return mPaddings;
     }
 
     /**
-     * Gets the block mode to which the key is restricted when used for encryption or decryption.
-     *
-     * @return block more or {@code null} if block mode is not restricted.
+     * Gets the set of block modes to which the key is restricted.
      *
      * @hide
      */
-    public @KeyStoreKeyConstraints.BlockModeEnum Integer getBlockMode() {
-        return mBlockMode;
+    public @KeyStoreKeyConstraints.BlockModeEnum int getBlockModes() {
+        return mBlockModes;
     }
 
     /**
-     * Gets the minimum number of seconds that must expire since the most recent use of the private
-     * key before it can be used again.
+     * Gets the set of user authenticators which protect access to the private key. The key can only
+     * be used iff the user has authenticated to at least one of these user authenticators.
      *
      * <p>This restriction applies only to private key operations. Public key operations are not
      * restricted.
      *
-     * @return number of seconds or {@code null} if there is no restriction on how frequently a key
-     *         can be used.
+     * @return user authenticators or {@code 0} if the key can be used without user authentication.
      *
      * @hide
      */
-    public Integer getMinSecondsBetweenOperations() {
-        return mMinSecondsBetweenOperations;
-    }
-
-    /**
-     * Gets the number of times the private key can be used without rebooting the device.
-     *
-     * <p>This restriction applies only to private key operations. Public key operations are not
-     * restricted.
-     *
-     * @return maximum number of times or {@code null} if there is no restriction.
-     *
-     * @hide
-     */
-    public Integer getMaxUsesPerBoot() {
-        return mMaxUsesPerBoot;
-    }
-
-    /**
-     * Gets the user authenticators which protect access to the private key. The key can only be
-     * used iff the user has authenticated to at least one of these user authenticators.
-     *
-     * <p>This restriction applies only to private key operations. Public key operations are not
-     * restricted.
-     *
-     * @return user authenticators or empty set if the key can be used without user authentication.
-     *
-     * @hide
-     */
-    public Set<Integer> getUserAuthenticators() {
-        return new HashSet<Integer>(mUserAuthenticators);
+    public @KeyStoreKeyConstraints.UserAuthenticatorEnum int getUserAuthenticators() {
+        return mUserAuthenticators;
     }
 
     /**
@@ -420,12 +368,12 @@
      * <p>This restriction applies only to private key operations. Public key operations are not
      * restricted.
      *
-     * @return duration in seconds or {@code null} if not restricted. {@code 0} means authentication
+     * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication
      *         is required for every use of the key.
      *
      * @hide
      */
-    public Integer getUserAuthenticationValidityDurationSeconds() {
+    public int getUserAuthenticationValidityDurationSeconds() {
         return mUserAuthenticationValidityDurationSeconds;
     }
 
@@ -490,21 +438,17 @@
 
         private Date mKeyValidityForConsumptionEnd;
 
-        private @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes;
+        private @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
 
-        private @KeyStoreKeyConstraints.DigestEnum Integer mDigest;
+        private @KeyStoreKeyConstraints.DigestEnum int mDigests;
 
-        private @KeyStoreKeyConstraints.PaddingEnum Integer mPadding;
+        private @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
 
-        private @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode;
+        private @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
 
-        private Integer mMinSecondsBetweenOperations;
+        private @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
 
-        private Integer mMaxUsesPerBoot;
-
-        private Set<Integer> mUserAuthenticators;
-
-        private Integer mUserAuthenticationValidityDurationSeconds;
+        private int mUserAuthenticationValidityDurationSeconds = -1;
 
         private boolean mInvalidatedOnNewFingerprintEnrolled;
 
@@ -694,9 +638,9 @@
         }
 
         /**
-         * Restricts the purposes for which the key can be used to the provided set of purposes.
+         * Restricts the key to being used only for the provided set of purposes.
          *
-         * <p>By default, the key can be used for encryption, decryption, signing, and verification.
+         * <p>This restriction must be specified. There is no default.
          *
          * @hide
          */
@@ -706,28 +650,28 @@
         }
 
         /**
-         * Restricts the key to being used only with the provided digest. Attempts to use the key
+         * Restricts the key to being used only with the provided digests. Attempts to use the key
          * with any other digests be rejected.
          *
          * <p>This restriction must be specified for keys which are used for signing/verification.
          *
          * @hide
          */
-        public Builder setDigest(@KeyStoreKeyConstraints.DigestEnum int digest) {
-            mDigest = digest;
+        public Builder setDigests(@KeyStoreKeyConstraints.DigestEnum int digests) {
+            mDigests = digests;
             return this;
         }
 
         /**
-         * Restricts the key to being used only with the provided padding scheme. Attempts to use
+         * Restricts the key to being used only with the provided padding schemes. Attempts to use
          * the key with any other padding will be rejected.
          *
          * <p>This restriction must be specified for keys which are used for encryption/decryption.
          *
          * @hide
          */
-        public Builder setPadding(@KeyStoreKeyConstraints.PaddingEnum int padding) {
-            mPadding = padding;
+        public Builder setPaddings(@KeyStoreKeyConstraints.PaddingEnum int paddings) {
+            mPaddings = paddings;
             return this;
         }
 
@@ -739,39 +683,8 @@
          *
          * @hide
          */
-        public Builder setBlockMode(@KeyStoreKeyConstraints.BlockModeEnum int blockMode) {
-            mBlockMode = blockMode;
-            return this;
-        }
-
-        /**
-         * Sets the minimum number of seconds that must expire since the most recent use of the key
-         * before it can be used again.
-         *
-         * <p>By default, there is no restriction on how frequently a key can be used.
-         *
-         * <p>This restriction applies only to private key operations. Public key operations are not
-         * restricted.
-         *
-         * @hide
-         */
-        public Builder setMinSecondsBetweenOperations(int seconds) {
-            mMinSecondsBetweenOperations = seconds;
-            return this;
-        }
-
-        /**
-         * Sets the maximum number of times a key can be used without rebooting the device.
-         *
-         * <p>By default, the key can be used for an unlimited number of times.
-         *
-         * <p>This restriction applies only to private key operations. Public key operations are not
-         * restricted.
-         *
-         * @hide
-         */
-        public Builder setMaxUsesPerBoot(int count) {
-            mMaxUsesPerBoot = count;
+        public Builder setBlockModes(@KeyStoreKeyConstraints.BlockModeEnum int blockModes) {
+            mBlockModes = blockModes;
             return this;
         }
 
@@ -784,16 +697,16 @@
          * <p>This restriction applies only to private key operations. Public key operations are not
          * restricted.
          *
-         * @param userAuthenticators user authenticators or empty list if this key can be accessed
+         * @param userAuthenticators user authenticators or {@code 0} if this key can be accessed
          *        without user authentication.
          *
          * @see #setUserAuthenticationValidityDurationSeconds(int)
          *
          * @hide
          */
-        public Builder setUserAuthenticators(Set<Integer> userAuthenticators) {
-            mUserAuthenticators =
-                    (userAuthenticators != null) ? new HashSet<Integer>(userAuthenticators) : null;
+        public Builder setUserAuthenticators(
+                @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators) {
+            mUserAuthenticators = userAuthenticators;
             return this;
         }
 
@@ -809,7 +722,7 @@
          * @param seconds duration in seconds or {@code 0} if the user needs to authenticate for
          *        every use of the key.
          *
-         * @see #setUserAuthenticators(Set)
+         * @see #setUserAuthenticators(int)
          *
          * @hide
          */
@@ -855,11 +768,9 @@
                     mKeyValidityForOriginationEnd,
                     mKeyValidityForConsumptionEnd,
                     mPurposes,
-                    mDigest,
-                    mPadding,
-                    mBlockMode,
-                    mMinSecondsBetweenOperations,
-                    mMaxUsesPerBoot,
+                    mDigests,
+                    mPaddings,
+                    mBlockModes,
                     mUserAuthenticators,
                     mUserAuthenticationValidityDurationSeconds,
                     mInvalidatedOnNewFingerprintEnrolled);
diff --git a/keystore/java/android/security/KeyStoreKeyConstraints.java b/keystore/java/android/security/KeyStoreKeyConstraints.java
index 75034d1..7137a9a 100644
--- a/keystore/java/android/security/KeyStoreKeyConstraints.java
+++ b/keystore/java/android/security/KeyStoreKeyConstraints.java
@@ -21,12 +21,8 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
 import java.util.Locale;
-import java.util.Set;
 
 /**
  * Constraints for {@code AndroidKeyStore} keys.
@@ -37,7 +33,8 @@
     private KeyStoreKeyConstraints() {}
 
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(flag=true, value={Purpose.ENCRYPT, Purpose.DECRYPT, Purpose.SIGN, Purpose.VERIFY})
+    @IntDef(flag = true,
+            value = {Purpose.ENCRYPT, Purpose.DECRYPT, Purpose.SIGN, Purpose.VERIFY})
     public @interface PurposeEnum {}
 
     /**
@@ -67,11 +64,6 @@
         public static final int VERIFY = 1 << 3;
 
         /**
-         * Number of flags defined above. Needs to be kept in sync with the flags above.
-         */
-        private static final int VALUE_COUNT = 4;
-
-        /**
          * @hide
          */
         public static int toKeymaster(@PurposeEnum int purpose) {
@@ -110,22 +102,12 @@
         /**
          * @hide
          */
-        public static int[] allToKeymaster(int purposes) {
-            int[] result = new int[VALUE_COUNT];
-            int resultCount = 0;
-            int purpose = 1;
-            for (int i = 0; i < 32; i++) {
-                if ((purposes & 1) != 0) {
-                    result[resultCount] = toKeymaster(purpose);
-                    resultCount++;
-                }
-                purposes >>>= 1;
-                purpose <<= 1;
-                if (purposes == 0) {
-                    break;
-                }
+        public static int[] allToKeymaster(@PurposeEnum int purposes) {
+            int[] result = getSetFlags(purposes);
+            for (int i = 0; i < result.length; i++) {
+                result[i] = toKeymaster(result[i]);
             }
-            return Arrays.copyOf(result, resultCount);
+            return result;
         }
 
         /**
@@ -244,7 +226,8 @@
     }
 
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({Padding.NONE, Padding.ZERO, Padding.PKCS7})
+    @IntDef(flag = true,
+            value = {Padding.NONE, Padding.PKCS7})
     public @interface PaddingEnum {}
 
     /**
@@ -256,17 +239,12 @@
         /**
          * No padding.
          */
-        public static final int NONE = 0;
-
-        /**
-         * Pad with zeros.
-         */
-        public static final int ZERO = 1;
+        public static final int NONE = 1 << 0;
 
         /**
          * PKCS#7 padding.
          */
-        public static final int PKCS7 = 2;
+        public static final int PKCS7 = 1 << 1;
 
         /**
          * @hide
@@ -275,8 +253,6 @@
             switch (padding) {
                 case NONE:
                     return KeymasterDefs.KM_PAD_NONE;
-                case ZERO:
-                    return KeymasterDefs.KM_PAD_ZERO;
                 case PKCS7:
                     return KeymasterDefs.KM_PAD_PKCS7;
                 default:
@@ -291,8 +267,6 @@
             switch (padding) {
                 case KeymasterDefs.KM_PAD_NONE:
                     return NONE;
-                case KeymasterDefs.KM_PAD_ZERO:
-                    return ZERO;
                 case KeymasterDefs.KM_PAD_PKCS7:
                     return PKCS7;
                 default:
@@ -307,8 +281,6 @@
             switch (padding) {
                 case NONE:
                     return "NONE";
-                case ZERO:
-                    return "ZERO";
                 case PKCS7:
                     return "PKCS#7";
                 default:
@@ -329,10 +301,33 @@
                 throw new IllegalArgumentException("Unknown padding: " + padding);
             }
         }
+
+        /**
+         * @hide
+         */
+        public static int[] allToKeymaster(@PaddingEnum int paddings) {
+            int[] result = getSetFlags(paddings);
+            for (int i = 0; i < result.length; i++) {
+                result[i] = toKeymaster(result[i]);
+            }
+            return result;
+        }
+
+        /**
+         * @hide
+         */
+        public static @PaddingEnum int allFromKeymaster(Collection<Integer> paddings) {
+            @PaddingEnum int result = 0;
+            for (int keymasterPadding : paddings) {
+                result |= fromKeymaster(keymasterPadding);
+            }
+            return result;
+        }
     }
 
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({Digest.NONE, Digest.SHA256})
+    @IntDef(flag = true,
+            value = {Digest.NONE, Digest.SHA256})
     public @interface DigestEnum {}
 
     /**
@@ -345,12 +340,12 @@
         /**
          * No digest: sign/authenticate the raw message.
          */
-        public static final int NONE = 0;
+        public static final int NONE = 1 << 0;
 
         /**
          * SHA-256 digest.
          */
-        public static final int SHA256 = 1;
+        public static final int SHA256 = 1 << 1;
 
         /**
          * @hide
@@ -369,6 +364,18 @@
         /**
          * @hide
          */
+        public static String[] allToString(@DigestEnum int digests) {
+            int[] values = getSetFlags(digests);
+            String[] result = new String[values.length];
+            for (int i = 0; i < values.length; i++) {
+                result[i] = toString(values[i]);
+            }
+            return result;
+        }
+
+        /**
+         * @hide
+         */
         public static int toKeymaster(@DigestEnum int digest) {
             switch (digest) {
                 case NONE:
@@ -397,6 +404,28 @@
         /**
          * @hide
          */
+        public static int[] allToKeymaster(@DigestEnum int digests) {
+            int[] result = getSetFlags(digests);
+            for (int i = 0; i < result.length; i++) {
+                result[i] = toKeymaster(result[i]);
+            }
+            return result;
+        }
+
+        /**
+         * @hide
+         */
+        public static @DigestEnum int allFromKeymaster(Collection<Integer> digests) {
+            @DigestEnum int result = 0;
+            for (int keymasterDigest : digests) {
+                result |= fromKeymaster(keymasterDigest);
+            }
+            return result;
+        }
+
+        /**
+         * @hide
+         */
         public static @DigestEnum Integer fromJCASecretKeyAlgorithm(String algorithm) {
             String algorithmLower = algorithm.toLowerCase(Locale.US);
             if (algorithmLower.startsWith("hmac")) {
@@ -441,7 +470,8 @@
     }
 
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({BlockMode.ECB, BlockMode.CBC, BlockMode.CTR})
+    @IntDef(flag = true,
+            value = {BlockMode.ECB, BlockMode.CBC, BlockMode.CTR})
     public @interface BlockModeEnum {}
 
     /**
@@ -451,13 +481,13 @@
         private BlockMode() {}
 
         /** Electronic Codebook (ECB) block mode. */
-        public static final int ECB = 0;
+        public static final int ECB = 1 << 0;
 
         /** Cipher Block Chaining (CBC) block mode. */
-        public static final int CBC = 1;
+        public static final int CBC = 1 << 1;
 
         /** Counter (CTR) block mode. */
-        public static final int CTR = 2;
+        public static final int CTR = 1 << 2;
 
         /**
          * @hide
@@ -494,6 +524,28 @@
         /**
          * @hide
          */
+        public static int[] allToKeymaster(@BlockModeEnum int modes) {
+            int[] result = getSetFlags(modes);
+            for (int i = 0; i < result.length; i++) {
+                result[i] = toKeymaster(result[i]);
+            }
+            return result;
+        }
+
+        /**
+         * @hide
+         */
+        public static @BlockModeEnum int allFromKeymaster(Collection<Integer> modes) {
+            @BlockModeEnum int result = 0;
+            for (int keymasterMode : modes) {
+                result |= fromKeymaster(keymasterMode);
+            }
+            return result;
+        }
+
+        /**
+         * @hide
+         */
         public static String toString(@BlockModeEnum int mode) {
             switch (mode) {
                 case ECB:
@@ -525,7 +577,8 @@
     }
 
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({UserAuthenticator.LOCK_SCREEN})
+    @IntDef(flag = true,
+            value = {UserAuthenticator.LOCK_SCREEN})
     public @interface UserAuthenticatorEnum {}
 
     /**
@@ -535,7 +588,7 @@
         private UserAuthenticator() {}
 
         /** Lock screen. */
-        public static final int LOCK_SCREEN = 1;
+        public static final int LOCK_SCREEN = 1 << 0;
 
         /** Fingerprint reader/sensor. */
         public static final int FINGERPRINT_READER = 1 << 1;
@@ -546,9 +599,9 @@
         public static int toKeymaster(@UserAuthenticatorEnum int userAuthenticator) {
             switch (userAuthenticator) {
                 case LOCK_SCREEN:
-                    return LOCK_SCREEN;
+                    return KeymasterDefs.HW_AUTH_PASSWORD;
                 case FINGERPRINT_READER:
-                    return FINGERPRINT_READER;
+                    return KeymasterDefs.HW_AUTH_FINGERPRINT;
                 default:
                     throw new IllegalArgumentException(
                             "Unknown user authenticator: " + userAuthenticator);
@@ -560,7 +613,7 @@
          */
         public static @UserAuthenticatorEnum int fromKeymaster(int userAuthenticator) {
             switch (userAuthenticator) {
-                case LOCK_SCREEN:
+                case KeymasterDefs.HW_AUTH_PASSWORD:
                     return LOCK_SCREEN;
                 case FINGERPRINT_READER:
                     return FINGERPRINT_READER;
@@ -573,10 +626,15 @@
         /**
          * @hide
          */
-        public static int allToKeymaster(Set<Integer> userAuthenticators) {
+        public static int allToKeymaster(@UserAuthenticatorEnum int userAuthenticators) {
             int result = 0;
-            for (@UserAuthenticatorEnum int userAuthenticator : userAuthenticators) {
-                result |= toKeymaster(userAuthenticator);
+            int userAuthenticator = 1;
+            while (userAuthenticators != 0) {
+                if ((userAuthenticators & 1) != 0) {
+                    result |= toKeymaster(userAuthenticator);
+                }
+                userAuthenticators >>>= 1;
+                userAuthenticator <<= 1;
             }
             return result;
         }
@@ -584,20 +642,17 @@
         /**
          * @hide
          */
-        public static Set<Integer> allFromKeymaster(int userAuthenticators) {
+        public static @UserAuthenticatorEnum int allFromKeymaster(int userAuthenticators) {
+            @UserAuthenticatorEnum int result = 0;
             int userAuthenticator = 1;
-            Set<Integer> result = null;
             while (userAuthenticators != 0) {
                 if ((userAuthenticators & 1) != 0) {
-                    if (result == null) {
-                        result = new HashSet<Integer>();
-                    }
-                    result.add(fromKeymaster(userAuthenticator));
+                    result |= fromKeymaster(userAuthenticator);
                 }
                 userAuthenticators >>>= 1;
                 userAuthenticator <<= 1;
             }
-            return (result != null) ? result : Collections.<Integer>emptySet();
+            return result;
         }
 
         /**
@@ -615,4 +670,38 @@
             }
         }
     }
+
+    private static final int[] EMPTY_INT_ARRAY = new int[0];
+
+    private static int[] getSetFlags(int flags) {
+        if (flags == 0) {
+            return EMPTY_INT_ARRAY;
+        }
+        int result[] = new int[getSetBitCount(flags)];
+        int resultOffset = 0;
+        int flag = 1;
+        while (flags != 0) {
+            if ((flags & 1) != 0) {
+                result[resultOffset] = flag;
+                resultOffset++;
+            }
+            flags >>>= 1;
+            flag <<= 1;
+        }
+        return result;
+    }
+
+    private static int getSetBitCount(int value) {
+        if (value == 0) {
+            return 0;
+        }
+        int result = 0;
+        while (value != 0) {
+            if ((value & 1) != 0) {
+                result++;
+            }
+            value >>>= 1;
+        }
+        return result;
+    }
 }
diff --git a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
index 48b6d06..abce32d 100644
--- a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
@@ -109,32 +109,20 @@
         }
         int keySizeBits = (spec.getKeySize() != null) ? spec.getKeySize() : mDefaultKeySizeBits;
         args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keySizeBits);
-        @KeyStoreKeyConstraints.PurposeEnum int purposes = (spec.getPurposes() != null)
-                ? spec.getPurposes()
-                : (KeyStoreKeyConstraints.Purpose.ENCRYPT
-                        | KeyStoreKeyConstraints.Purpose.DECRYPT
-                        | KeyStoreKeyConstraints.Purpose.SIGN
-                        | KeyStoreKeyConstraints.Purpose.VERIFY);
+        int purposes = spec.getPurposes();
         for (int keymasterPurpose :
             KeyStoreKeyConstraints.Purpose.allToKeymaster(purposes)) {
             args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose);
         }
-        if (spec.getBlockMode() != null) {
-            args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE,
-                    KeyStoreKeyConstraints.BlockMode.toKeymaster(spec.getBlockMode()));
+        for (int keymasterBlockMode :
+            KeyStoreKeyConstraints.BlockMode.allToKeymaster(spec.getBlockModes())) {
+            args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockMode);
         }
-        if (spec.getPadding() != null) {
-            args.addInt(KeymasterDefs.KM_TAG_PADDING,
-                    KeyStoreKeyConstraints.Padding.toKeymaster(spec.getPadding()));
+        for (int keymasterPadding :
+            KeyStoreKeyConstraints.Padding.allToKeymaster(spec.getPaddings())) {
+            args.addInt(KeymasterDefs.KM_TAG_PADDING, keymasterPadding);
         }
-        if (spec.getMaxUsesPerBoot() != null) {
-            args.addInt(KeymasterDefs.KM_TAG_MAX_USES_PER_BOOT, spec.getMaxUsesPerBoot());
-        }
-        if (spec.getMinSecondsBetweenOperations() != null) {
-            args.addInt(KeymasterDefs.KM_TAG_MIN_SECONDS_BETWEEN_OPS,
-                    spec.getMinSecondsBetweenOperations());
-        }
-        if (spec.getUserAuthenticators().isEmpty()) {
+        if (spec.getUserAuthenticators() == 0) {
             args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
         } else {
             args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE,
@@ -145,7 +133,7 @@
             // TODO: Add the invalidate on fingerprint enrolled constraint once Keymaster supports
             // that.
         }
-        if (spec.getUserAuthenticationValidityDurationSeconds() != null) {
+        if (spec.getUserAuthenticationValidityDurationSeconds() != -1) {
             args.addInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT,
                     spec.getUserAuthenticationValidityDurationSeconds());
         }
diff --git a/keystore/java/android/security/KeyStoreKeySpec.java b/keystore/java/android/security/KeyStoreKeySpec.java
index e5e5acc..256d9b3 100644
--- a/keystore/java/android/security/KeyStoreKeySpec.java
+++ b/keystore/java/android/security/KeyStoreKeySpec.java
@@ -17,10 +17,7 @@
 package android.security;
 
 import java.security.spec.KeySpec;
-import java.util.Collections;
 import java.util.Date;
-import java.util.HashSet;
-import java.util.Set;
 
 /**
  * Information about a key from the <a href="{@docRoot}training/articles/keystore.html">Android
@@ -37,34 +34,31 @@
     private final Date mKeyValidityForConsumptionEnd;
     private final @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
     private final @KeyStoreKeyConstraints.AlgorithmEnum int mAlgorithm;
-    private final @KeyStoreKeyConstraints.PaddingEnum Integer mPadding;
-    private final @KeyStoreKeyConstraints.DigestEnum Integer mDigest;
-    private final @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode;
-    private final Integer mMinSecondsBetweenOperations;
-    private final Integer mMaxUsesPerBoot;
-    private final Set<Integer> mUserAuthenticators;
-    private final Set<Integer> mTeeBackedUserAuthenticators;
-    private final Integer mUserAuthenticationValidityDurationSeconds;
+    private final @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
+    private final @KeyStoreKeyConstraints.DigestEnum int mDigests;
+    private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+    private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
+    private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mTeeEnforcedUserAuthenticators;
+    private final int mUserAuthenticationValidityDurationSeconds;
     private final boolean mInvalidatedOnNewFingerprintEnrolled;
 
-
     /**
      * @hide
      */
     KeyStoreKeySpec(String keystoreKeyAlias,
             @KeyStoreKeyCharacteristics.OriginEnum int origin,
-            int keySize, Date keyValidityStart, Date keyValidityForOriginationEnd,
+            int keySize,
+            Date keyValidityStart,
+            Date keyValidityForOriginationEnd,
             Date keyValidityForConsumptionEnd,
             @KeyStoreKeyConstraints.PurposeEnum int purposes,
             @KeyStoreKeyConstraints.AlgorithmEnum int algorithm,
-            @KeyStoreKeyConstraints.PaddingEnum Integer padding,
-            @KeyStoreKeyConstraints.DigestEnum Integer digest,
-            @KeyStoreKeyConstraints.BlockModeEnum Integer blockMode,
-            Integer minSecondsBetweenOperations,
-            Integer maxUsesPerBoot,
-            Set<Integer> userAuthenticators,
-            Set<Integer> teeBackedUserAuthenticators,
-            Integer userAuthenticationValidityDurationSeconds,
+            @KeyStoreKeyConstraints.PaddingEnum int paddings,
+            @KeyStoreKeyConstraints.DigestEnum int digests,
+            @KeyStoreKeyConstraints.BlockModeEnum int blockModes,
+            @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators,
+            @KeyStoreKeyConstraints.UserAuthenticatorEnum int teeEnforcedUserAuthenticators,
+            int userAuthenticationValidityDurationSeconds,
             boolean invalidatedOnNewFingerprintEnrolled) {
         mKeystoreAlias = keystoreKeyAlias;
         mOrigin = origin;
@@ -74,17 +68,11 @@
         mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd;
         mPurposes = purposes;
         mAlgorithm = algorithm;
-        mPadding = padding;
-        mDigest = digest;
-        mBlockMode = blockMode;
-        mMinSecondsBetweenOperations = minSecondsBetweenOperations;
-        mMaxUsesPerBoot = maxUsesPerBoot;
-        mUserAuthenticators = (userAuthenticators != null)
-                ? new HashSet<Integer>(userAuthenticators)
-                : Collections.<Integer>emptySet();
-        mTeeBackedUserAuthenticators = (teeBackedUserAuthenticators != null)
-                ? new HashSet<Integer>(teeBackedUserAuthenticators)
-                : Collections.<Integer>emptySet();
+        mPaddings = paddings;
+        mDigests = digests;
+        mBlockModes = blockModes;
+        mUserAuthenticators = userAuthenticators;
+        mTeeEnforcedUserAuthenticators = teeEnforcedUserAuthenticators;
         mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
         mInvalidatedOnNewFingerprintEnrolled = invalidatedOnNewFingerprintEnrolled;
     }
@@ -104,7 +92,7 @@
     }
 
     /**
-     * Gets the key's size in bits.
+     * Gets the size of the key in bits.
      */
     public int getKeySize() {
         return mKeySize;
@@ -152,78 +140,53 @@
     }
 
     /**
-     * Gets the only block mode with which the key can be used.
-     *
-     * @return block mode or {@code null} if the block mode is not restricted.
+     * Gets the set of block modes with which the key can be used.
      */
-    public @KeyStoreKeyConstraints.BlockModeEnum Integer getBlockMode() {
-        return mBlockMode;
+    public @KeyStoreKeyConstraints.BlockModeEnum int getBlockModes() {
+        return mBlockModes;
     }
 
     /**
-     * Gets the only padding mode with which the key can be used.
-     *
-     * @return padding mode or {@code null} if the padding mode is not restricted.
+     * Gets the set of padding modes with which the key can be used.
      */
-    public @KeyStoreKeyConstraints.PaddingEnum Integer getPadding() {
-        return mPadding;
+    public @KeyStoreKeyConstraints.PaddingEnum int getPaddings() {
+        return mPaddings;
     }
 
     /**
-     * Gets the only digest algorithm with which the key can be used.
-     *
-     * @return digest algorithm or {@code null} if the digest algorithm is not restricted.
+     * Gets the set of digest algorithms with which the key can be used.
      */
-    public @KeyStoreKeyConstraints.DigestEnum Integer getDigest() {
-        return mDigest;
+    public @KeyStoreKeyConstraints.DigestEnum int getDigests() {
+        return mDigests;
     }
 
     /**
-     * Gets the minimum number of seconds that must expire since the most recent use of the key
-     * before it can be used again.
+     * Gets the set of user authenticators which protect access to the key. The key can only be used
+     * iff the user has authenticated to at least one of these user authenticators.
      *
-     * @return number of seconds or {@code null} if there is no restriction on how frequently a key
-     *         can be used.
+     * @return user authenticators or {@code 0} if the key can be used without user authentication.
      */
-    public Integer getMinSecondsBetweenOperations() {
-        return mMinSecondsBetweenOperations;
+    public @KeyStoreKeyConstraints.UserAuthenticatorEnum int getUserAuthenticators() {
+        return mUserAuthenticators;
     }
 
     /**
-     * Gets the number of times the key can be used without rebooting the device.
-     *
-     * @return maximum number of times or {@code null} if there is no restriction.
+     * Gets the set of user authenticators for which the TEE enforces access restrictions for this
+     * key. This is a subset of the user authentications returned by
+     * {@link #getUserAuthenticators()}.
      */
-    public Integer getMaxUsesPerBoot() {
-        return mMaxUsesPerBoot;
-    }
-
-    /**
-     * Gets the user authenticators which protect access to the key. The key can only be used iff
-     * the user has authenticated to at least one of these user authenticators.
-     *
-     * @return user authenticators or empty set if the key can be used without user authentication.
-     */
-    public Set<Integer> getUserAuthenticators() {
-        return new HashSet<Integer>(mUserAuthenticators);
-    }
-
-    /**
-     * Gets the TEE-backed user authenticators which protect access to the key. This is a subset of
-     * the user authentications returned by {@link #getUserAuthenticators()}.
-     */
-    public Set<Integer> getTeeBackedUserAuthenticators() {
-        return new HashSet<Integer>(mTeeBackedUserAuthenticators);
+    public @KeyStoreKeyConstraints.UserAuthenticatorEnum int getTeeEnforcedUserAuthenticators() {
+        return mTeeEnforcedUserAuthenticators;
     }
 
     /**
      * Gets the duration of time (seconds) for which the key can be used after the user
      * successfully authenticates to one of the associated user authenticators.
      *
-     * @return duration in seconds or {@code null} if not restricted. {@code 0} means authentication
+     * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication
      *         is required for every use of the key.
      */
-    public Integer getUserAuthenticationValidityDurationSeconds() {
+    public int getUserAuthenticationValidityDurationSeconds() {
         return mUserAuthenticationValidityDurationSeconds;
     }
 
diff --git a/keystore/java/android/security/KeyStoreParameter.java b/keystore/java/android/security/KeyStoreParameter.java
index 88bd6b4..0b2f9b6 100644
--- a/keystore/java/android/security/KeyStoreParameter.java
+++ b/keystore/java/android/security/KeyStoreParameter.java
@@ -18,12 +18,10 @@
 
 import android.content.Context;
 
+import java.security.Key;
 import java.security.KeyPairGenerator;
 import java.security.KeyStore.ProtectionParameter;
-import java.util.Collections;
 import java.util.Date;
-import java.util.HashSet;
-import java.util.Set;
 
 /**
  * This provides the optional parameters that can be specified for
@@ -50,33 +48,27 @@
     private final Date mKeyValidityStart;
     private final Date mKeyValidityForOriginationEnd;
     private final Date mKeyValidityForConsumptionEnd;
-    private final @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes;
-    private final @KeyStoreKeyConstraints.AlgorithmEnum Integer mAlgorithm;
-    private final @KeyStoreKeyConstraints.PaddingEnum Integer mPadding;
-    private final @KeyStoreKeyConstraints.DigestEnum Integer mDigest;
-    private final @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode;
-    private final Integer mMinSecondsBetweenOperations;
-    private final Integer mMaxUsesPerBoot;
-    private final Set<Integer> mUserAuthenticators;
-    private final Integer mUserAuthenticationValidityDurationSeconds;
+    private final @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
+    private final @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
+    private final @KeyStoreKeyConstraints.DigestEnum Integer mDigests;
+    private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+    private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
+    private final int mUserAuthenticationValidityDurationSeconds;
     private final boolean mInvalidatedOnNewFingerprintEnrolled;
 
     private KeyStoreParameter(int flags,
             Date keyValidityStart,
             Date keyValidityForOriginationEnd,
             Date keyValidityForConsumptionEnd,
-            @KeyStoreKeyConstraints.PurposeEnum Integer purposes,
-            @KeyStoreKeyConstraints.AlgorithmEnum Integer algorithm,
-            @KeyStoreKeyConstraints.PaddingEnum Integer padding,
-            @KeyStoreKeyConstraints.DigestEnum Integer digest,
-            @KeyStoreKeyConstraints.BlockModeEnum Integer blockMode,
-            Integer minSecondsBetweenOperations,
-            Integer maxUsesPerBoot,
-            Set<Integer> userAuthenticators,
-            Integer userAuthenticationValidityDurationSeconds,
+            @KeyStoreKeyConstraints.PurposeEnum int purposes,
+            @KeyStoreKeyConstraints.PaddingEnum int paddings,
+            @KeyStoreKeyConstraints.DigestEnum Integer digests,
+            @KeyStoreKeyConstraints.BlockModeEnum int blockModes,
+            @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators,
+            int userAuthenticationValidityDurationSeconds,
             boolean invalidatedOnNewFingerprintEnrolled) {
-        if ((userAuthenticationValidityDurationSeconds != null)
-                && (userAuthenticationValidityDurationSeconds < 0)) {
+        if ((userAuthenticationValidityDurationSeconds < 0)
+                && (userAuthenticationValidityDurationSeconds != -1)) {
             throw new IllegalArgumentException(
                     "userAuthenticationValidityDurationSeconds must not be negative");
         }
@@ -86,15 +78,10 @@
         mKeyValidityForOriginationEnd = keyValidityForOriginationEnd;
         mKeyValidityForConsumptionEnd = keyValidityForConsumptionEnd;
         mPurposes = purposes;
-        mAlgorithm = algorithm;
-        mPadding = padding;
-        mDigest = digest;
-        mBlockMode = blockMode;
-        mMinSecondsBetweenOperations = minSecondsBetweenOperations;
-        mMaxUsesPerBoot = maxUsesPerBoot;
-        mUserAuthenticators = (userAuthenticators != null)
-                ? new HashSet<Integer>(userAuthenticators)
-                : Collections.<Integer>emptySet();
+        mPaddings = paddings;
+        mDigests = digests;
+        mBlockModes = blockModes;
+        mUserAuthenticators = userAuthenticators;
         mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
         mInvalidatedOnNewFingerprintEnrolled = invalidatedOnNewFingerprintEnrolled;
     }
@@ -147,105 +134,81 @@
     }
 
     /**
-     * Gets the set of purposes for which the key can be used to the provided set of purposes.
-     *
-     * @return set of purposes or {@code null} if the key can be used for any purpose.
+     * Gets the set of purposes for which the key can be used.
      *
      * @hide
      */
-    public @KeyStoreKeyConstraints.PurposeEnum Integer getPurposes() {
+    public @KeyStoreKeyConstraints.PurposeEnum int getPurposes() {
         return mPurposes;
     }
 
     /**
-     * Gets the algorithm to which the key is restricted.
+     * Gets the set of padding schemes to which the key is restricted.
      *
-     * @return algorithm or {@code null} if it's not restricted.
      * @hide
      */
-    public @KeyStoreKeyConstraints.AlgorithmEnum Integer getAlgorithm() {
-        return mAlgorithm;
+    public @KeyStoreKeyConstraints.PaddingEnum int getPaddings() {
+        return mPaddings;
     }
 
     /**
-     * Gets the padding scheme to which the key is restricted.
+     * Gets the set of digests to which the key is restricted.
      *
-     * @return padding scheme or {@code null} if the padding scheme is not restricted.
+     * @throws IllegalStateException if this restriction has not been specified.
+     *
+     * @see #isDigestsSpecified()
      *
      * @hide
      */
-    public @KeyStoreKeyConstraints.PaddingEnum Integer getPadding() {
-        return mPadding;
+    public @KeyStoreKeyConstraints.DigestEnum int getDigests() {
+        if (mDigests == null) {
+            throw new IllegalStateException("Digests not specified");
+        }
+        return mDigests;
     }
 
     /**
-     * Gets the digest to which the key is restricted when generating signatures or Message
-     * Authentication Codes (MACs).
+     * Returns {@code true} if digest restrictions have been specified.
      *
-     * @return digest or {@code null} if the digest is not restricted.
+     * @see #getDigests()
      *
      * @hide
      */
-    public @KeyStoreKeyConstraints.DigestEnum Integer getDigest() {
-        return mDigest;
+    public boolean isDigestsSpecified() {
+        return mDigests != null;
     }
 
     /**
-     * Gets the block mode to which the key is restricted when used for encryption or decryption.
-     *
-     * @return block more or {@code null} if block mode is not restricted.
+     * Gets the set of block modes to which the key is restricted.
      *
      * @hide
      */
-    public @KeyStoreKeyConstraints.BlockModeEnum Integer getBlockMode() {
-        return mBlockMode;
+    public @KeyStoreKeyConstraints.BlockModeEnum int getBlockModes() {
+        return mBlockModes;
     }
 
     /**
-     * Gets the minimum number of seconds that must expire since the most recent use of the key
-     * before it can be used again.
+     * Gets the set of user authenticators which protect access to this key. The key can only be
+     * used iff the user has authenticated to at least one of these user authenticators.
      *
-     * @return number of seconds or {@code null} if there is no restriction on how frequently a key
-     *         can be used.
+     * @return user authenticators or {@code 0} if the key can be used without user authentication.
      *
      * @hide
      */
-    public Integer getMinSecondsBetweenOperations() {
-        return mMinSecondsBetweenOperations;
-    }
-
-    /**
-     * Gets the number of times the key can be used without rebooting the device.
-     *
-     * @return maximum number of times or {@code null} if there is no restriction.
-     * @hide
-     */
-    public Integer getMaxUsesPerBoot() {
-        return mMaxUsesPerBoot;
-    }
-
-    /**
-     * Gets the user authenticators which protect access to this key. The key can only be used iff
-     * the user has authenticated to at least one of these user authenticators.
-     *
-     * @return user authenticators or empty set if the key can be used without user authentication.
-     *
-     * @hide
-     */
-    public Set<Integer> getUserAuthenticators() {
-        return new HashSet<Integer>(mUserAuthenticators);
+    public @KeyStoreKeyConstraints.UserAuthenticatorEnum int getUserAuthenticators() {
+        return mUserAuthenticators;
     }
 
     /**
      * Gets the duration of time (seconds) for which this key can be used after the user
      * successfully authenticates to one of the associated user authenticators.
      *
-     * @return duration in seconds or {@code null} if not restricted. {@code 0} means authentication
+     * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication
      *         is required for every use of the key.
      *
      * @hide
      */
-    public Integer getUserAuthenticationValidityDurationSeconds() {
+    public int getUserAuthenticationValidityDurationSeconds() {
         return mUserAuthenticationValidityDurationSeconds;
     }
 
@@ -284,15 +247,12 @@
         private Date mKeyValidityStart;
         private Date mKeyValidityForOriginationEnd;
         private Date mKeyValidityForConsumptionEnd;
-        private @KeyStoreKeyConstraints.PurposeEnum Integer mPurposes;
-        private @KeyStoreKeyConstraints.AlgorithmEnum Integer mAlgorithm;
-        private @KeyStoreKeyConstraints.PaddingEnum Integer mPadding;
-        private @KeyStoreKeyConstraints.DigestEnum Integer mDigest;
-        private @KeyStoreKeyConstraints.BlockModeEnum Integer mBlockMode;
-        private Integer mMinSecondsBetweenOperations;
-        private Integer mMaxUsesPerBoot;
-        private Set<Integer> mUserAuthenticators;
-        private Integer mUserAuthenticationValidityDurationSeconds;
+        private @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
+        private @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
+        private @KeyStoreKeyConstraints.DigestEnum Integer mDigests;
+        private @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+        private @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
+        private int mUserAuthenticationValidityDurationSeconds = -1;
         private boolean mInvalidatedOnNewFingerprintEnrolled;
 
         /**
@@ -385,9 +345,9 @@
         }
 
         /**
-         * Restricts the purposes for which the key can be used to the provided set of purposes.
+         * Restricts the key to being used only for the provided set of purposes.
          *
-         * <p>By default, the key can be used for encryption, decryption, signing, and verification.
+         * <p>This restriction must be specified. There is no default.
          *
          * @hide
          */
@@ -397,84 +357,43 @@
         }
 
         /**
-         * Sets the algorithm of the key.
-         *
-         * <p>The algorithm of symmetric keys can be deduced from the key itself. Thus, explicitly
-         * specifying the algorithm of symmetric keys using this method is not necessary.
-         *
-         * @hide
-         */
-        public Builder setAlgorithm(@KeyStoreKeyConstraints.AlgorithmEnum int algorithm) {
-            mAlgorithm = algorithm;
-            return this;
-        }
-
-        /**
-         * Restricts the key to being used only with the provided padding scheme. Attempts to use
+         * Restricts the key to being used only with the provided padding schemes. Attempts to use
          * the key with any other padding will be rejected.
          *
          * <p>This restriction must be specified for keys which are used for encryption/decryption.
          *
          * @hide
          */
-        public Builder setPadding(@KeyStoreKeyConstraints.PaddingEnum int padding) {
-            mPadding = padding;
+        public Builder setPaddings(@KeyStoreKeyConstraints.PaddingEnum int paddings) {
+            mPaddings = paddings;
             return this;
         }
 
         /**
-         * Restricts the key to being used only with the provided digest when generating signatures
-         * or Message Authentication Codes (MACs). Attempts to use the key with any other digest
-         * will be rejected.
+         * Restricts the key to being used only with the provided digests when generating signatures
+         * or HMACs. Attempts to use the key with any other digest will be rejected.
          *
-         * <p>For MAC keys, the default is to restrict to the digest specified in the key algorithm
-         * name. For asymmetric signing keys this constraint must be specified because there is no
-         * default.
-         *
-         * @see java.security.Key#getAlgorithm()
+         * <p>For HMAC keys, the default is to restrict to the digest specified in
+         * {@link Key#getAlgorithm()}. For asymmetric signing keys this constraint must be specified
+         * because there is no default.
          *
          * @hide
          */
-        public Builder setDigest(@KeyStoreKeyConstraints.DigestEnum int digest) {
-            mDigest = digest;
+        public Builder setDigests(@KeyStoreKeyConstraints.DigestEnum int digests) {
+            mDigests = digests;
             return this;
         }
 
         /**
-         * Restricts the key to being used only with the provided block mode when encrypting or
-         * decrypting. Attempts to use the key with any other block modes will be rejected.
+         * Restricts the key to being used only with the provided block modes. Attempts to use the
+         * key with any other block modes will be rejected.
          *
-         * <p>This restriction must be specified for keys which are used for encryption/decryption.
+         * <p>This restriction must be specified for symmetric encryption/decryption keys.
          *
          * @hide
          */
-        public Builder setBlockMode(@KeyStoreKeyConstraints.BlockModeEnum int blockMode) {
-            mBlockMode = blockMode;
-            return this;
-        }
-
-        /**
-         * Sets the minimum number of seconds that must expire since the most recent use of the key
-         * before it can be used again.
-         *
-         * <p>By default, there is no restriction on how frequently a key can be used.
-         *
-         * @hide
-         */
-        public Builder setMinSecondsBetweenOperations(int seconds) {
-            mMinSecondsBetweenOperations = seconds;
-            return this;
-        }
-
-        /**
-         * Sets the maximum number of times a key can be used without rebooting the device.
-         *
-         * <p>By default, the key can be used for an unlimited number of times.
-         *
-         * @hide
-         */
-        public Builder setMaxUsesPerBoot(int count) {
-            mMaxUsesPerBoot = count;
+        public Builder setBlockModes(@KeyStoreKeyConstraints.BlockModeEnum int blockModes) {
+            mBlockModes = blockModes;
             return this;
         }
 
@@ -484,16 +403,16 @@
          *
          * <p>By default, the key can be used without user authentication.
          *
-         * @param userAuthenticators user authenticators or empty list if this key can be accessed
+         * @param userAuthenticators user authenticators or {@code 0} if this key can be accessed
          *        without user authentication.
          *
          * @see #setUserAuthenticationValidityDurationSeconds(int)
          *
          * @hide
          */
-        public Builder setUserAuthenticators(Set<Integer> userAuthenticators) {
-            mUserAuthenticators =
-                    (userAuthenticators != null) ? new HashSet<Integer>(userAuthenticators) : null;
+        public Builder setUserAuthenticators(
+                @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators) {
+            mUserAuthenticators = userAuthenticators;
             return this;
         }
 
@@ -506,7 +425,7 @@
          * @param seconds duration in seconds or {@code 0} if the user needs to authenticate for
          *        every use of the key.
          *
-         * @see #setUserAuthenticators(Set)
+         * @see #setUserAuthenticators(int)
          *
          * @hide
          */
@@ -543,12 +462,9 @@
                     mKeyValidityForOriginationEnd,
                     mKeyValidityForConsumptionEnd,
                     mPurposes,
-                    mAlgorithm,
-                    mPadding,
-                    mDigest,
-                    mBlockMode,
-                    mMinSecondsBetweenOperations,
-                    mMaxUsesPerBoot,
+                    mPaddings,
+                    mDigests,
+                    mBlockModes,
                     mUserAuthenticators,
                     mUserAuthenticationValidityDurationSeconds,
                     mInvalidatedOnNewFingerprintEnrolled);
diff --git a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java
index c205d9d..8bf228a 100644
--- a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java
@@ -23,7 +23,6 @@
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.KeySpec;
 import java.util.Date;
-import java.util.Set;
 
 import javax.crypto.SecretKey;
 import javax.crypto.SecretKeyFactorySpi;
@@ -75,9 +74,11 @@
         int keySize;
         @KeyStoreKeyConstraints.PurposeEnum int purposes;
         @KeyStoreKeyConstraints.AlgorithmEnum int algorithm;
-        @KeyStoreKeyConstraints.PaddingEnum Integer padding;
-        @KeyStoreKeyConstraints.DigestEnum Integer digest;
-        @KeyStoreKeyConstraints.BlockModeEnum Integer blockMode;
+        @KeyStoreKeyConstraints.PaddingEnum int paddings;
+        @KeyStoreKeyConstraints.DigestEnum int digests;
+        @KeyStoreKeyConstraints.BlockModeEnum int blockModes;
+        @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators;
+        @KeyStoreKeyConstraints.UserAuthenticatorEnum int teeEnforcedUserAuthenticators;
         try {
             origin = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_ORIGIN);
             if (origin == null) {
@@ -97,18 +98,27 @@
                 throw new InvalidKeySpecException("Key algorithm not available");
             }
             algorithm = KeyStoreKeyConstraints.Algorithm.fromKeymaster(alg);
-            padding = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_PADDING);
-            if (padding != null) {
-                padding = KeyStoreKeyConstraints.Padding.fromKeymaster(padding);
-            }
-            digest = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_DIGEST);
-            if (digest != null) {
-                digest = KeyStoreKeyConstraints.Digest.fromKeymaster(digest);
-            }
-            blockMode = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_BLOCK_MODE);
-            if (blockMode != null) {
-                blockMode = KeyStoreKeyConstraints.BlockMode.fromKeymaster(blockMode);
-            }
+            paddings = KeyStoreKeyConstraints.Padding.allFromKeymaster(
+                    KeymasterUtils.getInts(keyCharacteristics, KeymasterDefs.KM_TAG_PADDING));
+            digests = KeyStoreKeyConstraints.Digest.allFromKeymaster(
+                    KeymasterUtils.getInts(keyCharacteristics, KeymasterDefs.KM_TAG_DIGEST));
+            blockModes = KeyStoreKeyConstraints.BlockMode.allFromKeymaster(
+                    KeymasterUtils.getInts(keyCharacteristics, KeymasterDefs.KM_TAG_BLOCK_MODE));
+
+            @KeyStoreKeyConstraints.UserAuthenticatorEnum
+            int swEnforcedKeymasterUserAuthenticators =
+                    keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0);
+            @KeyStoreKeyConstraints.UserAuthenticatorEnum
+            int hwEnforcedKeymasterUserAuthenticators =
+                    keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0);
+            @KeyStoreKeyConstraints.UserAuthenticatorEnum
+            int keymasterUserAuthenticators =
+                    swEnforcedKeymasterUserAuthenticators | hwEnforcedKeymasterUserAuthenticators;
+            userAuthenticators = KeyStoreKeyConstraints.UserAuthenticator.allFromKeymaster(
+                    keymasterUserAuthenticators);
+            teeEnforcedUserAuthenticators =
+                    KeyStoreKeyConstraints.UserAuthenticator.allFromKeymaster(
+                            hwEnforcedKeymasterUserAuthenticators);
         } catch (IllegalArgumentException e) {
             throw new InvalidKeySpecException("Unsupported key characteristic", e);
         }
@@ -130,17 +140,8 @@
                 && (keyValidityForConsumptionEnd.getTime() == Long.MAX_VALUE)) {
             keyValidityForConsumptionEnd = null;
         }
-
-        int swEnforcedUserAuthenticatorIds =
-                keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0);
-        int hwEnforcedUserAuthenticatorIds =
-                keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0);
-        int userAuthenticatorIds = swEnforcedUserAuthenticatorIds | hwEnforcedUserAuthenticatorIds;
-        Set<Integer> userAuthenticators =
-                KeyStoreKeyConstraints.UserAuthenticator.allFromKeymaster(userAuthenticatorIds);
-        Set<Integer> teeBackedUserAuthenticators =
-                KeyStoreKeyConstraints.UserAuthenticator.allFromKeymaster(
-                        hwEnforcedUserAuthenticatorIds);
+        Integer userAuthenticationValidityDurationSeconds =
+                KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_AUTH_TIMEOUT);
 
         // TODO: Populate the value below from key characteristics once Keymaster is ready.
         boolean invalidatedOnNewFingerprintEnrolled = false;
@@ -153,15 +154,13 @@
                 keyValidityForConsumptionEnd,
                 purposes,
                 algorithm,
-                padding,
-                digest,
-                blockMode,
-                KeymasterUtils.getInt(keyCharacteristics,
-                        KeymasterDefs.KM_TAG_MIN_SECONDS_BETWEEN_OPS),
-                KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_MAX_USES_PER_BOOT),
+                paddings,
+                digests,
+                blockModes,
                 userAuthenticators,
-                teeBackedUserAuthenticators,
-                KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_AUTH_TIMEOUT),
+                teeEnforcedUserAuthenticators,
+                ((userAuthenticationValidityDurationSeconds != null)
+                        ? userAuthenticationValidityDurationSeconds : -1),
                 invalidatedOnNewFingerprintEnrolled);
     }
 
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 65be9e1..a72faea 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -169,6 +169,10 @@
     newData->jankFrameCount += mData->jankFrameCount;
     newData->totalFrameCount >>= divider;
     newData->totalFrameCount += mData->totalFrameCount;
+    if (newData->statStartTime > mData->statStartTime
+            || newData->statStartTime == 0) {
+        newData->statStartTime = mData->statStartTime;
+    }
 
     freeData();
     mData = newData;
@@ -235,6 +239,7 @@
 }
 
 void JankTracker::dumpData(const ProfileData* data, int fd) {
+    dprintf(fd, "\nStats since: %lluns", data->statStartTime);
     dprintf(fd, "\nTotal frames rendered: %u", data->totalFrameCount);
     dprintf(fd, "\nJanky frames: %u (%.2f%%)", data->jankFrameCount,
             (float) data->jankFrameCount / (float) data->totalFrameCount * 100.0f);
@@ -252,6 +257,7 @@
     mData->frameCounts.fill(0);
     mData->totalFrameCount = 0;
     mData->jankFrameCount = 0;
+    mData->statStartTime = systemTime(CLOCK_MONOTONIC);
 }
 
 uint32_t JankTracker::findPercentile(const ProfileData* data, int percentile) {
diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h
index 4783001..3887e5e 100644
--- a/libs/hwui/JankTracker.h
+++ b/libs/hwui/JankTracker.h
@@ -44,10 +44,11 @@
 struct ProfileData {
     std::array<uint32_t, NUM_BUCKETS> jankTypeCounts;
     // See comments on kBucket* constants for what this holds
-    std::array<uint32_t, 57> frameCounts;
+    std::array<uint32_t, 55> frameCounts;
 
     uint32_t totalFrameCount;
     uint32_t jankFrameCount;
+    nsecs_t statStartTime;
 };
 
 // TODO: Replace DrawProfiler with this
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 98bfaff..4c5fb40 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1499,7 +1499,9 @@
      * @param sizeInShorts the number of shorts to read in audioData after the offset.
      * @return the number of shorts that were written or {@link #ERROR_INVALID_OPERATION}
      *    if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
-     *    the parameters don't resolve to valid data and indexes.
+     *    the parameters don't resolve to valid data and indexes, or
+     *    {@link AudioManager#ERROR_DEAD_OBJECT} if the AudioTrack is not valid anymore and
+     *    needs to be recreated.
      */
 
     public int write(short[] audioData, int offsetInShorts, int sizeInShorts) {
@@ -1559,7 +1561,9 @@
      *     queuing as much audio data for playback as possible without blocking.
      * @return the number of floats that were written, or {@link #ERROR_INVALID_OPERATION}
      *    if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
-     *    the parameters don't resolve to valid data and indexes.
+     *    the parameters don't resolve to valid data and indexes, or
+     *    {@link AudioManager#ERROR_DEAD_OBJECT} if the AudioTrack is not valid anymore and
+     *    needs to be recreated.
      */
     public int write(float[] audioData, int offsetInFloats, int sizeInFloats,
             @WriteMode int writeMode) {
@@ -1620,7 +1624,9 @@
      *     <BR>With {@link #WRITE_NON_BLOCKING}, the write will return immediately after
      *     queuing as much audio data for playback as possible without blocking.
      * @return 0 or a positive number of bytes that were written, or
-     *     {@link #ERROR_BAD_VALUE}, {@link #ERROR_INVALID_OPERATION}
+     *     {@link #ERROR_BAD_VALUE}, {@link #ERROR_INVALID_OPERATION}, or
+     *     {@link AudioManager#ERROR_DEAD_OBJECT} if the AudioTrack is not valid anymore and
+     *     needs to be recreated.
      */
     public int write(ByteBuffer audioData, int sizeInBytes,
             @WriteMode int writeMode) {
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index a7f33fa..fd7fca6 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -540,6 +540,8 @@
      * or the flags are not set properly
      * (e.g. missing {@link #CONFIGURE_FLAG_ENCODE} for an encoder).
      * @throws IllegalStateException if not in the Initialized state.
+     * @throws CryptoException upon DRM error.
+     * @throws CodecException upon codec error.
      */
     public void configure(
             MediaFormat format,
diff --git a/packages/StatementService/Android.mk b/packages/StatementService/Android.mk
new file mode 100644
index 0000000..f0adb1c
--- /dev/null
+++ b/packages/StatementService/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2015 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.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+
+LOCAL_PACKAGE_NAME := StatementService
+LOCAL_PRIVILEGED_MODULE := true
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    libprotobuf-java-nano \
+    volley
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH)/src)
diff --git a/packages/StatementService/AndroidManifest.xml b/packages/StatementService/AndroidManifest.xml
new file mode 100644
index 0000000..3ee453b
--- /dev/null
+++ b/packages/StatementService/AndroidManifest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.statementservice"
+        android:versionCode="1"
+        android:versionName="1.0">
+
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.INTENT_FILTER_VERIFICATION_AGENT"/>
+
+    <application
+            android:label="@string/service_name"
+            android:allowBackup="false">
+        <service
+                android:name=".DirectStatementService"
+                android:exported="false">
+            <intent-filter>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <action android:name="com.android.statementservice.aosp.service.CHECK_ACTION"/>
+            </intent-filter>
+        </service>
+
+        <receiver
+                android:name=".IntentFilterVerificationReceiver"
+                android:permission="android.permission.BIND_INTENT_FILTER_VERIFIER">
+            <!-- Set the priority 1 so newer implementation can have higher priority. -->
+            <intent-filter
+                    android:priority="1">
+                <action android:name="android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION"/>
+                <data android:mimeType="application/vnd.android.package-archive"/>
+            </intent-filter>
+        </receiver>
+
+    </application>
+
+</manifest>
diff --git a/packages/StatementService/proguard.flags b/packages/StatementService/proguard.flags
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/StatementService/proguard.flags
diff --git a/packages/StatementService/res/values/strings.xml b/packages/StatementService/res/values/strings.xml
new file mode 100644
index 0000000..df6d80b
--- /dev/null
+++ b/packages/StatementService/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="service_name">Intent Filter Verification Service</string>
+</resources>
diff --git a/packages/StatementService/src/com/android/statementservice/DirectStatementService.java b/packages/StatementService/src/com/android/statementservice/DirectStatementService.java
new file mode 100644
index 0000000..449738e
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/DirectStatementService.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2015 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.statementservice;
+
+import android.app.Service;
+import android.content.Intent;
+import android.net.http.HttpResponseCache;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+import com.android.statementservice.retriever.AbstractAsset;
+import com.android.statementservice.retriever.AbstractAssetMatcher;
+import com.android.statementservice.retriever.AbstractStatementRetriever;
+import com.android.statementservice.retriever.AbstractStatementRetriever.Result;
+import com.android.statementservice.retriever.AssociationServiceException;
+import com.android.statementservice.retriever.Relation;
+import com.android.statementservice.retriever.Statement;
+
+import org.json.JSONException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+/**
+ * Handles com.android.statementservice.service.CHECK_ALL_ACTION intents.
+ */
+public final class DirectStatementService extends Service {
+    private static final String TAG = DirectStatementService.class.getSimpleName();
+
+    /**
+     * Returns true if every asset in {@code SOURCE_ASSET_DESCRIPTORS} is associated with {@code
+     * EXTRA_TARGET_ASSET_DESCRIPTOR} for {@code EXTRA_RELATION} relation.
+     *
+     * <p>Takes parameter {@code EXTRA_RELATION}, {@code SOURCE_ASSET_DESCRIPTORS}, {@code
+     * EXTRA_TARGET_ASSET_DESCRIPTOR}, and {@code EXTRA_RESULT_RECEIVER}.
+     */
+    public static final String CHECK_ALL_ACTION =
+            "com.android.statementservice.service.CHECK_ALL_ACTION";
+
+    /**
+     * Parameter for {@link #CHECK_ALL_ACTION}.
+     *
+     * <p>A relation string.
+     */
+    public static final String EXTRA_RELATION =
+            "com.android.statementservice.service.RELATION";
+
+    /**
+     * Parameter for {@link #CHECK_ALL_ACTION}.
+     *
+     * <p>An array of asset descriptors in JSON.
+     */
+    public static final String EXTRA_SOURCE_ASSET_DESCRIPTORS =
+            "com.android.statementservice.service.SOURCE_ASSET_DESCRIPTORS";
+
+    /**
+     * Parameter for {@link #CHECK_ALL_ACTION}.
+     *
+     * <p>An asset descriptor in JSON.
+     */
+    public static final String EXTRA_TARGET_ASSET_DESCRIPTOR =
+            "com.android.statementservice.service.TARGET_ASSET_DESCRIPTOR";
+
+    /**
+     * Parameter for {@link #CHECK_ALL_ACTION}.
+     *
+     * <p>A {@code ResultReceiver} instance that will be used to return the result. If the request
+     * failed, return {@link #RESULT_FAIL} and an empty {@link android.os.Bundle}. Otherwise, return
+     * {@link #RESULT_SUCCESS} and a {@link android.os.Bundle} with the result stored in {@link
+     * #IS_ASSOCIATED}.
+     */
+    public static final String EXTRA_RESULT_RECEIVER =
+            "com.android.statementservice.service.RESULT_RECEIVER";
+
+    /**
+     * A boolean bundle entry that stores the result of {@link #CHECK_ALL_ACTION}.
+     * This is set only if the service returns with {@code RESULT_SUCCESS}.
+     * {@code IS_ASSOCIATED} is true if and only if {@code FAILED_SOURCES} is empty.
+     */
+    public static final String IS_ASSOCIATED = "is_associated";
+
+    /**
+     * A String ArrayList bundle entry that stores sources that can't be verified.
+     */
+    public static final String FAILED_SOURCES = "failed_sources";
+
+    /**
+     * Returned by the service if the request is successfully processed. The caller should check
+     * the {@code IS_ASSOCIATED} field to determine if the association exists or not.
+     */
+    public static final int RESULT_SUCCESS = 0;
+
+    /**
+     * Returned by the service if the request failed. The request will fail if, for example, the
+     * input is not well formed, or the network is not available.
+     */
+    public static final int RESULT_FAIL = 1;
+
+    private static final long HTTP_CACHE_SIZE_IN_BYTES = 1 * 1024 * 1024;  // 1 MBytes
+    private static final String CACHE_FILENAME = "request_cache";
+
+    private AbstractStatementRetriever mStatementRetriever;
+    private Handler mHandler;
+    private HandlerThread mThread;
+    private HttpResponseCache mHttpResponseCache;
+
+    @Override
+    public void onCreate() {
+        mThread = new HandlerThread("DirectStatementService thread",
+                android.os.Process.THREAD_PRIORITY_BACKGROUND);
+        mThread.start();
+        onCreate(AbstractStatementRetriever.createDirectRetriever(this), mThread.getLooper(),
+                getCacheDir());
+    }
+
+    /**
+     * Creates a DirectStatementService with the dependencies passed in for easy testing.
+     */
+    public void onCreate(AbstractStatementRetriever statementRetriever, Looper looper,
+                         File cacheDir) {
+        super.onCreate();
+        mStatementRetriever = statementRetriever;
+        mHandler = new Handler(looper);
+
+        try {
+            File httpCacheDir = new File(cacheDir, CACHE_FILENAME);
+            mHttpResponseCache = HttpResponseCache.install(httpCacheDir, HTTP_CACHE_SIZE_IN_BYTES);
+        } catch (IOException e) {
+            Log.i(TAG, "HTTPS response cache installation failed:" + e);
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mThread != null) {
+            mThread.quit();
+        }
+
+        try {
+            if (mHttpResponseCache != null) {
+                mHttpResponseCache.delete();
+            }
+        } catch (IOException e) {
+            Log.i(TAG, "HTTP(S) response cache deletion failed:" + e);
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        super.onStartCommand(intent, flags, startId);
+
+        if (intent == null) {
+            Log.e(TAG, "onStartCommand called with null intent");
+            return START_STICKY;
+        }
+
+        if (intent.getAction().equals(CHECK_ALL_ACTION)) {
+
+            Bundle extras = intent.getExtras();
+            List<String> sources = extras.getStringArrayList(EXTRA_SOURCE_ASSET_DESCRIPTORS);
+            String target = extras.getString(EXTRA_TARGET_ASSET_DESCRIPTOR);
+            String relation = extras.getString(EXTRA_RELATION);
+            ResultReceiver resultReceiver = extras.getParcelable(EXTRA_RESULT_RECEIVER);
+
+            if (resultReceiver == null) {
+                Log.e(TAG, " Intent does not have extra " + EXTRA_RESULT_RECEIVER);
+                return START_STICKY;
+            }
+            if (sources == null) {
+                Log.e(TAG, " Intent does not have extra " + EXTRA_SOURCE_ASSET_DESCRIPTORS);
+                resultReceiver.send(RESULT_FAIL, Bundle.EMPTY);
+                return START_STICKY;
+            }
+            if (target == null) {
+                Log.e(TAG, " Intent does not have extra " + EXTRA_TARGET_ASSET_DESCRIPTOR);
+                resultReceiver.send(RESULT_FAIL, Bundle.EMPTY);
+                return START_STICKY;
+            }
+            if (relation == null) {
+                Log.e(TAG, " Intent does not have extra " + EXTRA_RELATION);
+                resultReceiver.send(RESULT_FAIL, Bundle.EMPTY);
+                return START_STICKY;
+            }
+
+            mHandler.post(new ExceptionLoggingFutureTask<Void>(
+                    new IsAssociatedCallable(sources, target, relation, resultReceiver), TAG));
+        } else {
+            Log.e(TAG, "onStartCommand called with unsupported action: " + intent.getAction());
+        }
+        return START_STICKY;
+    }
+
+    private class IsAssociatedCallable implements Callable<Void> {
+
+        private List<String> mSources;
+        private String mTarget;
+        private String mRelation;
+        private ResultReceiver mResultReceiver;
+
+        public IsAssociatedCallable(List<String> sources, String target, String relation,
+                ResultReceiver resultReceiver) {
+            mSources = sources;
+            mTarget = target;
+            mRelation = relation;
+            mResultReceiver = resultReceiver;
+        }
+
+        private boolean verifyOneSource(AbstractAsset source, AbstractAssetMatcher target,
+                Relation relation) throws AssociationServiceException {
+            Result statements = mStatementRetriever.retrieveStatements(source);
+            for (Statement statement : statements.getStatements()) {
+                if (relation.matches(statement.getRelation())
+                        && target.matches(statement.getTarget())) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public Void call() {
+            Bundle result = new Bundle();
+            ArrayList<String> failedSources = new ArrayList<String>();
+            AbstractAssetMatcher target;
+            Relation relation;
+            try {
+                target = AbstractAssetMatcher.createMatcher(mTarget);
+                relation = Relation.create(mRelation);
+            } catch (AssociationServiceException | JSONException e) {
+                Log.e(TAG, "isAssociatedCallable failed with exception", e);
+                mResultReceiver.send(RESULT_FAIL, Bundle.EMPTY);
+                return null;
+            }
+
+            boolean allSourcesVerified = true;
+            for (String sourceString : mSources) {
+                AbstractAsset source;
+                try {
+                    source = AbstractAsset.create(sourceString);
+                } catch (AssociationServiceException e) {
+                    mResultReceiver.send(RESULT_FAIL, Bundle.EMPTY);
+                    return null;
+                }
+
+                try {
+                    if (!verifyOneSource(source, target, relation)) {
+                        failedSources.add(source.toJson());
+                        allSourcesVerified = false;
+                    }
+                } catch (AssociationServiceException e) {
+                    failedSources.add(source.toJson());
+                    allSourcesVerified = false;
+                }
+            }
+
+            result.putBoolean(IS_ASSOCIATED, allSourcesVerified);
+            result.putStringArrayList(FAILED_SOURCES, failedSources);
+            mResultReceiver.send(RESULT_SUCCESS, result);
+            return null;
+        }
+    }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/ExceptionLoggingFutureTask.java b/packages/StatementService/src/com/android/statementservice/ExceptionLoggingFutureTask.java
new file mode 100644
index 0000000..20c7f97
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/ExceptionLoggingFutureTask.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015 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.statementservice;
+
+import android.util.Log;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+
+/**
+ * {@link FutureTask} that logs unhandled exceptions.
+ */
+final class ExceptionLoggingFutureTask<V> extends FutureTask<V> {
+
+    private final String mTag;
+
+    public ExceptionLoggingFutureTask(Callable<V> callable, String tag) {
+        super(callable);
+        mTag = tag;
+    }
+
+    @Override
+    protected void done() {
+        try {
+            get();
+        } catch (ExecutionException | InterruptedException e) {
+            Log.e(mTag, "Uncaught exception.", e);
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/IntentFilterVerificationReceiver.java b/packages/StatementService/src/com/android/statementservice/IntentFilterVerificationReceiver.java
new file mode 100644
index 0000000..712347a
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/IntentFilterVerificationReceiver.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2015 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.statementservice;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.ResultReceiver;
+import android.util.Log;
+import android.util.Patterns;
+
+import com.android.statementservice.retriever.Utils;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Receives {@link Intent#ACTION_INTENT_FILTER_NEEDS_VERIFICATION} broadcast and calls
+ * {@link DirectStatementService} to verify the request. Calls
+ * {@link PackageManager#verifyIntentFilter} to notify {@link PackageManager} the result of the
+ * verification.
+ *
+ * This implementation of the API will send a HTTP request for each host specified in the query.
+ * To avoid overwhelming the network at app install time, {@code MAX_HOSTS_PER_REQUEST} limits
+ * the maximum number of hosts in a query. If a query contains more than
+ * {@code MAX_HOSTS_PER_REQUEST} hosts, it will fail immediately without making any HTTP request
+ * and call {@link PackageManager#verifyIntentFilter} with
+ * {@link PackageManager#INTENT_FILTER_VERIFICATION_FAILURE}.
+ */
+public final class IntentFilterVerificationReceiver extends BroadcastReceiver {
+    private static final String TAG = IntentFilterVerificationReceiver.class.getSimpleName();
+
+    private static final Integer MAX_HOSTS_PER_REQUEST = 10;
+
+    private static final String HANDLE_ALL_URLS_RELATION
+            = "delegate_permission/common.handle_all_urls";
+
+    private static final String ANDROID_ASSET_FORMAT = "{\"namespace\": \"android_app\", "
+            + "\"package_name\": \"%s\", \"sha256_cert_fingerprints\": [\"%s\"]}";
+    private static final String WEB_ASSET_FORMAT = "{\"namespace\": \"web\", \"site\": \"%s\"}";
+    private static final Pattern ANDROID_PACKAGE_NAME_PATTERN =
+            Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*)*$");
+    private static final String TOO_MANY_HOSTS_FORMAT =
+            "Request contains %d hosts which is more than the allowed %d.";
+
+    private static void sendErrorToPackageManager(PackageManager packageManager,
+            int verificationId) {
+        packageManager.verifyIntentFilter(verificationId,
+                PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
+                Collections.<String>emptyList());
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final String action = intent.getAction();
+        if (Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION.equals(action)) {
+            Bundle inputExtras = intent.getExtras();
+            if (inputExtras != null) {
+                Intent serviceIntent = new Intent(context, DirectStatementService.class);
+                serviceIntent.setAction(DirectStatementService.CHECK_ALL_ACTION);
+
+                int verificationId = inputExtras.getInt(
+                        PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID);
+                String scheme = inputExtras.getString(
+                        PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME);
+                String hosts = inputExtras.getString(
+                        PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS);
+                String packageName = inputExtras.getString(
+                        PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME);
+
+                Log.i(TAG, "Verify IntentFilter for " + hosts);
+
+                Bundle extras = new Bundle();
+                extras.putString(DirectStatementService.EXTRA_RELATION, HANDLE_ALL_URLS_RELATION);
+
+                String[] hostList = hosts.split(" ");
+                if (hostList.length > MAX_HOSTS_PER_REQUEST) {
+                    Log.w(TAG, String.format(TOO_MANY_HOSTS_FORMAT,
+                            hostList.length, MAX_HOSTS_PER_REQUEST));
+                    sendErrorToPackageManager(context.getPackageManager(), verificationId);
+                    return;
+                }
+
+                try {
+                    ArrayList<String> sourceAssets = new ArrayList<String>();
+                    for (String host : hostList) {
+                        sourceAssets.add(createWebAssetString(scheme, host));
+                    }
+                    extras.putStringArrayList(DirectStatementService.EXTRA_SOURCE_ASSET_DESCRIPTORS,
+                            sourceAssets);
+                } catch (MalformedURLException e) {
+                    Log.w(TAG, "Error when processing input host: " + e.getMessage());
+                    sendErrorToPackageManager(context.getPackageManager(), verificationId);
+                    return;
+                }
+                try {
+                    extras.putString(DirectStatementService.EXTRA_TARGET_ASSET_DESCRIPTOR,
+                            createAndroidAssetString(context, packageName));
+                } catch (NameNotFoundException e) {
+                    Log.w(TAG, "Error when processing input Android package: " + e.getMessage());
+                    sendErrorToPackageManager(context.getPackageManager(), verificationId);
+                    return;
+                }
+                extras.putParcelable(DirectStatementService.EXTRA_RESULT_RECEIVER,
+                        new IsAssociatedResultReceiver(
+                                new Handler(), context.getPackageManager(), verificationId));
+
+                serviceIntent.putExtras(extras);
+                context.startService(serviceIntent);
+            }
+        } else {
+            Log.w(TAG, "Intent action not supported: " + action);
+        }
+    }
+
+    private String createAndroidAssetString(Context context, String packageName)
+            throws NameNotFoundException {
+        if (!ANDROID_PACKAGE_NAME_PATTERN.matcher(packageName).matches()) {
+            throw new NameNotFoundException("Input package name is not valid.");
+        }
+
+        List<String> certFingerprints =
+                Utils.getCertFingerprintsFromPackageManager(packageName, context);
+
+        return String.format(ANDROID_ASSET_FORMAT, packageName,
+                Utils.joinStrings("\", \"", certFingerprints));
+    }
+
+    private String createWebAssetString(String scheme, String host) throws MalformedURLException {
+        if (!Patterns.DOMAIN_NAME.matcher(host).matches()) {
+            throw new MalformedURLException("Input host is not valid.");
+        }
+        if (!scheme.equals("http") && !scheme.equals("https")) {
+            throw new MalformedURLException("Input scheme is not valid.");
+        }
+
+        return String.format(WEB_ASSET_FORMAT, new URL(scheme, host, "").toString());
+    }
+
+    /**
+     * Receives the result of {@code StatementService.CHECK_ACTION} from
+     * {@link DirectStatementService} and passes it back to {@link PackageManager}.
+     */
+    private static class IsAssociatedResultReceiver extends ResultReceiver {
+
+        private final int mVerificationId;
+        private final PackageManager mPackageManager;
+
+        public IsAssociatedResultReceiver(Handler handler, PackageManager packageManager,
+                int verificationId) {
+            super(handler);
+            mVerificationId = verificationId;
+            mPackageManager = packageManager;
+        }
+
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            if (resultCode == DirectStatementService.RESULT_SUCCESS) {
+                if (resultData.getBoolean(DirectStatementService.IS_ASSOCIATED)) {
+                    mPackageManager.verifyIntentFilter(mVerificationId,
+                            PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS,
+                            Collections.<String>emptyList());
+                } else {
+                    mPackageManager.verifyIntentFilter(mVerificationId,
+                            PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
+                            resultData.getStringArrayList(DirectStatementService.FAILED_SOURCES));
+                }
+            } else {
+                sendErrorToPackageManager(mPackageManager, mVerificationId);
+            }
+        }
+    }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/AbstractAsset.java b/packages/StatementService/src/com/android/statementservice/retriever/AbstractAsset.java
new file mode 100644
index 0000000..e71cf54
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/AbstractAsset.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015 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.statementservice.retriever;
+
+/**
+ * A handle representing the identity and address of some digital asset. An asset is an online
+ * entity that typically provides some service or content. Examples of assets are websites, Android
+ * apps, Twitter feeds, and Plus Pages.
+ *
+ * <p> Asset can be represented by a JSON string. For example, the web site https://www.google.com
+ * can be represented by
+ * <pre>
+ * {"namespace": "web", "site": "https://www.google.com"}
+ * </pre>
+ *
+ * <p> The Android app with package name com.google.test that is signed by a certificate with sha256
+ * fingerprint 11:22:33 can be represented by
+ * <pre>
+ * {"namespace": "android_app",
+ *  "package_name": "com.google.test",
+ *  "sha256_cert_fingerprints": ["11:22:33"]}
+ * </pre>
+ *
+ * <p>Given a signed APK, Java 7's commandline keytool can compute the fingerprint using:
+ * {@code keytool -list -printcert -jarfile signed_app.apk}
+ */
+public abstract class AbstractAsset {
+
+    /**
+     * Returns a JSON string representation of this asset. The strings returned by this function are
+     * normalized -- they can be used for equality testing.
+     */
+    public abstract String toJson();
+
+    /**
+     * Returns a key that can be used by {@link AbstractAssetMatcher} to lookup the asset.
+     *
+     * <p> An asset will match an {@code AssetMatcher} only if the value of this method is equal to
+     * {@code AssetMatcher.getMatchedLookupKey()}.
+     */
+    public abstract int lookupKey();
+
+    /**
+     * Creates a new Asset from its JSON string representation.
+     *
+     * @throws AssociationServiceException if the assetJson is not well formatted.
+     */
+    public static AbstractAsset create(String assetJson)
+            throws AssociationServiceException {
+        return AssetFactory.create(assetJson);
+    }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/AbstractAssetMatcher.java b/packages/StatementService/src/com/android/statementservice/retriever/AbstractAssetMatcher.java
new file mode 100644
index 0000000..c35553f
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/AbstractAssetMatcher.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 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.statementservice.retriever;
+
+import org.json.JSONException;
+
+/**
+ * An asset matcher that can match asset with the given query.
+ */
+public abstract class AbstractAssetMatcher {
+
+    /**
+     * Returns true if this AssetMatcher matches the asset.
+     */
+    public abstract boolean matches(AbstractAsset asset);
+
+    /**
+     * This AssetMatcher will only match Asset with {@code lookupKey()} equal to the value returned
+     * by this method.
+     */
+    public abstract int getMatchedLookupKey();
+
+    /**
+     * Creates a new AssetMatcher from its JSON string representation.
+     *
+     * <p> For web namespace, {@code query} will match assets that have the same 'site' field.
+     *
+     * <p> For Android namespace, {@code query} will match assets that have the same
+     * 'package_name' field and have at least one common certificate fingerprint in
+     * 'sha256_cert_fingerprints' field.
+     */
+    public static AbstractAssetMatcher createMatcher(String query)
+            throws AssociationServiceException, JSONException {
+        return AssetMatcherFactory.create(query);
+    }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/AbstractStatementRetriever.java b/packages/StatementService/src/com/android/statementservice/retriever/AbstractStatementRetriever.java
new file mode 100644
index 0000000..fb30bc1
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/AbstractStatementRetriever.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2015 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.statementservice.retriever;
+
+import android.content.Context;
+import android.annotation.NonNull;
+
+import java.util.List;
+
+/**
+ * Retrieves the statements made by assets. This class is the entry point of the package.
+ * <p>
+ * An asset is an identifiable and addressable online entity that typically
+ * provides some service or content. Examples of assets are websites, Android
+ * apps, Twitter feeds, and Plus Pages.
+ * <p>
+ * Ownership of an asset is defined by being able to control it and speak for it.
+ * An asset owner may establish a relationship between the asset and another
+ * asset by making a statement about an intended relationship between the two.
+ * An example of a relationship is permission delegation. For example, the owner
+ * of a website (the webmaster) may delegate the ability the handle URLs to a
+ * particular mobile app. Relationships are considered public information.
+ * <p>
+ * A particular kind of relationship (like permission delegation) defines a binary
+ * relation on assets. The relation is not symmetric or transitive, nor is it
+ * antisymmetric or anti-transitive.
+ * <p>
+ * A statement S(r, a, b) is an assertion that the relation r holds for the
+ * ordered pair of assets (a, b). For example, taking r = "delegates permission
+ * to view user's location", a = New York Times mobile app,
+ * b = nytimes.com website, S(r, a, b) would be an assertion that "the New York
+ * Times mobile app delegates its ability to use the user's location to the
+ * nytimes.com website".
+ * <p>
+ * A statement S(r, a, b) is considered <b>reliable</b> if we have confidence that
+ * the statement is true; the exact criterion depends on the kind of statement,
+ * since some kinds of statements may be true on their face whereas others may
+ * require multiple parties to agree.
+ * <p>
+ * For example, to get the statements made by www.example.com use:
+ * <pre>
+ * result = retrieveStatements(AssetFactory.create(
+ *     "{\"namespace\": \"web\", \"site\": \"https://www.google.com\"}"))
+ * </pre>
+ * {@code result} will contain the statements and the expiration time of this result. The statements
+ * are considered reliable until the expiration time.
+ */
+public abstract class AbstractStatementRetriever {
+
+    /**
+     * Returns the statements made by the {@code source} asset with ttl.
+     *
+     * @throws AssociationServiceException if the asset namespace is not supported.
+     */
+    public abstract Result retrieveStatements(AbstractAsset source)
+            throws AssociationServiceException;
+
+    /**
+     * The retrieved statements and the expiration date.
+     */
+    public interface Result {
+
+        /**
+         * @return the retrieved statements.
+         */
+        @NonNull
+        public List<Statement> getStatements();
+
+        /**
+         * @return the expiration time in millisecond.
+         */
+        public long getExpireMillis();
+    }
+
+    /**
+     * Creates a new StatementRetriever that directly retrieves statements from the asset.
+     *
+     * <p> For web assets, {@link AbstractStatementRetriever} will try to retrieve the statement
+     * file from URL: {@code [webAsset.site]/.well-known/associations.json"} where {@code
+     * [webAsset.site]} is in the form {@code http{s}://[hostname]:[optional_port]}. The file
+     * should contain one JSON array of statements.
+     *
+     * <p> For Android assets, {@link AbstractStatementRetriever} will try to retrieve the statement
+     * from the AndroidManifest.xml. The developer should add a {@code meta-data} tag under
+     * {@code application} tag where attribute {@code android:name} equals "associated_assets"
+     * and {@code android:recourse} points to a string array resource. Each entry in the string
+     * array should contain exactly one statement in JSON format. Note that this implementation
+     * can only return statements made by installed apps.
+     */
+    public static AbstractStatementRetriever createDirectRetriever(Context context) {
+        return new DirectStatementRetriever(new URLFetcher(),
+                new AndroidPackageInfoFetcher(context));
+    }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/AndroidAppAsset.java b/packages/StatementService/src/com/android/statementservice/retriever/AndroidAppAsset.java
new file mode 100644
index 0000000..0c96038
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/AndroidAppAsset.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2015 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.statementservice.retriever;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Immutable value type that names an Android app asset.
+ *
+ * <p>An Android app can be named by its package name and certificate fingerprints using this JSON
+ * string: { "namespace": "android_app", "package_name": "[Java package name]",
+ * "sha256_cert_fingerprints": ["[SHA256 fingerprint of signing cert]", "[additional cert]", ...] }
+ *
+ * <p>For example, { "namespace": "android_app", "package_name": "com.test.mytestapp",
+ * "sha256_cert_fingerprints": ["24:D9:B4:57:A6:42:FB:E6:E5:B8:D6:9E:7B:2D:C2:D1:CB:D1:77:17:1D:7F:D4:A9:16:10:11:AB:92:B9:8F:3F"]
+ * }
+ *
+ * <p>Given a signed APK, Java 7's commandline keytool can compute the fingerprint using:
+ * {@code keytool -list -printcert -jarfile signed_app.apk}
+ *
+ * <p>Each entry in "sha256_cert_fingerprints" is a colon-separated hex string (e.g. 14:6D:E9:...)
+ * representing the certificate SHA-256 fingerprint.
+ */
+/* package private */ final class AndroidAppAsset extends AbstractAsset {
+
+    private static final String MISSING_FIELD_FORMAT_STRING = "Expected %s to be set.";
+    private static final String MISSING_APPCERTS_FORMAT_STRING =
+            "Expected %s to be non-empty array.";
+    private static final String APPCERT_NOT_STRING_FORMAT_STRING = "Expected all %s to be strings.";
+
+    private final List<String> mCertFingerprints;
+    private final String mPackageName;
+
+    public List<String> getCertFingerprints() {
+        return Collections.unmodifiableList(mCertFingerprints);
+    }
+
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    @Override
+    public String toJson() {
+        AssetJsonWriter writer = new AssetJsonWriter();
+
+        writer.writeFieldLower(Utils.NAMESPACE_FIELD, Utils.NAMESPACE_ANDROID_APP);
+        writer.writeFieldLower(Utils.ANDROID_APP_ASSET_FIELD_PACKAGE_NAME, mPackageName);
+        writer.writeArrayUpper(Utils.ANDROID_APP_ASSET_FIELD_CERT_FPS, mCertFingerprints);
+
+        return writer.closeAndGetString();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder asset = new StringBuilder();
+        asset.append("AndroidAppAsset: ");
+        asset.append(toJson());
+        return asset.toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof AndroidAppAsset)) {
+            return false;
+        }
+
+        return ((AndroidAppAsset) o).toJson().equals(toJson());
+    }
+
+    @Override
+    public int hashCode() {
+        return toJson().hashCode();
+    }
+
+    @Override
+    public int lookupKey() {
+        return getPackageName().hashCode();
+    }
+
+    /**
+     * Checks that the input is a valid Android app asset.
+     *
+     * @param asset a JSONObject that has "namespace", "package_name", and
+     *              "sha256_cert_fingerprints" fields.
+     * @throws AssociationServiceException if the asset is not well formatted.
+     */
+    public static AndroidAppAsset create(JSONObject asset)
+            throws AssociationServiceException {
+        String packageName = asset.optString(Utils.ANDROID_APP_ASSET_FIELD_PACKAGE_NAME);
+        if (packageName.equals("")) {
+            throw new AssociationServiceException(String.format(MISSING_FIELD_FORMAT_STRING,
+                    Utils.ANDROID_APP_ASSET_FIELD_PACKAGE_NAME));
+        }
+
+        JSONArray certArray = asset.optJSONArray(Utils.ANDROID_APP_ASSET_FIELD_CERT_FPS);
+        if (certArray == null || certArray.length() == 0) {
+            throw new AssociationServiceException(
+                    String.format(MISSING_APPCERTS_FORMAT_STRING,
+                            Utils.ANDROID_APP_ASSET_FIELD_CERT_FPS));
+        }
+        List<String> certFingerprints = new ArrayList<>(certArray.length());
+        for (int i = 0; i < certArray.length(); i++) {
+            try {
+                certFingerprints.add(certArray.getString(i));
+            } catch (JSONException e) {
+                throw new AssociationServiceException(
+                        String.format(APPCERT_NOT_STRING_FORMAT_STRING,
+                                Utils.ANDROID_APP_ASSET_FIELD_CERT_FPS));
+            }
+        }
+
+        return new AndroidAppAsset(packageName, certFingerprints);
+    }
+
+    /**
+     * Creates a new AndroidAppAsset.
+     *
+     * @param packageName the package name of the Android app.
+     * @param certFingerprints at least one of the Android app signing certificate sha-256
+     *                         fingerprint.
+     */
+    public static AndroidAppAsset create(String packageName, List<String> certFingerprints) {
+        if (packageName == null || packageName.equals("")) {
+            throw new AssertionError("Expected packageName to be set.");
+        }
+        if (certFingerprints == null || certFingerprints.size() == 0) {
+            throw new AssertionError("Expected certFingerprints to be set.");
+        }
+        List<String> lowerFps = new ArrayList<String>(certFingerprints.size());
+        for (String fp : certFingerprints) {
+            lowerFps.add(fp.toUpperCase(Locale.US));
+        }
+        return new AndroidAppAsset(packageName, lowerFps);
+    }
+
+    private AndroidAppAsset(String packageName, List<String> certFingerprints) {
+        if (packageName.equals("")) {
+            mPackageName = null;
+        } else {
+            mPackageName = packageName;
+        }
+
+        if (certFingerprints == null || certFingerprints.size() == 0) {
+            mCertFingerprints = null;
+        } else {
+            mCertFingerprints = Collections.unmodifiableList(sortAndDeDuplicate(certFingerprints));
+        }
+    }
+
+    /**
+     * Returns an ASCII-sorted copy of the list of certs with all duplicates removed.
+     */
+    private List<String> sortAndDeDuplicate(List<String> certs) {
+        if (certs.size() <= 1) {
+            return certs;
+        }
+        HashSet<String> set = new HashSet<>(certs);
+        List<String> result = new ArrayList<>(set);
+        Collections.sort(result);
+        return result;
+    }
+
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/AndroidAppAssetMatcher.java b/packages/StatementService/src/com/android/statementservice/retriever/AndroidAppAssetMatcher.java
new file mode 100644
index 0000000..8a9d838
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/AndroidAppAssetMatcher.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2015 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.statementservice.retriever;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Match assets that have the same 'package_name' field and have at least one common certificate
+ * fingerprint in 'sha256_cert_fingerprints' field.
+ */
+/* package private */ final class AndroidAppAssetMatcher extends AbstractAssetMatcher {
+
+    private final AndroidAppAsset mQuery;
+
+    public AndroidAppAssetMatcher(AndroidAppAsset query) {
+        mQuery = query;
+    }
+
+    @Override
+    public boolean matches(AbstractAsset asset) {
+        if (asset instanceof AndroidAppAsset) {
+            AndroidAppAsset androidAppAsset = (AndroidAppAsset) asset;
+            if (!androidAppAsset.getPackageName().equals(mQuery.getPackageName())) {
+                return false;
+            }
+
+            Set<String> certs = new HashSet<String>(mQuery.getCertFingerprints());
+            for (String cert : androidAppAsset.getCertFingerprints()) {
+                if (certs.contains(cert)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public int getMatchedLookupKey() {
+        return mQuery.lookupKey();
+    }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/AndroidPackageInfoFetcher.java b/packages/StatementService/src/com/android/statementservice/retriever/AndroidPackageInfoFetcher.java
new file mode 100644
index 0000000..1000c4c
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/AndroidPackageInfoFetcher.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 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.statementservice.retriever;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources.NotFoundException;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Class that provides information about an android app from {@link PackageManager}.
+ *
+ * Visible for testing.
+ *
+ * @hide
+ */
+public class AndroidPackageInfoFetcher {
+
+    /**
+     * The name of the metadata tag in AndroidManifest.xml that stores the associated asset array
+     * ID. The metadata tag should use the android:resource attribute to point to an array resource
+     * that contains the associated assets.
+     */
+    private static final String ASSOCIATED_ASSETS_KEY = "associated_assets";
+
+    private Context mContext;
+
+    public AndroidPackageInfoFetcher(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Returns the Sha-256 fingerprints of all certificates from the specified package as a list of
+     * upper case HEX Strings with bytes separated by colons. Given an app {@link
+     * android.content.pm.Signature}, the fingerprint can be computed as {@link
+     * Utils#computeNormalizedSha256Fingerprint} {@code(signature.toByteArray())}.
+     *
+     * <p>Given a signed APK, Java 7's commandline keytool can compute the fingerprint using: {@code
+     * keytool -list -printcert -jarfile signed_app.apk}
+     *
+     * <p>Example: "10:39:38:EE:45:37:E5:9E:8E:E7:92:F6:54:50:4F:B8:34:6F:C6:B3:46:D0:BB:C4:41:5F:C3:39:FC:FC:8E:C1"
+     *
+     * @throws NameNotFoundException if an app with packageName is not installed on the device.
+     */
+    public List<String> getCertFingerprints(String packageName) throws NameNotFoundException {
+        return Utils.getCertFingerprintsFromPackageManager(packageName, mContext);
+    }
+
+    /**
+     * Returns all statements that the specified package makes in its AndroidManifest.xml.
+     *
+     * @throws NameNotFoundException if the app is not installed on the device.
+     */
+    public List<String> getStatements(String packageName) throws NameNotFoundException {
+        PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(
+                packageName, PackageManager.GET_META_DATA);
+        ApplicationInfo appInfo = packageInfo.applicationInfo;
+        if (appInfo.metaData == null) {
+            return Collections.<String>emptyList();
+        }
+        int tokenResourceId = appInfo.metaData.getInt(ASSOCIATED_ASSETS_KEY);
+        if (tokenResourceId == 0) {
+            return Collections.<String>emptyList();
+        }
+        try {
+            return Arrays.asList(
+                    mContext.getPackageManager().getResourcesForApplication(packageName)
+                    .getStringArray(tokenResourceId));
+        } catch (NotFoundException e) {
+            return Collections.<String>emptyList();
+        }
+    }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/AssetFactory.java b/packages/StatementService/src/com/android/statementservice/retriever/AssetFactory.java
new file mode 100644
index 0000000..762365e
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/AssetFactory.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015 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.statementservice.retriever;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Factory to create asset from JSON string.
+ */
+/* package private */ final class AssetFactory {
+
+    private static final String FIELD_NOT_STRING_FORMAT_STRING = "Expected %s to be string.";
+
+    private AssetFactory() {}
+
+    /**
+     * Creates a new Asset object from its JSON string representation.
+     *
+     * @throws AssociationServiceException if the assetJson is not well formatted.
+     */
+    public static AbstractAsset create(String assetJson) throws AssociationServiceException {
+        try {
+            return create(new JSONObject(assetJson));
+        } catch (JSONException e) {
+            throw new AssociationServiceException(
+                    "Input is not a well formatted asset descriptor.");
+        }
+    }
+
+    /**
+     * Checks that the input is a valid asset with purposes.
+     *
+     * @throws AssociationServiceException if the asset is not well formatted.
+     */
+    private static AbstractAsset create(JSONObject asset)
+            throws AssociationServiceException {
+        String namespace = asset.optString(Utils.NAMESPACE_FIELD, null);
+        if (namespace == null) {
+            throw new AssociationServiceException(String.format(
+                    FIELD_NOT_STRING_FORMAT_STRING, Utils.NAMESPACE_FIELD));
+        }
+
+        if (namespace.equals(Utils.NAMESPACE_WEB)) {
+            return WebAsset.create(asset);
+        } else if (namespace.equals(Utils.NAMESPACE_ANDROID_APP)) {
+            return AndroidAppAsset.create(asset);
+        } else {
+            throw new AssociationServiceException("Namespace " + namespace + " is not supported.");
+        }
+    }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/AssetJsonWriter.java b/packages/StatementService/src/com/android/statementservice/retriever/AssetJsonWriter.java
new file mode 100644
index 0000000..080e45a
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/AssetJsonWriter.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2015 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.statementservice.retriever;
+
+import android.util.JsonWriter;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Creates a Json string where the order of the fields can be specified.
+ */
+/* package private */ final class AssetJsonWriter {
+
+    private StringWriter mStringWriter = new StringWriter();
+    private JsonWriter mWriter;
+    private boolean mClosed = false;
+
+    public AssetJsonWriter() {
+        mWriter = new JsonWriter(mStringWriter);
+        try {
+            mWriter.beginObject();
+        } catch (IOException e) {
+            throw new AssertionError("Unreachable exception.");
+        }
+    }
+
+    /**
+     * Appends a field to the output, putting both the key and value in lowercase. Null values are
+     * not written.
+     */
+    public void writeFieldLower(String key, String value) {
+        if (mClosed) {
+            throw new IllegalArgumentException(
+                    "Cannot write to an object that has already been closed.");
+        }
+
+        if (value != null) {
+            try {
+                mWriter.name(key.toLowerCase(Locale.US));
+                mWriter.value(value.toLowerCase(Locale.US));
+            } catch (IOException e) {
+                throw new AssertionError("Unreachable exception.");
+            }
+        }
+    }
+
+    /**
+     * Appends an array to the output, putting both the key and values in lowercase. If {@code
+     * values} is null, this field will not be written. Individual values in the list must not be
+     * null.
+     */
+    public void writeArrayUpper(String key, List<String> values) {
+        if (mClosed) {
+            throw new IllegalArgumentException(
+                    "Cannot write to an object that has already been closed.");
+        }
+
+        if (values != null) {
+            try {
+                mWriter.name(key.toLowerCase(Locale.US));
+                mWriter.beginArray();
+                for (String value : values) {
+                    mWriter.value(value.toUpperCase(Locale.US));
+                }
+                mWriter.endArray();
+            } catch (IOException e) {
+                throw new AssertionError("Unreachable exception.");
+            }
+        }
+    }
+
+    /**
+     * Returns the string representation of the constructed json. After calling this method, {@link
+     * #writeFieldLower} can no longer be called.
+     */
+    public String closeAndGetString() {
+        if (!mClosed) {
+            try {
+                mWriter.endObject();
+            } catch (IOException e) {
+                throw new AssertionError("Unreachable exception.");
+            }
+            mClosed = true;
+        }
+        return mStringWriter.toString();
+    }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/AssetMatcherFactory.java b/packages/StatementService/src/com/android/statementservice/retriever/AssetMatcherFactory.java
new file mode 100644
index 0000000..1a50757
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/AssetMatcherFactory.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 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.statementservice.retriever;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Factory to create asset matcher from JSON string.
+ */
+/* package private */ final class AssetMatcherFactory {
+
+    private static final String FIELD_NOT_STRING_FORMAT_STRING = "Expected %s to be string.";
+    private static final String NAMESPACE_NOT_SUPPORTED_STRING = "Namespace %s is not supported.";
+
+    public static AbstractAssetMatcher create(String query) throws AssociationServiceException,
+            JSONException {
+        JSONObject queryObject = new JSONObject(query);
+
+        String namespace = queryObject.optString(Utils.NAMESPACE_FIELD, null);
+        if (namespace == null) {
+            throw new AssociationServiceException(String.format(
+                    FIELD_NOT_STRING_FORMAT_STRING, Utils.NAMESPACE_FIELD));
+        }
+
+        if (namespace.equals(Utils.NAMESPACE_WEB)) {
+            return new WebAssetMatcher(WebAsset.create(queryObject));
+        } else if (namespace.equals(Utils.NAMESPACE_ANDROID_APP)) {
+            return new AndroidAppAssetMatcher(AndroidAppAsset.create(queryObject));
+        } else {
+            throw new AssociationServiceException(
+                    String.format(NAMESPACE_NOT_SUPPORTED_STRING, namespace));
+        }
+    }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/AssociationServiceException.java b/packages/StatementService/src/com/android/statementservice/retriever/AssociationServiceException.java
new file mode 100644
index 0000000..d6e49c2
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/AssociationServiceException.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 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.statementservice.retriever;
+
+/**
+ * Thrown when an error occurs in the Association Service.
+ */
+public class AssociationServiceException extends Exception {
+
+    public AssociationServiceException(String msg) {
+        super(msg);
+    }
+
+    public AssociationServiceException(String msg, Exception e) {
+        super(msg, e);
+    }
+
+    public AssociationServiceException(Exception e) {
+        super(e);
+    }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/DirectStatementRetriever.java b/packages/StatementService/src/com/android/statementservice/retriever/DirectStatementRetriever.java
new file mode 100644
index 0000000..3ad71c4
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/DirectStatementRetriever.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2015 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.statementservice.retriever;
+
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.util.Log;
+
+import org.json.JSONException;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * An implementation of {@link AbstractStatementRetriever} that directly retrieves statements from
+ * the asset.
+ */
+/* package private */ final class DirectStatementRetriever extends AbstractStatementRetriever {
+
+    private static final long DO_NOT_CACHE_RESULT = 0L;
+    private static final int HTTP_CONNECTION_TIMEOUT_MILLIS = 5000;
+    private static final long HTTP_CONTENT_SIZE_LIMIT_IN_BYTES = 1024 * 1024;
+    private static final int MAX_INCLUDE_LEVEL = 1;
+    private static final String WELL_KNOWN_STATEMENT_PATH = "/.well-known/associations.json";
+
+    private final URLFetcher mUrlFetcher;
+    private final AndroidPackageInfoFetcher mAndroidFetcher;
+
+    /**
+     * An immutable value type representing the retrieved statements and the expiration date.
+     */
+    public static class Result implements AbstractStatementRetriever.Result {
+
+        private final List<Statement> mStatements;
+        private final Long mExpireMillis;
+
+        @Override
+        public List<Statement> getStatements() {
+            return mStatements;
+        }
+
+        @Override
+        public long getExpireMillis() {
+            return mExpireMillis;
+        }
+
+        private Result(List<Statement> statements, Long expireMillis) {
+            mStatements = statements;
+            mExpireMillis = expireMillis;
+        }
+
+        public static Result create(List<Statement> statements, Long expireMillis) {
+            return new Result(statements, expireMillis);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder result = new StringBuilder();
+            result.append("Result: ");
+            result.append(mStatements.toString());
+            result.append(", mExpireMillis=");
+            result.append(mExpireMillis);
+            return result.toString();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            Result result = (Result) o;
+
+            if (!mExpireMillis.equals(result.mExpireMillis)) {
+                return false;
+            }
+            if (!mStatements.equals(result.mStatements)) {
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = mStatements.hashCode();
+            result = 31 * result + mExpireMillis.hashCode();
+            return result;
+        }
+    }
+
+    public DirectStatementRetriever(URLFetcher urlFetcher,
+                                    AndroidPackageInfoFetcher androidFetcher) {
+        this.mUrlFetcher = urlFetcher;
+        this.mAndroidFetcher = androidFetcher;
+    }
+
+    @Override
+    public Result retrieveStatements(AbstractAsset source) throws AssociationServiceException {
+        if (source instanceof AndroidAppAsset) {
+            return retrieveFromAndroid((AndroidAppAsset) source);
+        } else if (source instanceof WebAsset) {
+            return retrieveFromWeb((WebAsset) source);
+        } else {
+            throw new AssociationServiceException("Namespace is not supported.");
+        }
+    }
+
+    private String computeAssociationJsonUrl(WebAsset asset) {
+        try {
+            return new URL(asset.getScheme(), asset.getDomain(), asset.getPort(),
+                    WELL_KNOWN_STATEMENT_PATH)
+                    .toExternalForm();
+        } catch (MalformedURLException e) {
+            throw new AssertionError("Invalid domain name in database.");
+        }
+    }
+
+    private Result retrieveStatementFromUrl(String url, int maxIncludeLevel, AbstractAsset source)
+            throws AssociationServiceException {
+        List<Statement> statements = new ArrayList<Statement>();
+        if (maxIncludeLevel < 0) {
+            return Result.create(statements, DO_NOT_CACHE_RESULT);
+        }
+
+        WebContent webContent;
+        try {
+            webContent = mUrlFetcher.getWebContentFromUrl(new URL(url),
+                    HTTP_CONTENT_SIZE_LIMIT_IN_BYTES, HTTP_CONNECTION_TIMEOUT_MILLIS);
+        } catch (IOException e) {
+            return Result.create(statements, DO_NOT_CACHE_RESULT);
+        }
+
+        try {
+            ParsedStatement result = StatementParser
+                    .parseStatementList(webContent.getContent(), source);
+            statements.addAll(result.getStatements());
+            for (String delegate : result.getDelegates()) {
+                statements.addAll(
+                        retrieveStatementFromUrl(delegate, maxIncludeLevel - 1, source)
+                                .getStatements());
+            }
+            return Result.create(statements, webContent.getExpireTimeMillis());
+        } catch (JSONException e) {
+            return Result.create(statements, DO_NOT_CACHE_RESULT);
+        }
+    }
+
+    private Result retrieveFromWeb(WebAsset asset)
+            throws AssociationServiceException {
+        return retrieveStatementFromUrl(computeAssociationJsonUrl(asset), MAX_INCLUDE_LEVEL, asset);
+    }
+
+    private Result retrieveFromAndroid(AndroidAppAsset asset) throws AssociationServiceException {
+        try {
+            List<String> delegates = new ArrayList<String>();
+            List<Statement> statements = new ArrayList<Statement>();
+
+            List<String> certFps = mAndroidFetcher.getCertFingerprints(asset.getPackageName());
+            if (!Utils.hasCommonString(certFps, asset.getCertFingerprints())) {
+                throw new AssociationServiceException(
+                        "Specified certs don't match the installed app.");
+            }
+
+            AndroidAppAsset actualSource = AndroidAppAsset.create(asset.getPackageName(), certFps);
+            for (String statementJson : mAndroidFetcher.getStatements(asset.getPackageName())) {
+                ParsedStatement result =
+                        StatementParser.parseStatement(statementJson, actualSource);
+                statements.addAll(result.getStatements());
+                delegates.addAll(result.getDelegates());
+            }
+
+            for (String delegate : delegates) {
+                statements.addAll(retrieveStatementFromUrl(delegate, MAX_INCLUDE_LEVEL,
+                        actualSource).getStatements());
+            }
+
+            return Result.create(statements, DO_NOT_CACHE_RESULT);
+        } catch (JSONException | NameNotFoundException e) {
+            Log.w(DirectStatementRetriever.class.getSimpleName(), e);
+            return Result.create(Collections.<Statement>emptyList(), DO_NOT_CACHE_RESULT);
+        }
+    }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/ParsedStatement.java b/packages/StatementService/src/com/android/statementservice/retriever/ParsedStatement.java
new file mode 100644
index 0000000..9446e66
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/ParsedStatement.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 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.statementservice.retriever;
+
+import java.util.List;
+
+/**
+ * A class that stores a list of statement and/or a list of delegate url.
+ */
+/* package private */ final class ParsedStatement {
+
+    private final List<Statement> mStatements;
+    private final List<String> mDelegates;
+
+    public ParsedStatement(List<Statement> statements, List<String> delegates) {
+        this.mStatements = statements;
+        this.mDelegates = delegates;
+    }
+
+    public List<Statement> getStatements() {
+        return mStatements;
+    }
+
+    public List<String> getDelegates() {
+        return mDelegates;
+    }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/Relation.java b/packages/StatementService/src/com/android/statementservice/retriever/Relation.java
new file mode 100644
index 0000000..91218c6
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/Relation.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2015 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.statementservice.retriever;
+
+import android.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+/**
+ * An immutable value type representing a statement relation with "kind" and "detail".
+ *
+ * <p> The set of kinds is enumerated by the API: <ul> <li> <b>delegate_permission</b>: The detail
+ * field specifies which permission to delegate. A statement involving this relation does not
+ * constitute a requirement to do the delegation, just a permission to do so. </ul>
+ *
+ * <p> We may add other kinds in the future.
+ *
+ * <p> The detail field is a lowercase alphanumeric string with underscores and periods allowed
+ * (matching the regex [a-z0-9_.]+), but otherwise unstructured. It is also possible to specify '*'
+ * (the wildcard character) as the detail if the relation applies to any detail in the specified
+ * kind.
+ */
+public final class Relation {
+
+    private static final Pattern KIND_PATTERN = Pattern.compile("^[a-z0-9_.]+$");
+    private static final Pattern DETAIL_PATTERN = Pattern.compile("^([a-z0-9_.]+|[*])$");
+
+    private static final String MATCH_ALL_DETAILS = "*";
+
+    private final String mKind;
+    private final String mDetail;
+
+    private Relation(String kind, String detail) {
+        mKind = kind;
+        mDetail = detail;
+    }
+
+    /**
+     * Returns the relation's kind.
+     */
+    @NonNull
+    public String getKind() {
+        return mKind;
+    }
+
+    /**
+     * Returns the relation's detail.
+     */
+    @NonNull
+    public String getDetail() {
+        return mDetail;
+    }
+
+    /**
+     * Creates a new Relation object for the specified {@code kind} and {@code detail}.
+     *
+     * @throws AssociationServiceException if {@code kind} or {@code detail} is not well formatted.
+     */
+    public static Relation create(@NonNull String kind, @NonNull String detail)
+            throws AssociationServiceException {
+        if (!KIND_PATTERN.matcher(kind).matches() || !DETAIL_PATTERN.matcher(detail).matches()) {
+            throw new AssociationServiceException("Relation not well formatted.");
+        }
+        return new Relation(kind, detail);
+    }
+
+    /**
+     * Creates a new Relation object from its string representation.
+     *
+     * @throws AssociationServiceException if the relation is not well formatted.
+     */
+    public static Relation create(@NonNull String relation) throws AssociationServiceException {
+        String[] r = relation.split("/", 2);
+        if (r.length != 2) {
+            throw new AssociationServiceException("Relation not well formatted.");
+        }
+        return create(r[0], r[1]);
+    }
+
+    /**
+     * Returns true if {@code relation} has the same kind and detail. If {@code
+     * relation.getDetail()} is wildcard (*) then returns true if the kind is the same.
+     */
+    public boolean matches(Relation relation) {
+        return getKind().equals(relation.getKind()) && (getDetail().equals(MATCH_ALL_DETAILS)
+                || getDetail().equals(relation.getDetail()));
+    }
+
+    /**
+     * Returns a string representation of this relation.
+     */
+    @Override
+    public String toString() {
+        StringBuilder relation = new StringBuilder();
+        relation.append(getKind());
+        relation.append("/");
+        relation.append(getDetail());
+        return relation.toString();
+    }
+
+    // equals() and hashCode() are generated by Android Studio.
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        Relation relation = (Relation) o;
+
+        if (mDetail != null ? !mDetail.equals(relation.mDetail) : relation.mDetail != null) {
+            return false;
+        }
+        if (mKind != null ? !mKind.equals(relation.mKind) : relation.mKind != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mKind != null ? mKind.hashCode() : 0;
+        result = 31 * result + (mDetail != null ? mDetail.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/Statement.java b/packages/StatementService/src/com/android/statementservice/retriever/Statement.java
new file mode 100644
index 0000000..f83edaf
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/Statement.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2015 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.statementservice.retriever;
+
+import android.annotation.NonNull;
+
+/**
+ * An immutable value type representing a statement, consisting of a source, target, and relation.
+ * This reflects an assertion that the relation holds for the source, target pair. For example, if a
+ * web site has the following in its associations.json file:
+ *
+ * <pre>
+ * {
+ * "relation": ["delegate_permission/common.handle_all_urls"],
+ * "target"  : {"namespace": "android_app", "package_name": "com.example.app",
+ *              "sha256_cert_fingerprints": ["00:11:22:33"] }
+ * }
+ * </pre>
+ *
+ * Then invoking {@link AbstractStatementRetriever#retrieveStatements(AbstractAsset)} will return a
+ * {@link Statement} with {@link #getSource} equal to the input parameter, {@link #getRelation}
+ * equal to
+ *
+ * <pre>Relation.create("delegate_permission", "common.get_login_creds");</pre>
+ *
+ * and with {@link #getTarget} equal to
+ *
+ * <pre>AbstractAsset.create("{\"namespace\" : \"android_app\","
+ *                           + "\"package_name\": \"com.example.app\"}"
+ *                           + "\"sha256_cert_fingerprints\": \"[\"00:11:22:33\"]\"}");
+ * </pre>
+ */
+public final class Statement {
+
+    private final AbstractAsset mTarget;
+    private final Relation mRelation;
+    private final AbstractAsset mSource;
+
+    private Statement(AbstractAsset source, AbstractAsset target, Relation relation) {
+        mSource = source;
+        mTarget = target;
+        mRelation = relation;
+    }
+
+    /**
+     * Returns the source asset of the statement.
+     */
+    @NonNull
+    public AbstractAsset getSource() {
+        return mSource;
+    }
+
+    /**
+     * Returns the target asset of the statement.
+     */
+    @NonNull
+    public AbstractAsset getTarget() {
+        return mTarget;
+    }
+
+    /**
+     * Returns the relation of the statement.
+     */
+    @NonNull
+    public Relation getRelation() {
+        return mRelation;
+    }
+
+    /**
+     * Creates a new Statement object for the specified target asset and relation. For example:
+     * <pre>
+     *   Asset asset = Asset.Factory.create(
+     *       "{\"namespace\" : \"web\",\"site\": \"https://www.test.com\"}");
+     *   Relation relation = Relation.create("delegate_permission", "common.get_login_creds");
+     *   Statement statement = Statement.create(asset, relation);
+     * </pre>
+     */
+    public static Statement create(@NonNull AbstractAsset source, @NonNull AbstractAsset target,
+                                   @NonNull Relation relation) {
+        return new Statement(source, target, relation);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        Statement statement = (Statement) o;
+
+        if (!mRelation.equals(statement.mRelation)) {
+            return false;
+        }
+        if (!mTarget.equals(statement.mTarget)) {
+            return false;
+        }
+        if (!mSource.equals(statement.mSource)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mTarget.hashCode();
+        result = 31 * result + mRelation.hashCode();
+        result = 31 * result + mSource.hashCode();
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder statement = new StringBuilder();
+        statement.append("Statement: ");
+        statement.append(mSource);
+        statement.append(", ");
+        statement.append(mTarget);
+        statement.append(", ");
+        statement.append(mRelation);
+        return statement.toString();
+    }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/StatementParser.java b/packages/StatementService/src/com/android/statementservice/retriever/StatementParser.java
new file mode 100644
index 0000000..bcd91bd
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/StatementParser.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2015 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.statementservice.retriever;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class that parses JSON-formatted statements.
+ */
+/* package private */ final class StatementParser {
+
+    /**
+     * Parses a JSON array of statements.
+     */
+    static ParsedStatement parseStatementList(String statementList, AbstractAsset source)
+            throws JSONException, AssociationServiceException {
+        List<Statement> statements = new ArrayList<Statement>();
+        List<String> delegates = new ArrayList<String>();
+
+        JSONArray statementsJson = new JSONArray(statementList);
+        for (int i = 0; i < statementsJson.length(); i++) {
+            ParsedStatement result = parseStatement(statementsJson.getString(i), source);
+            statements.addAll(result.getStatements());
+            delegates.addAll(result.getDelegates());
+        }
+
+        return new ParsedStatement(statements, delegates);
+    }
+
+    /**
+     * Parses a single JSON statement.
+     */
+    static ParsedStatement parseStatement(String statementString, AbstractAsset source)
+            throws JSONException, AssociationServiceException {
+        List<Statement> statements = new ArrayList<Statement>();
+        List<String> delegates = new ArrayList<String>();
+        JSONObject statement = new JSONObject(statementString);
+        if (statement.optString(Utils.DELEGATE_FIELD_DELEGATE, null) != null) {
+            delegates.add(statement.optString(Utils.DELEGATE_FIELD_DELEGATE));
+        } else {
+            AbstractAsset target = AssetFactory
+                    .create(statement.getString(Utils.ASSET_DESCRIPTOR_FIELD_TARGET));
+            JSONArray relations = statement.getJSONArray(
+                    Utils.ASSET_DESCRIPTOR_FIELD_RELATION);
+            for (int i = 0; i < relations.length(); i++) {
+                statements.add(Statement
+                        .create(source, target, Relation.create(relations.getString(i))));
+            }
+        }
+
+        return new ParsedStatement(statements, delegates);
+    }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/URLFetcher.java b/packages/StatementService/src/com/android/statementservice/retriever/URLFetcher.java
new file mode 100644
index 0000000..4828ff9
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/URLFetcher.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2015 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.statementservice.retriever;
+
+import com.android.volley.Cache;
+import com.android.volley.NetworkResponse;
+import com.android.volley.toolbox.HttpHeaderParser;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Helper class for fetching HTTP or HTTPS URL.
+ *
+ * Visible for testing.
+ *
+ * @hide
+ */
+public class URLFetcher {
+
+    private static final long DO_NOT_CACHE_RESULT = 0L;
+    private static final int INPUT_BUFFER_SIZE_IN_BYTES = 1024;
+
+    /**
+     * Fetches the specified url and returns the content and ttl.
+     *
+     * @throws IOException if it can't retrieve the content due to a network problem.
+     * @throws AssociationServiceException if the URL scheme is not http or https or the content
+     * length exceeds {code fileSizeLimit}.
+     */
+    public WebContent getWebContentFromUrl(URL url, long fileSizeLimit, int connectionTimeoutMillis)
+            throws AssociationServiceException, IOException {
+        final String scheme = url.getProtocol().toLowerCase(Locale.US);
+        if (!scheme.equals("http") && !scheme.equals("https")) {
+            throw new IllegalArgumentException("The url protocol should be on http or https.");
+        }
+
+        HttpURLConnection connection;
+        connection = (HttpURLConnection) url.openConnection();
+        connection.setInstanceFollowRedirects(true);
+        connection.setConnectTimeout(connectionTimeoutMillis);
+        connection.setReadTimeout(connectionTimeoutMillis);
+        connection.setUseCaches(true);
+        connection.addRequestProperty("Cache-Control", "max-stale=60");
+
+        if (connection.getContentLength() > fileSizeLimit) {
+            throw new AssociationServiceException("The content size of the url is larger than "
+                    + fileSizeLimit);
+        }
+
+        Long expireTimeMillis = getExpirationTimeMillisFromHTTPHeader(connection.getHeaderFields());
+
+        try {
+            return new WebContent(inputStreamToString(
+                    connection.getInputStream(), connection.getContentLength(), fileSizeLimit),
+                expireTimeMillis);
+        } finally {
+            connection.disconnect();
+        }
+    }
+
+    /**
+     * Visible for testing.
+     * @hide
+     */
+    public static String inputStreamToString(InputStream inputStream, int length, long sizeLimit)
+            throws IOException, AssociationServiceException {
+        if (length < 0) {
+            length = 0;
+        }
+        ByteArrayOutputStream baos = new ByteArrayOutputStream(length);
+        BufferedInputStream bis = new BufferedInputStream(inputStream);
+        byte[] buffer = new byte[INPUT_BUFFER_SIZE_IN_BYTES];
+        int len = 0;
+        while ((len = bis.read(buffer)) != -1) {
+            baos.write(buffer, 0, len);
+            if (baos.size() > sizeLimit) {
+                throw new AssociationServiceException("The content size of the url is larger than "
+                        + sizeLimit);
+            }
+        }
+        return baos.toString("UTF-8");
+    }
+
+    /**
+     * Parses the HTTP headers to compute the ttl.
+     *
+     * @param headers a map that map the header key to the header values. Can be null.
+     * @return the ttl in millisecond or null if the ttl is not specified in the header.
+     */
+    private Long getExpirationTimeMillisFromHTTPHeader(Map<String, List<String>> headers) {
+        if (headers == null) {
+            return null;
+        }
+        Map<String, String> joinedHeaders = joinHttpHeaders(headers);
+
+        NetworkResponse response = new NetworkResponse(null, joinedHeaders);
+        Cache.Entry cachePolicy = HttpHeaderParser.parseCacheHeaders(response);
+
+        if (cachePolicy == null) {
+            // Cache is disabled, set the expire time to 0.
+            return DO_NOT_CACHE_RESULT;
+        } else if (cachePolicy.ttl == 0) {
+            // Cache policy is not specified, set the expire time to 0.
+            return DO_NOT_CACHE_RESULT;
+        } else {
+            // cachePolicy.ttl is actually the expire timestamp in millisecond.
+            return cachePolicy.ttl;
+        }
+    }
+
+    /**
+     * Converts an HTTP header map of the format provided by {@linkHttpUrlConnection} to a map of
+     * the format accepted by {@link HttpHeaderParser}. It does this by joining all the entries for
+     * a given header key with ", ".
+     */
+    private Map<String, String> joinHttpHeaders(Map<String, List<String>> headers) {
+        Map<String, String> joinedHeaders = new HashMap<String, String>();
+        for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
+            List<String> values = entry.getValue();
+            if (values.size() == 1) {
+                joinedHeaders.put(entry.getKey(), values.get(0));
+            } else {
+                joinedHeaders.put(entry.getKey(), Utils.joinStrings(", ", values));
+            }
+        }
+        return joinedHeaders;
+    }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/Utils.java b/packages/StatementService/src/com/android/statementservice/retriever/Utils.java
new file mode 100644
index 0000000..44af864
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/Utils.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2015 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.statementservice.retriever;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Utility library for computing certificate fingerprints. Also includes fields name used by
+ * Statement JSON string.
+ */
+public final class Utils {
+
+    private Utils() {}
+
+    /**
+     * Field name for namespace.
+     */
+    public static final String NAMESPACE_FIELD = "namespace";
+
+    /**
+     * Supported asset namespaces.
+     */
+    public static final String NAMESPACE_WEB = "web";
+    public static final String NAMESPACE_ANDROID_APP = "android_app";
+
+    /**
+     * Field names in a web asset descriptor.
+     */
+    public static final String WEB_ASSET_FIELD_SITE = "site";
+
+    /**
+     * Field names in a Android app asset descriptor.
+     */
+    public static final String ANDROID_APP_ASSET_FIELD_PACKAGE_NAME = "package_name";
+    public static final String ANDROID_APP_ASSET_FIELD_CERT_FPS = "sha256_cert_fingerprints";
+
+    /**
+     * Field names in a statement.
+     */
+    public static final String ASSET_DESCRIPTOR_FIELD_RELATION = "relation";
+    public static final String ASSET_DESCRIPTOR_FIELD_TARGET = "target";
+    public static final String DELEGATE_FIELD_DELEGATE = "delegate";
+
+    private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+            'A', 'B', 'C', 'D', 'E', 'F' };
+
+    /**
+     * Joins a list of strings, by placing separator between each string. For example,
+     * {@code joinStrings("; ", Arrays.asList(new String[]{"a", "b", "c"}))} returns
+     * "{@code a; b; c}".
+     */
+    public static String joinStrings(String separator, List<String> strings) {
+        switch(strings.size()) {
+            case 0:
+                return "";
+            case 1:
+                return strings.get(0);
+            default:
+                StringBuilder joiner = new StringBuilder();
+                boolean first = true;
+                for (String field : strings) {
+                    if (first) {
+                        first = false;
+                    } else {
+                        joiner.append(separator);
+                    }
+                    joiner.append(field);
+                }
+                return joiner.toString();
+        }
+    }
+
+    /**
+     * Returns the normalized sha-256 fingerprints of a given package according to the Android
+     * package manager.
+     */
+    public static List<String> getCertFingerprintsFromPackageManager(String packageName,
+            Context context) throws NameNotFoundException {
+        Signature[] signatures = context.getPackageManager().getPackageInfo(packageName,
+                PackageManager.GET_SIGNATURES).signatures;
+        ArrayList<String> result = new ArrayList<String>(signatures.length);
+        for (Signature sig : signatures) {
+            result.add(computeNormalizedSha256Fingerprint(sig.toByteArray()));
+        }
+        return result;
+    }
+
+    /**
+     * Computes the hash of the byte array using the specified algorithm, returning a hex string
+     * with a colon between each byte.
+     */
+    public static String computeNormalizedSha256Fingerprint(byte[] signature) {
+        MessageDigest digester;
+        try {
+            digester = MessageDigest.getInstance("SHA-256");
+        } catch (NoSuchAlgorithmException e) {
+            throw new AssertionError("No SHA-256 implementation found.");
+        }
+        digester.update(signature);
+        return byteArrayToHexString(digester.digest());
+    }
+
+    /**
+     * Returns true if there is at least one common string between the two lists of string.
+     */
+    public static boolean hasCommonString(List<String> list1, List<String> list2) {
+        HashSet<String> set2 = new HashSet<>(list2);
+        for (String string : list1) {
+            if (set2.contains(string)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Converts the byte array to an lowercase hexadecimal digits String with a colon character (:)
+     * between each byte.
+     */
+    private static String byteArrayToHexString(byte[] array) {
+        if (array.length == 0) {
+          return "";
+        }
+        char[] buf = new char[array.length * 3 - 1];
+
+        int bufIndex = 0;
+        for (int i = 0; i < array.length; i++) {
+            byte b = array[i];
+            if (i > 0) {
+                buf[bufIndex++] = ':';
+            }
+            buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F];
+            buf[bufIndex++] = HEX_DIGITS[b & 0x0F];
+        }
+        return new String(buf);
+    }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/WebAsset.java b/packages/StatementService/src/com/android/statementservice/retriever/WebAsset.java
new file mode 100644
index 0000000..ca9e62d
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/WebAsset.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2015 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.statementservice.retriever;
+
+import org.json.JSONObject;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Locale;
+
+/**
+ * Immutable value type that names a web asset.
+ *
+ * <p>A web asset can be named by its protocol, domain, and port using this JSON string:
+ *     { "namespace": "web",
+ *       "site": "[protocol]://[fully-qualified domain]{:[optional port]}" }
+ *
+ * <p>For example, a website hosted on a https server at www.test.com can be named using
+ *     { "namespace": "web",
+ *       "site": "https://www.test.com" }
+ *
+ * <p>The only protocol supported now are https and http. If the optional port is not specified,
+ * the default for each protocol will be used (i.e. 80 for http and 443 for https).
+ */
+/* package private */ final class WebAsset extends AbstractAsset {
+
+    private static final String MISSING_FIELD_FORMAT_STRING = "Expected %s to be set.";
+
+    private final URL mUrl;
+
+    private WebAsset(URL url) {
+        int port = url.getPort() != -1 ? url.getPort() : url.getDefaultPort();
+        try {
+            mUrl = new URL(url.getProtocol().toLowerCase(), url.getHost().toLowerCase(), port, "");
+        } catch (MalformedURLException e) {
+            throw new AssertionError(
+                    "Url should always be validated before calling the constructor.");
+        }
+    }
+
+    public String getDomain() {
+        return mUrl.getHost();
+    }
+
+    public String getPath() {
+        return mUrl.getPath();
+    }
+
+    public String getScheme() {
+        return mUrl.getProtocol();
+    }
+
+    public int getPort() {
+        return mUrl.getPort();
+    }
+
+    @Override
+    public String toJson() {
+        AssetJsonWriter writer = new AssetJsonWriter();
+
+        writer.writeFieldLower(Utils.NAMESPACE_FIELD, Utils.NAMESPACE_WEB);
+        writer.writeFieldLower(Utils.WEB_ASSET_FIELD_SITE, mUrl.toExternalForm());
+
+        return writer.closeAndGetString();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder asset = new StringBuilder();
+        asset.append("WebAsset: ");
+        asset.append(toJson());
+        return asset.toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof WebAsset)) {
+            return false;
+        }
+
+        return ((WebAsset) o).toJson().equals(toJson());
+    }
+
+    @Override
+    public int hashCode() {
+        return toJson().hashCode();
+    }
+
+    @Override
+    public int lookupKey() {
+        return toJson().hashCode();
+    }
+
+    /**
+     * Checks that the input is a valid web asset.
+     *
+     * @throws AssociationServiceException if the asset is not well formatted.
+     */
+    protected static WebAsset create(JSONObject asset)
+            throws AssociationServiceException {
+        if (asset.optString(Utils.WEB_ASSET_FIELD_SITE).equals("")) {
+            throw new AssociationServiceException(String.format(MISSING_FIELD_FORMAT_STRING,
+                    Utils.WEB_ASSET_FIELD_SITE));
+        }
+
+        URL url;
+        try {
+            url = new URL(asset.optString(Utils.WEB_ASSET_FIELD_SITE));
+        } catch (MalformedURLException e) {
+            throw new AssociationServiceException("Url is not well formatted.", e);
+        }
+
+        String scheme = url.getProtocol().toLowerCase(Locale.US);
+        if (!scheme.equals("https") && !scheme.equals("http")) {
+            throw new AssociationServiceException("Expected scheme to be http or https.");
+        }
+
+        if (url.getUserInfo() != null) {
+            throw new AssociationServiceException("The url should not contain user info.");
+        }
+
+        String path = url.getFile(); // This is url.getPath() + url.getQuery().
+        if (!path.equals("/") && !path.equals("")) {
+            throw new AssociationServiceException(
+                    "Site should only have scheme, domain, and port.");
+        }
+
+        return new WebAsset(url);
+    }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/WebAssetMatcher.java b/packages/StatementService/src/com/android/statementservice/retriever/WebAssetMatcher.java
new file mode 100644
index 0000000..8a1078b
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/WebAssetMatcher.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015 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.statementservice.retriever;
+
+/**
+ * Match assets that have the same 'site' field.
+ */
+/* package private */ final class WebAssetMatcher extends AbstractAssetMatcher {
+
+    private final WebAsset mQuery;
+
+    public WebAssetMatcher(WebAsset query) {
+        mQuery = query;
+    }
+
+    @Override
+    public boolean matches(AbstractAsset asset) {
+        if (asset instanceof WebAsset) {
+            WebAsset webAsset = (WebAsset) asset;
+            return webAsset.toJson().equals(mQuery.toJson());
+        }
+        return false;
+    }
+
+    @Override
+    public int getMatchedLookupKey() {
+        return mQuery.lookupKey();
+    }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/WebContent.java b/packages/StatementService/src/com/android/statementservice/retriever/WebContent.java
new file mode 100644
index 0000000..86a635c
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/WebContent.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 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.statementservice.retriever;
+
+/**
+ * An immutable value type representing the response from a web server.
+ *
+ * Visible for testing.
+ *
+ * @hide
+ */
+public final class WebContent {
+
+    private final String mContent;
+    private final Long mExpireTimeMillis;
+
+    public WebContent(String content, Long expireTimeMillis) {
+        mContent = content;
+        mExpireTimeMillis = expireTimeMillis;
+    }
+
+    /**
+     * Returns the expiration time of the content as specified in the HTTP header.
+     */
+    public Long getExpireTimeMillis() {
+        return mExpireTimeMillis;
+    }
+
+    /**
+     * Returns content of the HTTP message body.
+     */
+    public String getContent() {
+        return mContent;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index dd28734..d8e732e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -748,6 +748,11 @@
         synchronized (this) {
             if (DEBUG) Log.d(TAG, "setKeyguardEnabled(" + enabled + ")");
 
+            if (isSecure()) {
+                Log.d(TAG, "current mode is SecurityMode, ignore hide keyguard");
+                return;
+            }
+
             mExternallyEnabled = enabled;
 
             if (!enabled && mShowing) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index ff6a45a..64730c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.view.LayoutInflater;
@@ -46,20 +47,17 @@
     private final DndDetailAdapter mDetailAdapter;
 
     private boolean mListening;
-    private boolean mVisible;
     private boolean mShowingDetail;
 
     public DndTile(Host host) {
         super(host);
         mController = host.getZenModeController();
         mDetailAdapter = new DndDetailAdapter();
-        mVisible = isVisible(host.getContext());
         mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_SET_VISIBLE));
     }
 
     public static void setVisible(Context context, boolean visible) {
-        context.sendBroadcast(new Intent(DndTile.ACTION_SET_VISIBLE)
-                .putExtra(DndTile.EXTRA_VISIBLE, visible));
+        getSharedPrefs(context).edit().putBoolean(PREF_KEY_VISIBLE, visible).commit();
     }
 
     public static boolean isVisible(Context context) {
@@ -98,7 +96,7 @@
     protected void handleUpdateState(BooleanState state, Object arg) {
         final int zen = arg instanceof Integer ? (Integer) arg : mController.getZen();
         state.value = zen != Global.ZEN_MODE_OFF;
-        state.visible = mVisible;
+        state.visible = isVisible(mContext);
         switch (zen) {
             case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
                 state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on);
@@ -145,26 +143,38 @@
         mListening = listening;
         if (mListening) {
             mController.addCallback(mZenCallback);
+            getSharedPrefs(mContext).registerOnSharedPreferenceChangeListener(mPrefListener);
         } else {
             mController.removeCallback(mZenCallback);
+            getSharedPrefs(mContext).unregisterOnSharedPreferenceChangeListener(mPrefListener);
         }
     }
 
+    private static SharedPreferences getSharedPrefs(Context context) {
+        return context.getSharedPreferences(context.getPackageName(), 0);
+    }
+
+    private final OnSharedPreferenceChangeListener mPrefListener
+            = new OnSharedPreferenceChangeListener() {
+        @Override
+        public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+            if (PREF_KEY_COMBINED_ICON.equals(key) || PREF_KEY_VISIBLE.equals(key)) {
+                refreshState();
+            }
+        }
+    };
+
     private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
         public void onZenChanged(int zen) {
             refreshState(zen);
         }
     };
 
-    private static SharedPreferences getSharedPrefs(Context context) {
-        return context.getSharedPreferences(context.getPackageName(), 0);
-    }
-
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            mVisible = intent.getBooleanExtra(EXTRA_VISIBLE, false);
-            getSharedPrefs(mContext).edit().putBoolean(PREF_KEY_VISIBLE, mVisible).commit();
+            final boolean visible = intent.getBooleanExtra(EXTRA_VISIBLE, false);
+            setVisible(mContext, visible);
             refreshState();
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
index 3b8fccc..cab152f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
@@ -69,7 +69,7 @@
         @Override
         public void onAnimationEnd(Animator animation) {
             mSwipeAnimator = null;
-            setSwipingInProgress(false);
+            mSwipingInProgress = false;
         }
     };
     private Runnable mAnimationEndRunnable = new Runnable() {
@@ -117,14 +117,17 @@
     }
 
     public boolean onTouchEvent(MotionEvent event) {
-        if (mMotionCancelled && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
+        int action = event.getActionMasked();
+        if (mMotionCancelled && action != MotionEvent.ACTION_DOWN
+                && action != MotionEvent.ACTION_UP
+                && action != MotionEvent.ACTION_CANCEL) {
             return false;
         }
         final float y = event.getY();
         final float x = event.getX();
 
         boolean isUp = false;
-        switch (event.getActionMasked()) {
+        switch (action) {
             case MotionEvent.ACTION_DOWN:
                 if (mSwipingInProgress) {
                     cancelAnimation();
@@ -152,7 +155,8 @@
                     mInitialTouchY = y;
                     mInitialTouchX = x;
                     mTranslationOnDown = mTranslation;
-                    setSwipingInProgress(true);
+                    mSwipingInProgress = true;
+                    mCallback.onSwipingStarted(w < -mTouchSlop);
                 }
                 if (mSwipingInProgress) {
                     setTranslation(mTranslationOnDown + x - mInitialTouchX, false, false);
@@ -179,13 +183,6 @@
         }
     }
 
-    private void setSwipingInProgress(boolean inProgress) {
-        mSwipingInProgress = inProgress;
-        if (inProgress) {
-            mCallback.onSwipingStarted();
-        }
-    }
-
     private boolean rightSwipePossible() {
         return mRightIcon.getVisibility() == View.VISIBLE;
     }
@@ -323,6 +320,9 @@
         }
         animator.start();
         mSwipeAnimator = animator;
+        if (snapBack) {
+            mCallback.onSwipingAborted();
+        }
     }
 
     private void startFinishingCircleAnimation(float velocity, Runnable mAnimationEndRunnable) {
@@ -451,7 +451,11 @@
             mSwipeAnimator.cancel();
         }
         setTranslation(0.0f, true, animate);
-        setSwipingInProgress(false);
+        mMotionCancelled = true;
+        if (mSwipingInProgress) {
+            mCallback.onSwipingAborted();
+        }
+        mSwipingInProgress = false;
     }
 
     public interface Callback {
@@ -470,7 +474,9 @@
 
         float getPageWidth();
 
-        void onSwipingStarted();
+        void onSwipingStarted(boolean isRightwardMotion);
+
+        void onSwipingAborted();
 
         KeyguardAffordanceView getLeftIcon();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index a247c8e..628ae84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -100,6 +100,7 @@
     private final TrustDrawable mTrustDrawable;
     private final Interpolator mLinearOutSlowInInterpolator;
     private int mLastUnlockIconRes = 0;
+    private boolean mPrewarmSent;
 
     public KeyguardBottomAreaView(Context context) {
         this(context, null);
@@ -335,12 +336,47 @@
         mLockPatternUtils.requireCredentialEntry(mLockPatternUtils.getCurrentUser());
     }
 
-    public void launchCamera() {
+    public void prewarmCamera() {
         Intent intent = getCameraIntent();
+        String targetPackage = PreviewInflater.getTargetPackage(mContext, intent,
+                mLockPatternUtils.getCurrentUser());
+        if (targetPackage != null) {
+            Intent prewarm = new Intent(MediaStore.ACTION_STILL_IMAGE_CAMERA_PREWARM);
+            prewarm.setPackage(targetPackage);
+            mPrewarmSent = true;
+            mContext.sendBroadcast(prewarm);
+        }
+    }
+
+    public void maybeCooldownCamera() {
+        if (!mPrewarmSent) {
+            return;
+        }
+        mPrewarmSent = false;
+        Intent intent = getCameraIntent();
+        String targetPackage = PreviewInflater.getTargetPackage(mContext, intent,
+                mLockPatternUtils.getCurrentUser());
+        if (targetPackage != null) {
+            Intent prewarm = new Intent(MediaStore.ACTION_STILL_IMAGE_CAMERA_COOLDOWN);
+            prewarm.setPackage(targetPackage);
+            mContext.sendBroadcast(prewarm);
+        }
+    }
+
+    public void launchCamera() {
+
+        // Reset prewarm state.
+        mPrewarmSent = false;
+        final Intent intent = getCameraIntent();
         boolean wouldLaunchResolverActivity = PreviewInflater.wouldLaunchResolverActivity(
                 mContext, intent, mLockPatternUtils.getCurrentUser());
         if (intent == SECURE_CAMERA_INTENT && !wouldLaunchResolverActivity) {
-            mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+            AsyncTask.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+                }
+            });
         } else {
 
             // We need to delay starting the activity because ResolverActivity finishes itself if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 195da46..216730b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -1809,14 +1809,24 @@
     }
 
     @Override
-    public void onSwipingStarted() {
-        mSecureCameraLaunchManager.onSwipingStarted();
+    public void onSwipingStarted(boolean isRightwardMotion) {
+        boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? isRightwardMotion
+                : !isRightwardMotion;
+        if (!start) {
+            mSecureCameraLaunchManager.onSwipingStarted();
+            mKeyguardBottomArea.prewarmCamera();
+        }
         requestDisallowInterceptTouchEvent(true);
         mOnlyAffordanceInThisMotion = true;
         mQsTracking = false;
     }
 
     @Override
+    public void onSwipingAborted() {
+        mKeyguardBottomArea.maybeCooldownCamera();
+    }
+
+    @Override
     public KeyguardAffordanceView getLeftIcon() {
         return getLayoutDirection() == LAYOUT_DIRECTION_RTL
                 ? mKeyguardBottomArea.getCameraView()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java
index 34068fd..0dce82f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.policy;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -106,15 +107,28 @@
 
     public static boolean wouldLaunchResolverActivity(Context ctx, Intent intent,
             int currentUserId) {
+        return getTargetPackage(ctx, intent, currentUserId) == null;
+    }
+
+    /**
+     * @return the target package of the intent it resolves to a specific package or {@code null} if
+     *         it resolved to the resolver activity
+     */
+    public static String getTargetPackage(Context ctx, Intent intent,
+            int currentUserId) {
         PackageManager packageManager = ctx.getPackageManager();
         final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
                 intent, PackageManager.MATCH_DEFAULT_ONLY, currentUserId);
         if (appList.size() == 0) {
-            return false;
+            return null;
         }
         ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
                 PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA, currentUserId);
-        return wouldLaunchResolverActivity(resolved, appList);
+        if (resolved == null || wouldLaunchResolverActivity(resolved, appList)) {
+            return null;
+        } else {
+            return resolved.activityInfo.packageName;
+        }
     }
 
     private static boolean wouldLaunchResolverActivity(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
index a3d9377..265e2c6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
@@ -45,6 +45,7 @@
 import android.util.SparseArray;
 
 import com.android.systemui.R;
+import com.android.systemui.qs.tiles.DndTile;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -489,8 +490,7 @@
 
     public void showDndTile(boolean visible) {
         if (D.BUG) Log.d(TAG, "showDndTile");
-        mContext.sendBroadcast(new Intent("com.android.systemui.dndtile.SET_VISIBLE")
-                .putExtra("visible", visible));
+        DndTile.setVisible(mContext, visible);
     }
 
     private final class VC extends IVolumeController.Stub {
diff --git a/rs/java/android/renderscript/RenderScript.java b/rs/java/android/renderscript/RenderScript.java
index 7ef17a7..fd19d49 100644
--- a/rs/java/android/renderscript/RenderScript.java
+++ b/rs/java/android/renderscript/RenderScript.java
@@ -16,7 +16,6 @@
 
 package android.renderscript;
 
-import java.io.File;
 import java.lang.reflect.Method;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
@@ -129,8 +128,6 @@
     native void nContextInitToClient(long con);
     native void nContextDeinitToClient(long con);
 
-    static File mCacheDir;
-
     // this should be a monotonically increasing ID
     // used in conjunction with the API version of a device
     static final long sMinorID = 1;
@@ -146,23 +143,6 @@
         return sMinorID;
     }
 
-     /**
-     * Sets the directory to use as a persistent storage for the
-     * renderscript object file cache.
-     *
-     * @hide
-     * @param cacheDir A directory the current process can write to
-     */
-    public static void setupDiskCache(File cacheDir) {
-        if (!sInitialized) {
-            Log.e(LOG_TAG, "RenderScript.setupDiskCache() called when disabled");
-            return;
-        }
-
-        // Defer creation of cache path to nScriptCCreate().
-        mCacheDir = cacheDir;
-    }
-
     /**
      * ContextType specifies the specific type of context to be created.
      *
diff --git a/rs/java/android/renderscript/RenderScriptCacheDir.java b/rs/java/android/renderscript/RenderScriptCacheDir.java
new file mode 100644
index 0000000..95a9d75
--- /dev/null
+++ b/rs/java/android/renderscript/RenderScriptCacheDir.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2008-2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.renderscript;
+
+import java.io.File;
+
+/**
+ * Used only for tracking the RenderScript cache directory.
+ * @hide
+ */
+public class RenderScriptCacheDir {
+     /**
+     * Sets the directory to use as a persistent storage for the
+     * renderscript object file cache.
+     *
+     * @hide
+     * @param cacheDir A directory the current process can write to
+     */
+    public static void setupDiskCache(File cacheDir) {
+        // Defer creation of cache path to nScriptCCreate().
+        mCacheDir = cacheDir;
+    }
+
+    static File mCacheDir;
+
+}
diff --git a/rs/java/android/renderscript/ScriptC.java b/rs/java/android/renderscript/ScriptC.java
index 64d21e4..bf706c1 100644
--- a/rs/java/android/renderscript/ScriptC.java
+++ b/rs/java/android/renderscript/ScriptC.java
@@ -124,7 +124,7 @@
 
         // Create the RS cache path if we haven't done so already.
         if (mCachePath == null) {
-            File f = new File(rs.mCacheDir, CACHE_PATH);
+            File f = new File(RenderScriptCacheDir.mCacheDir, CACHE_PATH);
             mCachePath = f.getAbsolutePath();
             f.mkdirs();
         }
@@ -135,7 +135,7 @@
     private static synchronized long internalStringCreate(RenderScript rs, String resName, byte[] bitcode) {
         // Create the RS cache path if we haven't done so already.
         if (mCachePath == null) {
-            File f = new File(rs.mCacheDir, CACHE_PATH);
+            File f = new File(RenderScriptCacheDir.mCacheDir, CACHE_PATH);
             mCachePath = f.getAbsolutePath();
             f.mkdirs();
         }
diff --git a/rs/java/android/renderscript/ScriptGroup2.java b/rs/java/android/renderscript/ScriptGroup2.java
index 8b9f73e..858a957 100644
--- a/rs/java/android/renderscript/ScriptGroup2.java
+++ b/rs/java/android/renderscript/ScriptGroup2.java
@@ -549,11 +549,17 @@
         /**
          * Creates a script group
          *
+         * @param name name for the script group. Legal names can only contain letters, digits,
+         *        '-', or '_'. The name can be no longer than 100 characters.
          * @param outputs futures intended as outputs of the script group
          * @return a script group
          */
 
-        public ScriptGroup2 create(Future... outputs) {
+        public ScriptGroup2 create(String name, Future... outputs) {
+            if (name == null || name.isEmpty() || name.length() > 100 ||
+                !name.equals(name.replaceAll("[^a-zA-Z0-9-]", "_"))) {
+                throw new RSIllegalArgumentException("invalid script group name");
+            }
             ScriptGroup2 ret = new ScriptGroup2(mRS, mClosures, mInputs, outputs);
             return ret;
         }
diff --git a/services/core/java/com/android/server/AssetAtlasService.java b/services/core/java/com/android/server/AssetAtlasService.java
index f106667..66cc29a 100644
--- a/services/core/java/com/android/server/AssetAtlasService.java
+++ b/services/core/java/com/android/server/AssetAtlasService.java
@@ -415,12 +415,20 @@
                 new Thread(worker, "Atlas Worker #" + (i + 1)).start();
             }
 
+            boolean isAllWorkerFinished;
             try {
-                signal.await(10, TimeUnit.SECONDS);
+                isAllWorkerFinished = signal.await(10, TimeUnit.SECONDS);
             } catch (InterruptedException e) {
                 Log.w(LOG_TAG, "Could not complete configuration computation");
                 return null;
             }
+
+            if (!isAllWorkerFinished) {
+                // We have to abort here, otherwise the async updates on "results" would crash the
+                // sort later.
+                Log.w(LOG_TAG, "Could not complete configuration computation before timeout.");
+                return null;
+            }
         }
 
         // Maximize the number of packed bitmaps, minimize the texture size
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 18ab3b4..607e09c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17467,7 +17467,8 @@
                                 fd = ParcelFileDescriptor.open(heapdumpFile,
                                         ParcelFileDescriptor.MODE_CREATE |
                                                 ParcelFileDescriptor.MODE_TRUNCATE |
-                                                ParcelFileDescriptor.MODE_READ_WRITE);
+                                                ParcelFileDescriptor.MODE_WRITE_ONLY |
+                                                ParcelFileDescriptor.MODE_APPEND);
                                 IApplicationThread thread = myProc.thread;
                                 if (thread != null) {
                                     try {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index f8e9cbf..d08cddc 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -156,8 +156,7 @@
     static final int LOCK_TASK_START_MSG = FIRST_SUPERVISOR_STACK_MSG + 9;
     static final int LOCK_TASK_END_MSG = FIRST_SUPERVISOR_STACK_MSG + 10;
     static final int CONTAINER_CALLBACK_TASK_LIST_EMPTY = FIRST_SUPERVISOR_STACK_MSG + 11;
-    static final int CONTAINER_TASK_LIST_EMPTY_TIMEOUT = FIRST_SUPERVISOR_STACK_MSG + 12;
-    static final int LAUNCH_TASK_BEHIND_COMPLETE = FIRST_SUPERVISOR_STACK_MSG + 13;
+    static final int LAUNCH_TASK_BEHIND_COMPLETE = FIRST_SUPERVISOR_STACK_MSG + 12;
 
     private final static String VIRTUAL_DISPLAY_BASE_NAME = "ActivityViewVirtualDisplay";
 
@@ -3803,15 +3802,6 @@
                         }
                     }
                 } break;
-                case CONTAINER_TASK_LIST_EMPTY_TIMEOUT: {
-                    synchronized (mService) {
-                        Slog.w(TAG, "Timeout waiting for all activities in task to finish. " +
-                                msg.obj);
-                        final ActivityContainer container = (ActivityContainer) msg.obj;
-                        container.mStack.finishAllActivitiesLocked(true);
-                        container.onTaskListEmptyLocked();
-                    }
-                } break;
                 case LAUNCH_TASK_BEHIND_COMPLETE: {
                     synchronized (mService) {
                         ActivityRecord r = ActivityRecord.forTokenLocked((IBinder) msg.obj);
@@ -3916,10 +3906,6 @@
                 }
                 mContainerState = CONTAINER_STATE_FINISHING;
 
-                final Message msg =
-                        mHandler.obtainMessage(CONTAINER_TASK_LIST_EMPTY_TIMEOUT, this);
-                mHandler.sendMessageDelayed(msg, 2000);
-
                 long origId = Binder.clearCallingIdentity();
                 try {
                     mStack.finishAllActivitiesLocked(false);
@@ -4039,7 +4025,6 @@
         }
 
         void onTaskListEmptyLocked() {
-            mHandler.removeMessages(CONTAINER_TASK_LIST_EMPTY_TIMEOUT, this);
             detachLocked();
             deleteActivityContainer(this);
             mHandler.obtainMessage(CONTAINER_CALLBACK_TASK_LIST_EMPTY, this).sendToTarget();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 84dc748..4fea889 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -267,8 +267,7 @@
     private static final boolean DEBUG_DEXOPT = false;
     private static final boolean DEBUG_ABI_SELECTION = false;
 
-    static final boolean RUNTIME_PERMISSIONS_ENABLED =
-            SystemProperties.getInt("ro.runtime.permissions.enabled", 0) == 1;
+    static final boolean RUNTIME_PERMISSIONS_ENABLED = true;
 
     private static final int RADIO_UID = Process.PHONE_UID;
     private static final int LOG_UID = Process.LOG_UID;
@@ -11148,13 +11147,13 @@
 
         startIntentFilterVerifications(args.user.getIdentifier(), pkg);
 
+        // Call with SCAN_NO_DEX, since dexopt has already been made
         if (replace) {
-            // Call replacePackageLI with SCAN_NO_DEX, since we already made dexopt
             replacePackageLI(pkg, parseFlags, scanFlags | SCAN_REPLACING | SCAN_NO_DEX, args.user,
                     installerPackageName, volumeUuid, res);
         } else {
-            installNewPackageLI(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
-                    args.user, installerPackageName, volumeUuid, res);
+            installNewPackageLI(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES
+                    | SCAN_NO_DEX, args.user, installerPackageName, volumeUuid, res);
         }
         synchronized (mPackages) {
             final PackageSetting ps = mSettings.mPackages.get(pkgName);
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index c6951bd..1a125d4 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -24,6 +24,7 @@
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.IBinder;
 import android.os.Message;
@@ -63,6 +64,7 @@
     Display mDisplay;
 
     private final Region mTmpRegion = new Region();
+    private final Rect mTmpRect = new Rect();
 
     DragState(WindowManagerService service, IBinder token, SurfaceControl surface,
             int flags, IBinder localWin) {
@@ -411,6 +413,12 @@
                 continue;
             }
 
+            child.getStackBounds(mTmpRect);
+            if (!mTmpRect.contains(x, y)) {
+                // outside of this window's activity stack == don't tell about drags
+                continue;
+            }
+
             child.getTouchableRegion(mTmpRegion);
 
             final int touchFlags = flags &
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index ac1b0f1..1a30cba 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1403,6 +1403,9 @@
                 if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
                         "SIZE " + width + "x" + height, null);
                 mSurfaceControl.setSize(width, height);
+                mSurfaceControl.setMatrix(
+                        mDsDx * w.mHScale, mDtDx * w.mVScale,
+                        mDsDy * w.mHScale, mDtDy * w.mVScale);
                 mAnimator.setPendingLayoutChanges(w.getDisplayId(),
                         WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
                 if ((w.mAttrs.flags & LayoutParams.FLAG_DIM_BEHIND) != 0) {
diff --git a/tests/CameraPrewarmTest/Android.mk b/tests/CameraPrewarmTest/Android.mk
new file mode 100644
index 0000000..b6316f0
--- /dev/null
+++ b/tests/CameraPrewarmTest/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := CameraPrewarmTest
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
diff --git a/tests/CameraPrewarmTest/AndroidManifest.xml b/tests/CameraPrewarmTest/AndroidManifest.xml
new file mode 100644
index 0000000..eb40200
--- /dev/null
+++ b/tests/CameraPrewarmTest/AndroidManifest.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 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
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.google.android.test.cameraprewarm">
+    <application android:label="@string/activity_title">
+
+        <activity android:name=".CameraActivity"
+                android:theme="@android:style/Theme.NoTitleBar">
+            <intent-filter>
+                <action android:name="android.media.action.STILL_IMAGE_CAMERA_SECURE" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.media.action.STILL_IMAGE_CAMERA" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".SecureCameraActivity"
+                android:theme="@android:style/Theme.NoTitleBar">
+            <intent-filter>
+                <action android:name="android.media.action.STILL_IMAGE_CAMERA_SECURE" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <receiver android:name=".PrewarmReceiver" >
+            <intent-filter>
+                <action android:name="android.media.action.STILL_IMAGE_CAMERA_PREWARM" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.media.action.STILL_IMAGE_CAMERA_COOLDOWN" />
+            </intent-filter>
+        </receiver>
+
+    </application>
+</manifest>
diff --git a/tests/CameraPrewarmTest/res/layout/camera_activity.xml b/tests/CameraPrewarmTest/res/layout/camera_activity.xml
new file mode 100644
index 0000000..64437bc
--- /dev/null
+++ b/tests/CameraPrewarmTest/res/layout/camera_activity.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center">
+</LinearLayout>
+
diff --git a/tests/CameraPrewarmTest/res/values/strings.xml b/tests/CameraPrewarmTest/res/values/strings.xml
new file mode 100644
index 0000000..11f7ac7
--- /dev/null
+++ b/tests/CameraPrewarmTest/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 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
+  -->
+
+<resources>
+    <string name="activity_title">Assistant</string>
+    <string name="search_label">Orilla Search Engine</string>
+</resources>
diff --git a/tests/CameraPrewarmTest/src/com/google/android/test/cameraprewarm/CameraActivity.java b/tests/CameraPrewarmTest/src/com/google/android/test/cameraprewarm/CameraActivity.java
new file mode 100644
index 0000000..4d22234
--- /dev/null
+++ b/tests/CameraPrewarmTest/src/com/google/android/test/cameraprewarm/CameraActivity.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 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.google.android.test.cameraprewarm;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.WindowManager;
+
+import com.google.android.test.cameraprewarm.R;
+
+public class CameraActivity extends Activity {
+
+    public final static String TAG = "PrewarmTest";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.camera_activity);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+        Log.i(TAG, "Activity created");
+    }
+}
diff --git a/tests/CameraPrewarmTest/src/com/google/android/test/cameraprewarm/PrewarmReceiver.java b/tests/CameraPrewarmTest/src/com/google/android/test/cameraprewarm/PrewarmReceiver.java
new file mode 100644
index 0000000..d49f96d
--- /dev/null
+++ b/tests/CameraPrewarmTest/src/com/google/android/test/cameraprewarm/PrewarmReceiver.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 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.google.android.test.cameraprewarm;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.MediaStore;
+import android.util.Log;
+
+public class PrewarmReceiver extends BroadcastReceiver {
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (intent.getAction().equals(MediaStore.ACTION_STILL_IMAGE_CAMERA_PREWARM)) {
+            Log.i(CameraActivity.TAG, "Prewarm received");
+        } else if (intent.getAction().equals(MediaStore.ACTION_STILL_IMAGE_CAMERA_COOLDOWN)){
+            Log.i(CameraActivity.TAG, "Cooldown received");
+        }
+    }
+}
diff --git a/tests/CameraPrewarmTest/src/com/google/android/test/cameraprewarm/SecureCameraActivity.java b/tests/CameraPrewarmTest/src/com/google/android/test/cameraprewarm/SecureCameraActivity.java
new file mode 100644
index 0000000..530fe00
--- /dev/null
+++ b/tests/CameraPrewarmTest/src/com/google/android/test/cameraprewarm/SecureCameraActivity.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 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.google.android.test.cameraprewarm;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.WindowManager;
+
+import com.google.android.test.cameraprewarm.R;
+
+public class SecureCameraActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.camera_activity);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+        Log.i(CameraActivity.TAG, "Activity created");
+    }
+}