Merge "Fix onHardwarePreDraw call in ThreadedRenderer"
diff --git a/api/current.txt b/api/current.txt
index d3aaf2c..b16eb2f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -31,7 +31,6 @@
     field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
     field public static final java.lang.String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
     field public static final java.lang.String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT";
-    field public static final java.lang.String BIND_VOICE_INTERACTION = "android.permission.BIND_VOICE_INTERACTION";
     field public static final java.lang.String BIND_VPN_SERVICE = "android.permission.BIND_VPN_SERVICE";
     field public static final java.lang.String BIND_WALLPAPER = "android.permission.BIND_WALLPAPER";
     field public static final java.lang.String BLUETOOTH = "android.permission.BLUETOOTH";
@@ -1017,7 +1016,6 @@
     field public static final int selectableItemBackground = 16843534; // 0x101030e
     field public static final int selectedDateVerticalBar = 16843591; // 0x1010347
     field public static final int selectedWeekBackgroundColor = 16843586; // 0x1010342
-    field public static final int sessionService = 16843841; // 0x1010441
     field public static final int settingsActivity = 16843301; // 0x1010225
     field public static final int shadowColor = 16843105; // 0x1010161
     field public static final int shadowDx = 16843106; // 0x1010162
@@ -3238,7 +3236,6 @@
     method public int getTaskId();
     method public final java.lang.CharSequence getTitle();
     method public final int getTitleColor();
-    method public android.app.VoiceInteractor getVoiceInteractor();
     method public final int getVolumeControlStream();
     method public android.view.Window getWindow();
     method public android.view.WindowManager getWindowManager();
@@ -3250,7 +3247,6 @@
     method public boolean isFinishing();
     method public boolean isImmersive();
     method public boolean isTaskRoot();
-    method public boolean isVoiceInteraction();
     method public final deprecated android.database.Cursor managedQuery(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
     method public boolean moveTaskToBack(boolean);
     method public boolean navigateUpTo(android.content.Intent);
@@ -3352,12 +3348,12 @@
     method public final void setProgressBarIndeterminate(boolean);
     method public final void setProgressBarIndeterminateVisibility(boolean);
     method public final void setProgressBarVisibility(boolean);
-    method public void setRecentsActivityValues(android.app.ActivityManager.RecentsActivityValues);
     method public void setRequestedOrientation(int);
     method public final void setResult(int);
     method public final void setResult(int, android.content.Intent);
     method public final void setSecondaryProgress(int);
     method public void setSharedElementListener(android.app.SharedElementListener);
+    method public void setTaskDescription(android.app.ActivityManager.TaskDescription);
     method public void setTitle(java.lang.CharSequence);
     method public void setTitle(int);
     method public deprecated void setTitleColor(int);
@@ -3482,7 +3478,6 @@
     method public void readFromParcel(android.os.Parcel);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator CREATOR;
-    field public android.app.ActivityManager.RecentsActivityValues activityValues;
     field public android.content.Intent baseIntent;
     field public java.lang.CharSequence description;
     field public int id;
@@ -3490,21 +3485,6 @@
     field public int persistentId;
   }
 
-  public static class ActivityManager.RecentsActivityValues implements android.os.Parcelable {
-    ctor public ActivityManager.RecentsActivityValues(android.app.ActivityManager.RecentsActivityValues);
-    ctor public ActivityManager.RecentsActivityValues(java.lang.CharSequence, android.graphics.Bitmap, int);
-    ctor public ActivityManager.RecentsActivityValues(java.lang.CharSequence, android.graphics.Bitmap);
-    ctor public ActivityManager.RecentsActivityValues(java.lang.CharSequence);
-    ctor public ActivityManager.RecentsActivityValues();
-    method public int describeContents();
-    method public void readFromParcel(android.os.Parcel);
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator CREATOR;
-    field public int colorPrimary;
-    field public android.graphics.Bitmap icon;
-    field public java.lang.CharSequence label;
-  }
-
   public static class ActivityManager.RunningAppProcessInfo implements android.os.Parcelable {
     ctor public ActivityManager.RunningAppProcessInfo();
     ctor public ActivityManager.RunningAppProcessInfo(java.lang.String, int, java.lang.String[]);
@@ -3574,6 +3554,21 @@
     field public android.content.ComponentName topActivity;
   }
 
+  public static class ActivityManager.TaskDescription implements android.os.Parcelable {
+    ctor public ActivityManager.TaskDescription(java.lang.String, android.graphics.Bitmap, int);
+    ctor public ActivityManager.TaskDescription(java.lang.String, android.graphics.Bitmap);
+    ctor public ActivityManager.TaskDescription(java.lang.String);
+    ctor public ActivityManager.TaskDescription();
+    ctor public ActivityManager.TaskDescription(android.app.ActivityManager.TaskDescription);
+    method public int describeContents();
+    method public android.graphics.Bitmap getIcon();
+    method public java.lang.String getLabel();
+    method public int getPrimaryColor();
+    method public void readFromParcel(android.os.Parcel);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+  }
+
   public class ActivityOptions {
     method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
     method public static android.app.ActivityOptions makeScaleUpAnimation(android.view.View, int, int, int, int);
@@ -4912,29 +4907,6 @@
     field public static final int MODE_NIGHT_YES = 2; // 0x2
   }
 
-  public class VoiceInteractor {
-    method public boolean submitRequest(android.app.VoiceInteractor.Request);
-    method public boolean[] supportsCommands(java.lang.String[]);
-  }
-
-  public static class VoiceInteractor.CommandRequest extends android.app.VoiceInteractor.Request {
-    ctor public VoiceInteractor.CommandRequest(java.lang.String, android.os.Bundle);
-    method public void onCommandResult(android.os.Bundle);
-  }
-
-  public static class VoiceInteractor.ConfirmationRequest extends android.app.VoiceInteractor.Request {
-    ctor public VoiceInteractor.ConfirmationRequest(java.lang.CharSequence, android.os.Bundle);
-    method public void onConfirmationResult(boolean, android.os.Bundle);
-  }
-
-  public static abstract class VoiceInteractor.Request {
-    ctor public VoiceInteractor.Request();
-    method public void cancel();
-    method public android.app.Activity getActivity();
-    method public android.content.Context getContext();
-    method public void onCancel();
-  }
-
   public final class WallpaperInfo implements android.os.Parcelable {
     ctor public WallpaperInfo(android.content.Context, android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
     method public int describeContents();
@@ -5245,7 +5217,7 @@
   public abstract class TaskService extends android.app.Service {
     ctor public TaskService();
     method public final android.os.IBinder onBind(android.content.Intent);
-    method public abstract void onStartTask(android.app.task.TaskParams);
+    method public abstract boolean onStartTask(android.app.task.TaskParams);
     method public abstract boolean onStopTask(android.app.task.TaskParams);
     method public final void taskFinished(android.app.task.TaskParams, boolean);
     field public static final java.lang.String PERMISSION_BIND = "android.permission.BIND_TASK_SERVICE";
@@ -7132,7 +7104,6 @@
     field public static final java.lang.String CATEGORY_TAB = "android.intent.category.TAB";
     field public static final java.lang.String CATEGORY_TEST = "android.intent.category.TEST";
     field public static final java.lang.String CATEGORY_UNIT_TEST = "android.intent.category.UNIT_TEST";
-    field public static final java.lang.String CATEGORY_VOICE = "android.intent.category.VOICE";
     field public static final android.os.Parcelable.Creator CREATOR;
     field public static final java.lang.String EXTRA_ALARM_COUNT = "android.intent.extra.ALARM_COUNT";
     field public static final java.lang.String EXTRA_ALLOW_MULTIPLE = "android.intent.extra.ALLOW_MULTIPLE";
@@ -13736,16 +13707,17 @@
     ctor public DeniedByServerException(java.lang.String);
   }
 
-  public final class DngCreator {
+  public final class DngCreator implements java.lang.AutoCloseable {
     ctor public DngCreator(android.hardware.camera2.CameraCharacteristics, android.hardware.camera2.CaptureResult);
+    method public void close();
     method public android.media.DngCreator setDescription(java.lang.String);
     method public android.media.DngCreator setLocation(android.location.Location);
     method public android.media.DngCreator setOrientation(int);
     method public android.media.DngCreator setThumbnail(android.graphics.Bitmap);
     method public android.media.DngCreator setThumbnail(android.media.Image);
-    method public void writeByteBuffer(java.io.OutputStream, java.nio.ByteBuffer, int, long) throws java.io.IOException;
+    method public void writeByteBuffer(java.io.OutputStream, android.util.Size, java.nio.ByteBuffer, long) throws java.io.IOException;
     method public void writeImage(java.io.OutputStream, android.media.Image) throws java.io.IOException;
-    method public void writeInputStream(java.io.OutputStream, java.io.InputStream, int, long) throws java.io.IOException;
+    method public void writeInputStream(java.io.OutputStream, android.util.Size, java.io.InputStream, long) throws java.io.IOException;
   }
 
   public class ExifInterface {
@@ -25335,72 +25307,6 @@
 
 }
 
-package android.service.voice {
-
-  public class VoiceInteractionService extends android.app.Service {
-    ctor public VoiceInteractionService();
-    method public android.os.IBinder onBind(android.content.Intent);
-    method public void startSession(android.os.Bundle);
-    field public static final java.lang.String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService";
-    field public static final java.lang.String SERVICE_META_DATA = "android.voice_interaction";
-  }
-
-  public abstract class VoiceInteractionSession implements android.view.KeyEvent.Callback {
-    ctor public VoiceInteractionSession(android.content.Context);
-    ctor public VoiceInteractionSession(android.content.Context, android.os.Handler);
-    method public void finish();
-    method public android.view.LayoutInflater getLayoutInflater();
-    method public android.app.Dialog getWindow();
-    method public void hideWindow();
-    method public void onBackPressed();
-    method public abstract void onCancel(android.service.voice.VoiceInteractionSession.Request);
-    method public void onCloseSystemDialogs();
-    method public abstract void onCommand(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.String, android.os.Bundle);
-    method public void onComputeInsets(android.service.voice.VoiceInteractionSession.Insets);
-    method public abstract void onConfirm(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.String, android.os.Bundle);
-    method public void onCreate(android.os.Bundle);
-    method public android.view.View onCreateContentView();
-    method public void onDestroy();
-    method public abstract boolean[] onGetSupportedCommands(android.service.voice.VoiceInteractionSession.Caller, java.lang.String[]);
-    method public boolean onKeyDown(int, android.view.KeyEvent);
-    method public boolean onKeyLongPress(int, android.view.KeyEvent);
-    method public boolean onKeyMultiple(int, int, android.view.KeyEvent);
-    method public boolean onKeyUp(int, android.view.KeyEvent);
-    method public void onTaskFinished(android.content.Intent, int);
-    method public void onTaskStarted(android.content.Intent, int);
-    method public void setContentView(android.view.View);
-    method public void setTheme(int);
-    method public void showWindow();
-    method public void startVoiceActivity(android.content.Intent);
-  }
-
-  public static class VoiceInteractionSession.Caller {
-  }
-
-  public static final class VoiceInteractionSession.Insets {
-    ctor public VoiceInteractionSession.Insets();
-    field public static final int TOUCHABLE_INSETS_CONTENT = 1; // 0x1
-    field public static final int TOUCHABLE_INSETS_FRAME = 0; // 0x0
-    field public static final int TOUCHABLE_INSETS_REGION = 3; // 0x3
-    field public int contentTopInsets;
-    field public int touchableInsets;
-    field public final android.graphics.Region touchableRegion;
-  }
-
-  public static class VoiceInteractionSession.Request {
-    method public void sendCancelResult();
-    method public void sendCommandResult(boolean, android.os.Bundle);
-    method public void sendConfirmResult(boolean, android.os.Bundle);
-  }
-
-  public abstract class VoiceInteractionSessionService extends android.app.Service {
-    ctor public VoiceInteractionSessionService();
-    method public android.os.IBinder onBind(android.content.Intent);
-    method public abstract android.service.voice.VoiceInteractionSession onNewSession(android.os.Bundle);
-  }
-
-}
-
 package android.service.wallpaper {
 
   public abstract class WallpaperService extends android.app.Service {
@@ -27112,6 +27018,7 @@
     method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
     method public boolean isNetworkRoaming();
     method public void listen(android.telephony.PhoneStateListener, int);
+    method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
     field public static final java.lang.String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE";
     field public static final java.lang.String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE";
     field public static final int CALL_STATE_IDLE = 0; // 0x0
@@ -30828,6 +30735,7 @@
     field public static final int KEYCODE_P = 44; // 0x2c
     field public static final int KEYCODE_PAGE_DOWN = 93; // 0x5d
     field public static final int KEYCODE_PAGE_UP = 92; // 0x5c
+    field public static final int KEYCODE_PAIRING = 225; // 0xe1
     field public static final int KEYCODE_PERIOD = 56; // 0x38
     field public static final int KEYCODE_PICTSYMBOLS = 94; // 0x5e
     field public static final int KEYCODE_PLUS = 81; // 0x51
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 36c36a8..e1a94d7 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1222,6 +1222,7 @@
      * Check whether this activity is running as part of a voice interaction with the user.
      * If true, it should perform its interaction with the user through the
      * {@link VoiceInteractor} returned by {@link #getVoiceInteractor}.
+     * @hide
      */
     public boolean isVoiceInteraction() {
         return mVoiceInteractor != null;
@@ -1230,6 +1231,7 @@
     /**
      * Retrieve the active {@link VoiceInteractor} that the user is going through to
      * interact with this activity.
+     * @hide
      */
     public VoiceInteractor getVoiceInteractor() {
         return mVoiceInteractor;
@@ -3611,15 +3613,15 @@
             theme.applyStyle(resid, false);
         }
 
-        // Get the primary color and update the RecentsActivityValues for this activity
+        // Get the primary color and update the TaskDescription for this activity
         if (theme != null) {
             TypedArray a = theme.obtainStyledAttributes(com.android.internal.R.styleable.Theme);
             int colorPrimary = a.getColor(com.android.internal.R.styleable.Theme_colorPrimary, 0);
             a.recycle();
             if (colorPrimary != 0) {
-                ActivityManager.RecentsActivityValues v = new ActivityManager.RecentsActivityValues();
-                v.colorPrimary = colorPrimary;
-                setRecentsActivityValues(v);
+                ActivityManager.TaskDescription v = new ActivityManager.TaskDescription(null, null,
+                        colorPrimary);
+                setTaskDescription(v);
             }
         }
     }
@@ -4924,27 +4926,30 @@
     }
 
     /**
-     * Sets information describing this Activity for presentation inside the Recents System UI. When
-     * {@link ActivityManager#getRecentTasks} is called, the activities of each task are
-     * traversed in order from the topmost activity to the bottommost. The traversal continues for
-     * each property until a suitable value is found. For each task those values will be returned in
-     * {@link android.app.ActivityManager.RecentsActivityValues}.
+     * Sets information describing the task with this activity for presentation inside the Recents
+     * System UI. When {@link ActivityManager#getRecentTasks} is called, the activities of each task
+     * are traversed in order from the topmost activity to the bottommost. The traversal continues
+     * for each property until a suitable value is found. For each task the taskDescription will be
+     * returned in {@link android.app.ActivityManager.TaskDescription}.
      *
      * @see ActivityManager#getRecentTasks
-     * @see android.app.ActivityManager.RecentsActivityValues
+     * @see android.app.ActivityManager.TaskDescription
      *
-     * @param values The Recents values that describe this activity.
+     * @param taskDescription The TaskDescription properties that describe the task with this activity
      */
-    public void setRecentsActivityValues(ActivityManager.RecentsActivityValues values) {
-        ActivityManager.RecentsActivityValues activityValues =
-                new ActivityManager.RecentsActivityValues(values);
-        // Scale the icon down to something reasonable
-        if (values.icon != null) {
+    public void setTaskDescription(ActivityManager.TaskDescription taskDescription) {
+        ActivityManager.TaskDescription td;
+        // Scale the icon down to something reasonable if it is provided
+        if (taskDescription.getIcon() != null) {
             final int size = ActivityManager.getLauncherLargeIconSizeInner(this);
-            activityValues.icon = Bitmap.createScaledBitmap(values.icon, size, size, true);
+            final Bitmap icon = Bitmap.createScaledBitmap(taskDescription.getIcon(), size, size, true);
+            td = new ActivityManager.TaskDescription(taskDescription.getLabel(), icon,
+                    taskDescription.getPrimaryColor());
+        } else {
+            td = taskDescription;
         }
         try {
-            ActivityManagerNative.getDefault().setRecentsActivityValues(mToken, activityValues);
+            ActivityManagerNative.getDefault().setTaskDescription(mToken, td);
         } catch (RemoteException e) {
         }
     }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 1d05320..abcb0d0 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -33,6 +33,7 @@
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.Color;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Debug;
@@ -477,65 +478,84 @@
     /**
      * Information you can set and retrieve about the current activity within the recent task list.
      */
-    public static class RecentsActivityValues implements Parcelable {
-        public CharSequence label;
-        public Bitmap icon;
-        public int colorPrimary;
-
-        public RecentsActivityValues(RecentsActivityValues values) {
-            copyFrom(values);
-        }
+    public static class TaskDescription implements Parcelable {
+        private String mLabel;
+        private Bitmap mIcon;
+        private int mColorPrimary;
 
         /**
-         * Creates the RecentsActivityValues to the specified values.
+         * Creates the TaskDescription to the specified values.
          *
-         * @param label A label and description of the current state of this activity.
-         * @param icon An icon that represents the current state of this activity.
-         * @param color A color to override the theme's primary color.
+         * @param label A label and description of the current state of this task.
+         * @param icon An icon that represents the current state of this task.
+         * @param colorPrimary A color to override the theme's primary color.  This color must be opaque.
          */
-        public RecentsActivityValues(CharSequence label, Bitmap icon, int color) {
-            this.label = label;
-            this.icon = icon;
-            this.colorPrimary = color;
+        public TaskDescription(String label, Bitmap icon, int colorPrimary) {
+            if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
+                throw new RuntimeException("A TaskDescription's primary color should be opaque");
+            }
+
+            mLabel = label;
+            mIcon = icon;
+            mColorPrimary = colorPrimary;
         }
 
         /**
-         * Creates the RecentsActivityValues to the specified values.
+         * Creates the TaskDescription to the specified values.
          *
          * @param label A label and description of the current state of this activity.
          * @param icon An icon that represents the current state of this activity.
          */
-        public RecentsActivityValues(CharSequence label, Bitmap icon) {
+        public TaskDescription(String label, Bitmap icon) {
             this(label, icon, 0);
         }
 
         /**
-         * Creates the RecentsActivityValues to the specified values.
+         * Creates the TaskDescription to the specified values.
          *
          * @param label A label and description of the current state of this activity.
          */
-        public RecentsActivityValues(CharSequence label) {
+        public TaskDescription(String label) {
             this(label, null, 0);
         }
 
-        public RecentsActivityValues() {
+        /**
+         * Creates an empty TaskDescription.
+         */
+        public TaskDescription() {
             this(null, null, 0);
         }
 
-        private RecentsActivityValues(Parcel source) {
+        /**
+         * Creates a copy of another TaskDescription.
+         */
+        public TaskDescription(TaskDescription td) {
+            this(td.getLabel(), td.getIcon(), td.getPrimaryColor());
+        }
+
+        private TaskDescription(Parcel source) {
             readFromParcel(source);
         }
 
         /**
-         * Do a shallow copy of another set of activity values.
-         * @hide
+         * @return The label and description of the current state of this task.
          */
-        public void copyFrom(RecentsActivityValues v) {
-            if (v != null) {
-                label = v.label;
-                icon = v.icon;
-                colorPrimary = v.colorPrimary;
-            }
+        public String getLabel() {
+            return mLabel;
+        }
+
+        /**
+         * @return The icon that represents the current state of this task.
+         */
+        public Bitmap getIcon() {
+            return mIcon;
+        }
+
+        /**
+         * @return The color override on the theme's primary color.
+         */
+        public int getPrimaryColor() {
+            return mColorPrimary;
         }
 
         @Override
@@ -545,37 +565,41 @@
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            TextUtils.writeToParcel(label, dest,
-                    Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
-            if (icon == null) {
+            if (mLabel == null) {
                 dest.writeInt(0);
             } else {
                 dest.writeInt(1);
-                icon.writeToParcel(dest, 0);
+                dest.writeString(mLabel);
             }
-            dest.writeInt(colorPrimary);
+            if (mIcon == null) {
+                dest.writeInt(0);
+            } else {
+                dest.writeInt(1);
+                mIcon.writeToParcel(dest, 0);
+            }
+            dest.writeInt(mColorPrimary);
         }
 
         public void readFromParcel(Parcel source) {
-            label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
-            icon = source.readInt() > 0 ? Bitmap.CREATOR.createFromParcel(source) : null;
-            colorPrimary = source.readInt();
+            mLabel = source.readInt() > 0 ? source.readString() : null;
+            mIcon = source.readInt() > 0 ? Bitmap.CREATOR.createFromParcel(source) : null;
+            mColorPrimary = source.readInt();
         }
 
-        public static final Creator<RecentsActivityValues> CREATOR
-                = new Creator<RecentsActivityValues>() {
-            public RecentsActivityValues createFromParcel(Parcel source) {
-                return new RecentsActivityValues(source);
+        public static final Creator<TaskDescription> CREATOR
+                = new Creator<TaskDescription>() {
+            public TaskDescription createFromParcel(Parcel source) {
+                return new TaskDescription(source);
             }
-            public RecentsActivityValues[] newArray(int size) {
-                return new RecentsActivityValues[size];
+            public TaskDescription[] newArray(int size) {
+                return new TaskDescription[size];
             }
         };
 
         @Override
         public String toString() {
-            return "RecentsActivityValues Label: " + label + " Icon: " + icon +
-                    " colorPrimary: " + colorPrimary;
+            return "TaskDescription Label: " + mLabel + " Icon: " + mIcon +
+                    " colorPrimary: " + mColorPrimary;
         }
     }
 
@@ -629,9 +653,11 @@
 
         /**
          * The recent activity values for the highest activity in the stack to have set the values.
-         * {@link Activity#setRecentsActivityValues(android.app.ActivityManager.RecentsActivityValues)}.
+         * {@link Activity#setTaskDescription(android.app.ActivityManager.TaskDescription)}.
+         *
+         * @hide
          */
-        public RecentsActivityValues activityValues;
+        public TaskDescription taskDescription;
 
         public RecentTaskInfo() {
         }
@@ -654,9 +680,9 @@
             ComponentName.writeToParcel(origActivity, dest);
             TextUtils.writeToParcel(description, dest,
                     Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
-            if (activityValues != null) {
+            if (taskDescription != null) {
                 dest.writeInt(1);
-                activityValues.writeToParcel(dest, 0);
+                taskDescription.writeToParcel(dest, 0);
             } else {
                 dest.writeInt(0);
             }
@@ -670,8 +696,8 @@
             baseIntent = source.readInt() > 0 ? Intent.CREATOR.createFromParcel(source) : null;
             origActivity = ComponentName.readFromParcel(source);
             description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
-            activityValues = source.readInt() > 0 ?
-                    RecentsActivityValues.CREATOR.createFromParcel(source) : null;
+            taskDescription = source.readInt() > 0 ?
+                    TaskDescription.CREATOR.createFromParcel(source) : null;
             stackId = source.readInt();
             userId = source.readInt();
         }
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index e704a1c..0f65454 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2158,12 +2158,12 @@
             return true;
         }
 
-        case SET_RECENTS_ACTIVITY_VALUES_TRANSACTION: {
+        case SET_TASK_DESCRIPTION_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             IBinder token = data.readStrongBinder();
-            ActivityManager.RecentsActivityValues values =
-                    ActivityManager.RecentsActivityValues.CREATOR.createFromParcel(data);
-            setRecentsActivityValues(token, values);
+            ActivityManager.TaskDescription values =
+                    ActivityManager.TaskDescription.CREATOR.createFromParcel(data);
+            setTaskDescription(token, values);
             reply.writeNoException();
             return true;
         }
@@ -4967,14 +4967,14 @@
     }
 
     @Override
-    public void setRecentsActivityValues(IBinder token, ActivityManager.RecentsActivityValues values)
+    public void setTaskDescription(IBinder token, ActivityManager.TaskDescription values)
             throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeStrongBinder(token);
         values.writeToParcel(data, 0);
-        mRemote.transact(SET_RECENTS_ACTIVITY_VALUES_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
+        mRemote.transact(SET_TASK_DESCRIPTION_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
         reply.readException();
         data.recycle();
         reply.recycle();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 8753312..8434c2a0 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -440,7 +440,7 @@
     public boolean isInLockTaskMode() throws RemoteException;
 
     /** @hide */
-    public void setRecentsActivityValues(IBinder token, ActivityManager.RecentsActivityValues values)
+    public void setTaskDescription(IBinder token, ActivityManager.TaskDescription values)
             throws RemoteException;
 
     /*
@@ -739,7 +739,7 @@
     int START_LOCK_TASK_BY_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+214;
     int STOP_LOCK_TASK_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+215;
     int IS_IN_LOCK_TASK_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+216;
-    int SET_RECENTS_ACTIVITY_VALUES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+217;
+    int SET_TASK_DESCRIPTION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+217;
     int START_VOICE_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+218;
     int GET_ACTIVITY_OPTIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+219;
     int GET_APP_TASKS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+220;
diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java
index 6dc48b0..4fcf7cd 100644
--- a/core/java/android/app/VoiceInteractor.java
+++ b/core/java/android/app/VoiceInteractor.java
@@ -34,6 +34,7 @@
 
 /**
  * Interface for an {@link Activity} to interact with the user through voice.
+ * @hide
  */
 public class VoiceInteractor {
     static final String TAG = "VoiceInteractor";
diff --git a/core/java/android/app/task/ITaskCallback.aidl b/core/java/android/app/task/ITaskCallback.aidl
index ffa57d1..d8a32fd 100644
--- a/core/java/android/app/task/ITaskCallback.aidl
+++ b/core/java/android/app/task/ITaskCallback.aidl
@@ -34,14 +34,17 @@
      * Immediate callback to the system after sending a start signal, used to quickly detect ANR.
      *
      * @param taskId Unique integer used to identify this task.
+     * @param ongoing True to indicate that the client is processing the task. False if the task is
+     * complete
      */
-    void acknowledgeStartMessage(int taskId);
+    void acknowledgeStartMessage(int taskId, boolean ongoing);
     /**
      * Immediate callback to the system after sending a stop signal, used to quickly detect ANR.
      *
      * @param taskId Unique integer used to identify this task.
+     * @param rescheulde Whether or not to reschedule this task.
      */
-    void acknowledgeStopMessage(int taskId);
+    void acknowledgeStopMessage(int taskId, boolean reschedule);
     /*
      * Tell the task manager that the client is done with its execution, so that it can go on to
      * the next one and stop attributing wakelock time to us etc.
diff --git a/core/java/android/app/task/TaskService.java b/core/java/android/app/task/TaskService.java
index 81333be..ab1a565 100644
--- a/core/java/android/app/task/TaskService.java
+++ b/core/java/android/app/task/TaskService.java
@@ -18,7 +18,6 @@
 
 import android.app.Service;
 import android.content.Intent;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -124,22 +123,20 @@
             switch (msg.what) {
                 case MSG_EXECUTE_TASK:
                     try {
-                        TaskService.this.onStartTask(params);
+                        boolean workOngoing = TaskService.this.onStartTask(params);
+                        ackStartMessage(params, workOngoing);
                     } catch (Exception e) {
                         Log.e(TAG, "Error while executing task: " + params.getTaskId());
                         throw new RuntimeException(e);
-                    } finally {
-                        maybeAckMessageReceived(params, MSG_EXECUTE_TASK);
                     }
                     break;
                 case MSG_STOP_TASK:
                     try {
-                        TaskService.this.onStopTask(params);
+                        boolean ret = TaskService.this.onStopTask(params);
+                        ackStopMessage(params, ret);
                     } catch (Exception e) {
                         Log.e(TAG, "Application unable to handle onStopTask.", e);
                         throw new RuntimeException(e);
-                    } finally {
-                        maybeAckMessageReceived(params, MSG_STOP_TASK);
                     }
                     break;
                 case MSG_TASK_FINISHED:
@@ -162,30 +159,34 @@
             }
         }
 
-        /**
-         * Messages come in on the application's main thread, so rather than run the risk of
-         * waiting for an app that may be doing something foolhardy, we ack to the system after
-         * processing a message. This allows us to throw up an ANR dialogue as quickly as possible.
-         * @param params id of the task we're acking.
-         * @param state Information about what message we're acking.
-         */
-        private void maybeAckMessageReceived(TaskParams params, int state) {
+        private void ackStartMessage(TaskParams params, boolean workOngoing) {
             final ITaskCallback callback = params.getCallback();
             final int taskId = params.getTaskId();
             if (callback != null) {
                 try {
-                    if (state == MSG_EXECUTE_TASK) {
-                        callback.acknowledgeStartMessage(taskId);
-                    } else if (state == MSG_STOP_TASK) {
-                        callback.acknowledgeStopMessage(taskId);
-                    }
+                     callback.acknowledgeStartMessage(taskId, workOngoing);
                 } catch(RemoteException e) {
                     Log.e(TAG, "System unreachable for starting task.");
                 }
             } else {
                 if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d(TAG, state + ": Attempting to ack a task that has already been" +
-                            "processed.");
+                    Log.d(TAG, "Attempting to ack a task that has already been processed.");
+                }
+            }
+        }
+
+        private void ackStopMessage(TaskParams params, boolean reschedule) {
+            final ITaskCallback callback = params.getCallback();
+            final int taskId = params.getTaskId();
+            if (callback != null) {
+                try {
+                    callback.acknowledgeStopMessage(taskId, reschedule);
+                } catch(RemoteException e) {
+                    Log.e(TAG, "System unreachable for stopping task.");
+                }
+            } else {
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Attempting to ack a task that has already been processed.");
                 }
             }
         }
@@ -203,12 +204,14 @@
      *
      * @param params Parameters specifying info about this task, including the extras bundle you
      *               optionally provided at task-creation time.
+     * @return True if your service needs to process the work (on a separate thread). False if
+     * there's no more work to be done for this task.
      */
-    public abstract void onStartTask(TaskParams params);
+    public abstract boolean onStartTask(TaskParams params);
 
     /**
-     * This method is called if your task should be stopped even before you've called
-     * {@link #taskFinished(TaskParams, boolean)}.
+     * This method is called if the system has determined that you must stop execution of your task
+     * even before you've had a chance to call {@link #taskFinished(TaskParams, boolean)}.
      *
      * <p>This will happen if the requirements specified at schedule time are no longer met. For
      * example you may have requested WiFi with
@@ -217,33 +220,27 @@
      * {@link android.content.Task.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its
      * idle maintenance window. You are solely responsible for the behaviour of your application
      * upon receipt of this message; your app will likely start to misbehave if you ignore it. One
-     * repercussion is that the system will cease to hold a wakelock for you.</p>
-     *
-     * <p>After you've done your clean-up you are still expected to call
-     * {@link #taskFinished(TaskParams, boolean)} this will inform the TaskManager that all is well, and
-     * allow you to reschedule your task as it is probably uncompleted. Until you call
-     * taskFinished() you will not receive any newly scheduled tasks with the given task id as the
-     * TaskManager will consider the task to be in an error state.</p>
+     * immediate repercussion is that the system will cease holding a wakelock for you.</p>
      *
      * @param params Parameters specifying info about this task.
      * @return True to indicate to the TaskManager whether you'd like to reschedule this task based
-     * on the criteria provided at task creation-time. False to drop the task. Regardless of the
-     * value returned, your task must stop executing.
+     * on the retry criteria provided at task creation-time. False to drop the task. Regardless of
+     * the value returned, your task must stop executing.
      */
     public abstract boolean onStopTask(TaskParams params);
 
     /**
-     * Callback to inform the TaskManager you have completed execution. This can be called from any
+     * Callback to inform the TaskManager you've finished executing. This can be called from any
      * thread, as it will ultimately be run on your application's main thread. When the system
      * receives this message it will release the wakelock being held.
      * <p>
-     *     You can specify post-execution behaviour to the scheduler here with <code>needsReschedule
-     *     </code>. This will apply a back-off timer to your task based on the default, or what was
-     *     set with {@link android.content.Task.Builder#setBackoffCriteria(long, int)}. The
-     *     original requirements are always honoured even for a backed-off task.
-     *     Note that a task running in idle mode will not be backed-off. Instead what will happen
-     *     is the task will be re-added to the queue and re-executed within a future idle
-     *     maintenance window.
+     *     You can specify post-execution behaviour to the scheduler here with
+     *     <code>needsReschedule </code>. This will apply a back-off timer to your task based on
+     *     the default, or what was set with
+     *     {@link android.content.Task.Builder#setBackoffCriteria(long, int)}. The original
+     *     requirements are always honoured even for a backed-off task. Note that a task running in
+     *     idle mode will not be backed-off. Instead what will happen is the task will be re-added
+     *     to the queue and re-executed within a future idle maintenance window.
      * </p>
      *
      * @param params Parameters specifying system-provided info about this task, this was given to
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index 00fd7ce..b98e5ae 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -81,8 +81,8 @@
  */
 public final class BluetoothSocket implements Closeable {
     private static final String TAG = "BluetoothSocket";
-    private static final boolean DBG = true;
-    private static final boolean VDBG = false;
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
 
     /** @hide */
     public static final int MAX_RFCOMM_CHANNEL = 30;
@@ -185,7 +185,7 @@
         BluetoothSocket as = new BluetoothSocket(this);
         as.mSocketState = SocketState.CONNECTED;
         FileDescriptor[] fds = mSocket.getAncillaryFileDescriptors();
-        if (VDBG) Log.d(TAG, "socket fd passed by stack  fds: " + fds);
+        if (DBG) Log.d(TAG, "socket fd passed by stack  fds: " + fds);
         if(fds == null || fds.length != 1) {
             Log.e(TAG, "socket fd passed from stack failed, fds: " + fds);
             as.close();
@@ -352,24 +352,24 @@
         // read out port number
         try {
             synchronized(this) {
-                if (VDBG) Log.d(TAG, "bindListen(), SocketState: " + mSocketState + ", mPfd: " +
+                if (DBG) Log.d(TAG, "bindListen(), SocketState: " + mSocketState + ", mPfd: " +
                                 mPfd);
                 if(mSocketState != SocketState.INIT) return EBADFD;
                 if(mPfd == null) return -1;
                 FileDescriptor fd = mPfd.getFileDescriptor();
-                if (VDBG) Log.d(TAG, "bindListen(), new LocalSocket ");
+                if (DBG) Log.d(TAG, "bindListen(), new LocalSocket ");
                 mSocket = new LocalSocket(fd);
-                if (VDBG) Log.d(TAG, "bindListen(), new LocalSocket.getInputStream() ");
+                if (DBG) Log.d(TAG, "bindListen(), new LocalSocket.getInputStream() ");
                 mSocketIS = mSocket.getInputStream();
                 mSocketOS = mSocket.getOutputStream();
             }
-            if (VDBG) Log.d(TAG, "bindListen(), readInt mSocketIS: " + mSocketIS);
+            if (DBG) Log.d(TAG, "bindListen(), readInt mSocketIS: " + mSocketIS);
             int channel = readInt(mSocketIS);
             synchronized(this) {
                 if(mSocketState == SocketState.INIT)
                     mSocketState = SocketState.LISTENING;
             }
-            if (VDBG) Log.d(TAG, "channel: " + channel);
+            if (DBG) Log.d(TAG, "channel: " + channel);
             if (mPort == -1) {
                 mPort = channel;
             } // else ASSERT(mPort == channel)
@@ -439,7 +439,7 @@
 
     @Override
     public void close() throws IOException {
-        if (VDBG) Log.d(TAG, "close() in, this: " + this + ", channel: " + mPort + ", state: " + mSocketState);
+        if (DBG) Log.d(TAG, "close() in, this: " + this + ", channel: " + mPort + ", state: " + mSocketState);
         if(mSocketState == SocketState.CLOSED)
             return;
         else
@@ -449,10 +449,10 @@
                  if(mSocketState == SocketState.CLOSED)
                     return;
                  mSocketState = SocketState.CLOSED;
-                 if (VDBG) Log.d(TAG, "close() this: " + this + ", channel: " + mPort + ", mSocketIS: " + mSocketIS +
+                 if (DBG) Log.d(TAG, "close() this: " + this + ", channel: " + mPort + ", mSocketIS: " + mSocketIS +
                         ", mSocketOS: " + mSocketOS + "mSocket: " + mSocket);
                  if(mSocket != null) {
-                    if (VDBG) Log.d(TAG, "Closing mSocket: " + mSocket);
+                    if (DBG) Log.d(TAG, "Closing mSocket: " + mSocket);
                     mSocket.shutdownInput();
                     mSocket.shutdownOutput();
                     mSocket.close();
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 076f657..fed63d2 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2809,6 +2809,7 @@
      * An activity that supports this category must be prepared to run with
      * no UI shown at all (though in some case it may have a UI shown), and
      * rely on {@link android.app.VoiceInteractor} to interact with the user.
+     * @hide
      */
     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
     public static final String CATEGORY_VOICE = "android.intent.category.VOICE";
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 1127fe5..7cc6d1d 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -30,6 +30,8 @@
  * through the {@link CameraManager CameraManager}
  * interface in addition to through the CameraDevice interface.</p>
  *
+ * <p>{@link CameraCharacteristics} objects are immutable.</p>
+ *
  * @see CameraDevice
  * @see CameraManager
  */
@@ -47,6 +49,14 @@
         mProperties = properties;
     }
 
+    /**
+     * Returns a copy of the underlying {@link CameraMetadataNative}.
+     * @hide
+     */
+    public CameraMetadataNative getNativeCopy() {
+        return new CameraMetadataNative(mProperties);
+    }
+
     @Override
     public <T> T get(Key<T> key) {
         return mProperties.get(key);
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index d79f4b0..f91fcb9 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -33,6 +33,8 @@
  * capture. The result also includes additional metadata about the state of the
  * camera device during the capture.</p>
  *
+ * <p>{@link CameraCharacteristics} objects are immutable.</p>
+ *
  */
 public final class CaptureResult extends CameraMetadata {
 
@@ -58,6 +60,14 @@
         mSequenceId = sequenceId;
     }
 
+    /**
+     * Returns a copy of the underlying {@link CameraMetadataNative}.
+     * @hide
+     */
+    public CameraMetadataNative getNativeCopy() {
+        return new CameraMetadataNative(mResults);
+    }
+
     @Override
     public <T> T get(Key<T> key) {
         return mResults.get(key);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d063168..cd28085 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -773,13 +773,24 @@
      * Activity Action: Show Device Name Settings.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you safeguard
-     * against ithis.
+     * against this.
      *
      * @hide
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String DEVICE_NAME_SETTINGS = "android.settings.DEVICE_NAME";
 
+    /**
+     * Activity Action: Show pairing settings.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you safeguard
+     * against this.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_PAIRING_SETTINGS = "android.settings.PAIRING_SETTINGS";
+
     // End of Intent actions for Settings
 
     /**
diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java
index bb40eec..98f70f40 100644
--- a/core/java/android/service/trust/TrustAgentService.java
+++ b/core/java/android/service/trust/TrustAgentService.java
@@ -16,12 +16,17 @@
 
 package android.service.trust;
 
+import android.Manifest;
 import android.annotation.SdkConstant;
 import android.app.Service;
+import android.content.ComponentName;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.Log;
 import android.util.Slog;
 
 /**
@@ -83,6 +88,22 @@
         };
     };
 
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        ComponentName component = new ComponentName(this, getClass());
+        try {
+            ServiceInfo serviceInfo = getPackageManager().getServiceInfo(component, 0 /* flags */);
+            if (!Manifest.permission.BIND_TRUST_AGENT.equals(serviceInfo.permission)) {
+                throw new IllegalStateException(component.flattenToShortString()
+                        + " is not declared with the permission "
+                        + "\"" + Manifest.permission.BIND_TRUST_AGENT + "\"");
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Can't get ServiceInfo for " + component.toShortString());
+        }
+    }
+
     /**
      * Called when the user attempted to authenticate on the device.
      *
diff --git a/core/java/android/service/voice/package.html b/core/java/android/service/voice/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/core/java/android/service/voice/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+    {@hide}
+</body>
+</html>
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 852fce5..b8e1b89 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -639,8 +639,13 @@
      * Wakes up the device.  Behaves somewhat like {@link #KEYCODE_POWER} but it
      * has no effect if the device is already awake. */
     public static final int KEYCODE_WAKEUP          = 224;
+    /** Key code constant: Pairing key.
+     * Initiates peripheral pairing mode. Useful for pairing remote control
+     * devices or game controllers, especially if no other input mode is
+     * available. */
+    public static final int KEYCODE_PAIRING         = 225;
 
-    private static final int LAST_KEYCODE = KEYCODE_WAKEUP;
+    private static final int LAST_KEYCODE = KEYCODE_PAIRING;
 
     // NOTE: If you add a new keycode here you must also add it to:
     //  isSystem()
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9b09d85..eed6412 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -111,6 +111,7 @@
     private static final boolean DEBUG_IMF = false || LOCAL_LOGV;
     private static final boolean DEBUG_CONFIGURATION = false || LOCAL_LOGV;
     private static final boolean DEBUG_FPS = false;
+    private static final boolean DEBUG_INPUT_STAGES = false || LOCAL_LOGV;
 
     /**
      * Set this system property to true to force the view hierarchy to render
@@ -668,6 +669,11 @@
     public void detachFunctor(long functor) {
         // TODO: Make the resize buffer some other way to not need this block
         mBlockResizeBuffer = true;
+        if (mAttachInfo.mHardwareRenderer != null) {
+            // Fence so that any pending invokeFunctor() messages will be processed
+            // before we return from detachFunctor.
+            mAttachInfo.mHardwareRenderer.fence();
+        }
     }
 
     public boolean invokeFunctor(long functor, boolean waitForCompletion) {
@@ -3481,6 +3487,9 @@
          * Called when an event is being delivered to the next stage.
          */
         protected void onDeliverToNext(QueuedInputEvent q) {
+            if (DEBUG_INPUT_STAGES) {
+                Log.v(TAG, "Done with " + getClass().getSimpleName() + ". " + q);
+            }
             if (mNext != null) {
                 mNext.deliver(q);
             } else {
@@ -5515,6 +5524,37 @@
 
             return false;
         }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder("QueuedInputEvent{flags=");
+            boolean hasPrevious = false;
+            hasPrevious = flagToString("DELIVER_POST_IME", FLAG_DELIVER_POST_IME, hasPrevious, sb);
+            hasPrevious = flagToString("DEFERRED", FLAG_DEFERRED, hasPrevious, sb);
+            hasPrevious = flagToString("FINISHED", FLAG_FINISHED, hasPrevious, sb);
+            hasPrevious = flagToString("FINISHED_HANDLED", FLAG_FINISHED_HANDLED, hasPrevious, sb);
+            hasPrevious = flagToString("RESYNTHESIZED", FLAG_RESYNTHESIZED, hasPrevious, sb);
+            hasPrevious = flagToString("UNHANDLED", FLAG_UNHANDLED, hasPrevious, sb);
+            if (!hasPrevious) {
+                sb.append("0");
+            }
+            sb.append(", hasNextQueuedEvent=" + (mEvent != null ? "true" : "false"));
+            sb.append(", hasInputEventReceiver=" + (mReceiver != null ? "true" : "false"));
+            sb.append(", mEvent=" + mEvent + "}");
+            return sb.toString();
+        }
+
+        private boolean flagToString(String name, int flag,
+                boolean hasPrevious, StringBuilder sb) {
+            if ((mFlags & flag) != 0) {
+                if (hasPrevious) {
+                    sb.append("|");
+                }
+                sb.append(name);
+                return true;
+            }
+            return hasPrevious;
+        }
     }
 
     private QueuedInputEvent obtainQueuedInputEvent(InputEvent event,
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 0ad2ab2..99bbe39 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -228,6 +228,7 @@
 	libz \
 	libaudioutils \
 	libpdfrenderer \
+	libimg_utils \
 
 ifeq ($(USE_OPENGL_RENDERER),true)
 	LOCAL_SHARED_LIBRARIES += libhwui
diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp
index fa2cfe3..3312109 100644
--- a/core/jni/android_hardware_camera2_CameraMetadata.cpp
+++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp
@@ -30,6 +30,7 @@
 #include "JNIHelp.h"
 #include "android_os_Parcel.h"
 #include "android_runtime/AndroidRuntime.h"
+#include "android_runtime/android_hardware_camera2_CameraMetadata.h"
 
 #include <binder/IServiceManager.h>
 #include <camera/CameraMetadata.h>
@@ -57,6 +58,31 @@
 
 static fields_t fields;
 
+namespace android {
+
+status_t CameraMetadata_getNativeMetadata(JNIEnv* env, jobject thiz,
+        /*out*/CameraMetadata* metadata) {
+    if (!thiz) {
+        ALOGE("%s: Invalid java metadata object.", __FUNCTION__);
+        return BAD_VALUE;
+    }
+
+    if (!metadata) {
+        ALOGE("%s: Invalid output metadata object.", __FUNCTION__);
+        return BAD_VALUE;
+    }
+    CameraMetadata* nativePtr = reinterpret_cast<CameraMetadata*>(env->GetLongField(thiz,
+            fields.metadata_ptr));
+    if (nativePtr == NULL) {
+        ALOGE("%s: Invalid native pointer in java metadata object.", __FUNCTION__);
+        return BAD_VALUE;
+    }
+    *metadata = *nativePtr;
+    return OK;
+}
+
+} /*namespace android*/
+
 namespace {
 struct Helpers {
     static size_t getTypeSize(uint8_t type) {
diff --git a/core/jni/android_media_AudioErrors.h b/core/jni/android_media_AudioErrors.h
new file mode 100644
index 0000000..4907830
--- /dev/null
+++ b/core/jni/android_media_AudioErrors.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_AUDIOERRORS_H
+#define ANDROID_MEDIA_AUDIOERRORS_H
+
+#include <utils/Errors.h>
+
+namespace android {
+// status codes used by JAVA APIs. Translation from native error codes is done by
+// nativeToJavaStatus()
+// must be kept in sync with values in
+// frameworks/base/media/java/android/media/AudioSystem.java.
+enum {
+    AUDIO_JAVA_SUCCESS            = 0,
+    AUDIO_JAVA_ERROR              = -1,
+    AUDIO_JAVA_BAD_VALUE          = -2,
+    AUDIO_JAVA_INVALID_OPERATION  = -3,
+    AUDIO_JAVA_PERMISSION_DENIED  = -4,
+    AUDIO_JAVA_NO_INIT            = -5,
+    AUDIO_JAVA_DEAD_OBJECT        = -6,
+};
+
+static inline jint nativeToJavaStatus(status_t status) {
+    switch (status) {
+    case NO_ERROR:
+        return AUDIO_JAVA_SUCCESS;
+    case BAD_VALUE:
+        return AUDIO_JAVA_BAD_VALUE;
+    case INVALID_OPERATION:
+        return AUDIO_JAVA_INVALID_OPERATION;
+    case PERMISSION_DENIED:
+        return AUDIO_JAVA_PERMISSION_DENIED;
+    case NO_INIT:
+        return AUDIO_JAVA_NO_INIT;
+    case DEAD_OBJECT:
+        return AUDIO_JAVA_DEAD_OBJECT;
+    default:
+        return AUDIO_JAVA_ERROR;
+    }
+}
+}; // namespace android
+#endif // ANDROID_MEDIA_AUDIOERRORS_H
diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp
index 09bdc61..a54eba1 100644
--- a/core/jni/android_media_AudioRecord.cpp
+++ b/core/jni/android_media_AudioRecord.cpp
@@ -27,6 +27,7 @@
 #include <media/AudioRecord.h>
 
 #include "android_media_AudioFormat.h"
+#include "android_media_AudioErrors.h"
 
 // ----------------------------------------------------------------------------
 
@@ -55,29 +56,12 @@
 
 // ----------------------------------------------------------------------------
 
-#define AUDIORECORD_SUCCESS                         0
-#define AUDIORECORD_ERROR                           -1
-#define AUDIORECORD_ERROR_BAD_VALUE                 -2
-#define AUDIORECORD_ERROR_INVALID_OPERATION         -3
 #define AUDIORECORD_ERROR_SETUP_ZEROFRAMECOUNT      -16
 #define AUDIORECORD_ERROR_SETUP_INVALIDCHANNELMASK -17
 #define AUDIORECORD_ERROR_SETUP_INVALIDFORMAT       -18
 #define AUDIORECORD_ERROR_SETUP_INVALIDSOURCE       -19
 #define AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED    -20
 
-jint android_media_translateRecorderErrorCode(int code) {
-    switch (code) {
-    case NO_ERROR:
-        return AUDIORECORD_SUCCESS;
-    case BAD_VALUE:
-        return AUDIORECORD_ERROR_BAD_VALUE;
-    case INVALID_OPERATION:
-        return AUDIORECORD_ERROR_INVALID_OPERATION;
-    default:
-        return AUDIORECORD_ERROR;
-    }
-}
-
 // ----------------------------------------------------------------------------
 static void recorderCallback(int event, void* user, void *info) {
 
@@ -197,13 +181,13 @@
 
     if (jSession == NULL) {
         ALOGE("Error creating AudioRecord: invalid session ID pointer");
-        return (jint) AUDIORECORD_ERROR;
+        return (jint) AUDIO_JAVA_ERROR;
     }
 
     jint* nSession = (jint *) env->GetPrimitiveArrayCritical(jSession, NULL);
     if (nSession == NULL) {
         ALOGE("Error creating AudioRecord: Error retrieving session id pointer");
-        return (jint) AUDIORECORD_ERROR;
+        return (jint) AUDIO_JAVA_ERROR;
     }
     int sessionId = nSession[0];
     env->ReleasePrimitiveArrayCritical(jSession, nSession, 0);
@@ -259,7 +243,7 @@
     // of the Java object (in mNativeCallbackCookie) so we can free the memory in finalize()
     env->SetLongField(thiz, javaAudioRecordFields.nativeCallbackCookie, (jlong)lpCallbackData);
 
-    return (jint) AUDIORECORD_SUCCESS;
+    return (jint) AUDIO_JAVA_SUCCESS;
 
     // failure:
 native_init_failure:
@@ -280,10 +264,10 @@
     sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
     if (lpRecorder == NULL ) {
         jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return (jint) AUDIORECORD_ERROR;
+        return (jint) AUDIO_JAVA_ERROR;
     }
 
-    return (jint) android_media_translateRecorderErrorCode(
+    return nativeToJavaStatus(
             lpRecorder->start((AudioSystem::sync_event_t)event, triggerSession));
 }
 
@@ -383,7 +367,7 @@
     env->ReleaseByteArrayElements(javaAudioData, recordBuff, 0);
 
     if (readSize < 0) {
-        readSize = AUDIORECORD_ERROR_INVALID_OPERATION;
+        readSize = (jint)AUDIO_JAVA_INVALID_OPERATION;
     }
     return (jint) readSize;
 }
@@ -428,7 +412,7 @@
     env->ReleaseShortArrayElements(javaAudioData, recordBuff, 0);
 
     if (readSize < 0) {
-        readSize = AUDIORECORD_ERROR_INVALID_OPERATION;
+        readSize = (jint)AUDIO_JAVA_INVALID_OPERATION;
     } else {
         readSize /= sizeof(short);
     }
@@ -461,7 +445,7 @@
     ssize_t readSize = lpRecorder->read(nativeFromJavaBuf,
                                    capacity < sizeInBytes ? capacity : sizeInBytes);
     if (readSize < 0) {
-        readSize = AUDIORECORD_ERROR_INVALID_OPERATION;
+        readSize = (jint)AUDIO_JAVA_INVALID_OPERATION;
     }
     return (jint)readSize;
 }
@@ -475,9 +459,9 @@
     if (lpRecorder == NULL) {
         jniThrowException(env, "java/lang/IllegalStateException",
             "Unable to retrieve AudioRecord pointer for setMarkerPosition()");
-        return AUDIORECORD_ERROR;
+        return (jint)AUDIO_JAVA_ERROR;
     }
-    return android_media_translateRecorderErrorCode( lpRecorder->setMarkerPosition(markerPos) );
+    return nativeToJavaStatus( lpRecorder->setMarkerPosition(markerPos) );
 }
 
 
@@ -490,7 +474,7 @@
     if (lpRecorder == NULL) {
         jniThrowException(env, "java/lang/IllegalStateException",
             "Unable to retrieve AudioRecord pointer for getMarkerPosition()");
-        return AUDIORECORD_ERROR;
+        return (jint)AUDIO_JAVA_ERROR;
     }
     lpRecorder->getMarkerPosition(&markerPos);
     return (jint)markerPos;
@@ -506,9 +490,9 @@
     if (lpRecorder == NULL) {
         jniThrowException(env, "java/lang/IllegalStateException",
             "Unable to retrieve AudioRecord pointer for setPositionUpdatePeriod()");
-        return AUDIORECORD_ERROR;
+        return (jint)AUDIO_JAVA_ERROR;
     }
-    return android_media_translateRecorderErrorCode( lpRecorder->setPositionUpdatePeriod(period) );
+    return nativeToJavaStatus( lpRecorder->setPositionUpdatePeriod(period) );
 }
 
 
@@ -521,7 +505,7 @@
     if (lpRecorder == NULL) {
         jniThrowException(env, "java/lang/IllegalStateException",
             "Unable to retrieve AudioRecord pointer for getPositionUpdatePeriod()");
-        return AUDIORECORD_ERROR;
+        return (jint)AUDIO_JAVA_ERROR;
     }
     lpRecorder->getPositionUpdatePeriod(&period);
     return (jint)period;
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 5fcb5f3..e548e91 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -32,6 +32,7 @@
 #include <binder/MemoryBase.h>
 
 #include "android_media_AudioFormat.h"
+#include "android_media_AudioErrors.h"
 
 // ----------------------------------------------------------------------------
 
@@ -94,31 +95,12 @@
 // ----------------------------------------------------------------------------
 #define DEFAULT_OUTPUT_SAMPLE_RATE   44100
 
-#define AUDIOTRACK_SUCCESS                         0
-#define AUDIOTRACK_ERROR                           -1
-#define AUDIOTRACK_ERROR_BAD_VALUE                 -2
-#define AUDIOTRACK_ERROR_INVALID_OPERATION         -3
 #define AUDIOTRACK_ERROR_SETUP_AUDIOSYSTEM         -16
 #define AUDIOTRACK_ERROR_SETUP_INVALIDCHANNELMASK  -17
 #define AUDIOTRACK_ERROR_SETUP_INVALIDFORMAT       -18
 #define AUDIOTRACK_ERROR_SETUP_INVALIDSTREAMTYPE   -19
 #define AUDIOTRACK_ERROR_SETUP_NATIVEINITFAILED    -20
 
-
-jint android_media_translateErrorCode(int code) {
-    switch (code) {
-    case NO_ERROR:
-        return AUDIOTRACK_SUCCESS;
-    case BAD_VALUE:
-        return AUDIOTRACK_ERROR_BAD_VALUE;
-    case INVALID_OPERATION:
-        return AUDIOTRACK_ERROR_INVALID_OPERATION;
-    default:
-        return AUDIOTRACK_ERROR;
-    }
-}
-
-
 // ----------------------------------------------------------------------------
 static void audioCallback(int event, void* user, void *info) {
 
@@ -249,13 +231,13 @@
 
     if (jSession == NULL) {
         ALOGE("Error creating AudioTrack: invalid session ID pointer");
-        return (jint) AUDIOTRACK_ERROR;
+        return (jint) AUDIO_JAVA_ERROR;
     }
 
     jint* nSession = (jint *) env->GetPrimitiveArrayCritical(jSession, NULL);
     if (nSession == NULL) {
         ALOGE("Error creating AudioTrack: Error retrieving session id pointer");
-        return (jint) AUDIOTRACK_ERROR;
+        return (jint) AUDIO_JAVA_ERROR;
     }
     int sessionId = nSession[0];
     env->ReleasePrimitiveArrayCritical(jSession, nSession, 0);
@@ -346,7 +328,7 @@
     //ALOGV("storing lpJniStorage: %x\n", (long)lpJniStorage);
     env->SetLongField(thiz, javaAudioTrackFields.jniData, (jlong)lpJniStorage);
 
-    return (jint) AUDIOTRACK_SUCCESS;
+    return (jint) AUDIO_JAVA_SUCCESS;
 
     // failures:
 native_init_failure:
@@ -596,7 +578,7 @@
     ScopedBytesRO bytes(env, javaBytes);
     if (bytes.get() == NULL) {
         ALOGE("Error retrieving source of audio data to play, can't play");
-        return AUDIOTRACK_ERROR_BAD_VALUE;
+        return (jint)AUDIO_JAVA_BAD_VALUE;
     }
 
     jint written = writeToTrack(lpTrack, javaAudioFormat, bytes.get(), byteOffset,
@@ -695,7 +677,7 @@
     if (lpTrack == NULL) {
         jniThrowException(env, "java/lang/IllegalStateException",
             "Unable to retrieve AudioTrack pointer for frameCount()");
-        return AUDIOTRACK_ERROR;
+        return (jint)AUDIO_JAVA_ERROR;
     }
 
     return lpTrack->frameCount();
@@ -709,9 +691,9 @@
     if (lpTrack == NULL) {
         jniThrowException(env, "java/lang/IllegalStateException",
             "Unable to retrieve AudioTrack pointer for setSampleRate()");
-        return AUDIOTRACK_ERROR;
+        return (jint)AUDIO_JAVA_ERROR;
     }
-    return android_media_translateErrorCode(lpTrack->setSampleRate(sampleRateInHz));
+    return nativeToJavaStatus(lpTrack->setSampleRate(sampleRateInHz));
 }
 
 
@@ -721,7 +703,7 @@
     if (lpTrack == NULL) {
         jniThrowException(env, "java/lang/IllegalStateException",
             "Unable to retrieve AudioTrack pointer for getSampleRate()");
-        return AUDIOTRACK_ERROR;
+        return (jint)AUDIO_JAVA_ERROR;
     }
     return (jint) lpTrack->getSampleRate();
 }
@@ -734,9 +716,9 @@
     if (lpTrack == NULL) {
         jniThrowException(env, "java/lang/IllegalStateException",
             "Unable to retrieve AudioTrack pointer for setMarkerPosition()");
-        return AUDIOTRACK_ERROR;
+        return (jint)AUDIO_JAVA_ERROR;
     }
-    return android_media_translateErrorCode( lpTrack->setMarkerPosition(markerPos) );
+    return nativeToJavaStatus( lpTrack->setMarkerPosition(markerPos) );
 }
 
 
@@ -748,7 +730,7 @@
     if (lpTrack == NULL) {
         jniThrowException(env, "java/lang/IllegalStateException",
             "Unable to retrieve AudioTrack pointer for getMarkerPosition()");
-        return AUDIOTRACK_ERROR;
+        return (jint)AUDIO_JAVA_ERROR;
     }
     lpTrack->getMarkerPosition(&markerPos);
     return (jint)markerPos;
@@ -762,9 +744,9 @@
     if (lpTrack == NULL) {
         jniThrowException(env, "java/lang/IllegalStateException",
             "Unable to retrieve AudioTrack pointer for setPositionUpdatePeriod()");
-        return AUDIOTRACK_ERROR;
+        return (jint)AUDIO_JAVA_ERROR;
     }
-    return android_media_translateErrorCode( lpTrack->setPositionUpdatePeriod(period) );
+    return nativeToJavaStatus( lpTrack->setPositionUpdatePeriod(period) );
 }
 
 
@@ -776,7 +758,7 @@
     if (lpTrack == NULL) {
         jniThrowException(env, "java/lang/IllegalStateException",
             "Unable to retrieve AudioTrack pointer for getPositionUpdatePeriod()");
-        return AUDIOTRACK_ERROR;
+        return (jint)AUDIO_JAVA_ERROR;
     }
     lpTrack->getPositionUpdatePeriod(&period);
     return (jint)period;
@@ -790,9 +772,9 @@
     if (lpTrack == NULL) {
         jniThrowException(env, "java/lang/IllegalStateException",
             "Unable to retrieve AudioTrack pointer for setPosition()");
-        return AUDIOTRACK_ERROR;
+        return (jint)AUDIO_JAVA_ERROR;
     }
-    return android_media_translateErrorCode( lpTrack->setPosition(position) );
+    return nativeToJavaStatus( lpTrack->setPosition(position) );
 }
 
 
@@ -804,7 +786,7 @@
     if (lpTrack == NULL) {
         jniThrowException(env, "java/lang/IllegalStateException",
             "Unable to retrieve AudioTrack pointer for getPosition()");
-        return AUDIOTRACK_ERROR;
+        return (jint)AUDIO_JAVA_ERROR;
     }
     lpTrack->getPosition(&position);
     return (jint)position;
@@ -818,7 +800,7 @@
     if (lpTrack == NULL) {
         jniThrowException(env, "java/lang/IllegalStateException",
             "Unable to retrieve AudioTrack pointer for latency()");
-        return AUDIOTRACK_ERROR;
+        return (jint)AUDIO_JAVA_ERROR;
     }
     return (jint)lpTrack->latency();
 }
@@ -830,7 +812,7 @@
 
     if (lpTrack == NULL) {
         ALOGE("Unable to retrieve AudioTrack pointer for getTimestamp()");
-        return AUDIOTRACK_ERROR;
+        return (jint)AUDIO_JAVA_ERROR;
     }
     AudioTimestamp timestamp;
     status_t status = lpTrack->getTimestamp(timestamp);
@@ -838,13 +820,13 @@
         jlong* nTimestamp = (jlong *) env->GetPrimitiveArrayCritical(jTimestamp, NULL);
         if (nTimestamp == NULL) {
             ALOGE("Unable to get array for getTimestamp()");
-            return AUDIOTRACK_ERROR;
+            return (jint)AUDIO_JAVA_ERROR;
         }
         nTimestamp[0] = (jlong) timestamp.mPosition;
         nTimestamp[1] = (jlong) ((timestamp.mTime.tv_sec * 1000000000LL) + timestamp.mTime.tv_nsec);
         env->ReleasePrimitiveArrayCritical(jTimestamp, nTimestamp, 0);
     }
-    return (jint) android_media_translateErrorCode(status);
+    return (jint) nativeToJavaStatus(status);
 }
 
 
@@ -855,9 +837,9 @@
     if (lpTrack == NULL) {
         jniThrowException(env, "java/lang/IllegalStateException",
             "Unable to retrieve AudioTrack pointer for setLoop()");
-        return AUDIOTRACK_ERROR;
+        return (jint)AUDIO_JAVA_ERROR;
     }
-    return android_media_translateErrorCode( lpTrack->setLoop(loopStart, loopEnd, loopCount) );
+    return nativeToJavaStatus( lpTrack->setLoop(loopStart, loopEnd, loopCount) );
 }
 
 
@@ -867,9 +849,9 @@
     if (lpTrack == NULL) {
         jniThrowException(env, "java/lang/IllegalStateException",
             "Unable to retrieve AudioTrack pointer for reload()");
-        return AUDIOTRACK_ERROR;
+        return (jint)AUDIO_JAVA_ERROR;
     }
-    return android_media_translateErrorCode( lpTrack->reload() );
+    return nativeToJavaStatus( lpTrack->reload() );
 }
 
 
@@ -952,9 +934,9 @@
     if (lpTrack == NULL) {
         jniThrowException(env, "java/lang/IllegalStateException",
             "Unable to retrieve AudioTrack pointer for attachAuxEffect()");
-        return AUDIOTRACK_ERROR;
+        return (jint)AUDIO_JAVA_ERROR;
     }
-    return android_media_translateErrorCode( lpTrack->attachAuxEffect(effectId) );
+    return nativeToJavaStatus( lpTrack->attachAuxEffect(effectId) );
 }
 
 // ----------------------------------------------------------------------------
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4eac802..f262a93 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2056,7 +2056,7 @@
         android:protectionLevel="signature|system" />
 
     <!-- Must be required by a {@link android.service.voice.VoiceInteractionService},
-         to ensure that only the system can bind to it. -->
+         to ensure that only the system can bind to it. @hide -->
     <permission android:name="android.permission.BIND_VOICE_INTERACTION"
         android:label="@string/permlab_bindVoiceInteraction"
         android:description="@string/permdesc_bindVoiceInteraction"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 1d35c84..3746780 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1672,6 +1672,7 @@
         <enum name="KEYCODE_MEDIA_AUDIO_TRACK" value="222" />
         <enum name="KEYCODE_MEDIA_SLEEP" value="223" />
         <enum name="KEYCODE_MEDIA_WAKEUP" value="224" />
+        <enum name="KEYCODE_PAIRING" value="225" />
     </attr>
 
     <!-- ***************************************************************** -->
@@ -6343,8 +6344,9 @@
     <!-- Use <code>voice-interaction-service</code> as the root tag of the XML resource that
          describes a {@link android.service.voice.VoiceInteractionService}, which is referenced from
          its {@link android.service.voice.VoiceInteractionService#SERVICE_META_DATA} meta-data entry.
-         Described here are the attributes that can be included in that tag. -->
+         Described here are the attributes that can be included in that tag. @hide -->
     <declare-styleable name="VoiceInteractionService">
+        <!-- @hide -->
         <attr name="sessionService" format="string" />
         <attr name="settingsActivity" />
     </declare-styleable>
diff --git a/include/android_runtime/android_hardware_camera2_CameraMetadata.h b/include/android_runtime/android_hardware_camera2_CameraMetadata.h
new file mode 100644
index 0000000..3c76ca5
--- /dev/null
+++ b/include/android_runtime/android_hardware_camera2_CameraMetadata.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_CAMERA2_CAMERAMETADATA_JNI_H
+#define ANDROID_HARDWARE_CAMERA2_CAMERAMETADATA_JNI_H
+
+#include <camera/CameraMetadata.h>
+
+#include "jni.h"
+
+namespace android {
+
+/**
+ * Copies the native metadata for this java object into the given output CameraMetadata object.
+ */
+status_t CameraMetadata_getNativeMetadata(JNIEnv* env, jobject thiz,
+               /*out*/CameraMetadata* metadata);
+
+} /*namespace android*/
+
+#endif /*ANDROID_HARDWARE_CAMERA2_CAMERAMETADATA_JNI_H*/
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 384e120..d4e85c8 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -61,25 +61,25 @@
      */
     public static final int RECORDSTATE_RECORDING = 3;// matches SL_RECORDSTATE_RECORDING
 
-    // Error codes:
-    // to keep in sync with frameworks/base/core/jni/android_media_AudioRecord.cpp
     /**
      * Denotes a successful operation.
      */
-    public static final int SUCCESS                 = 0;
+    public  static final int SUCCESS                               = AudioSystem.SUCCESS;
     /**
      * Denotes a generic operation failure.
      */
-    public static final int ERROR                   = -1;
+    public  static final int ERROR                                 = AudioSystem.ERROR;
     /**
      * Denotes a failure due to the use of an invalid value.
      */
-    public static final int ERROR_BAD_VALUE         = -2;
+    public  static final int ERROR_BAD_VALUE                       = AudioSystem.BAD_VALUE;
     /**
      * Denotes a failure due to the improper use of a method.
      */
-    public static final int ERROR_INVALID_OPERATION = -3;
+    public  static final int ERROR_INVALID_OPERATION               = AudioSystem.INVALID_OPERATION;
 
+    // Error codes:
+    // to keep in sync with frameworks/base/core/jni/android_media_AudioRecord.cpp
     private static final int AUDIORECORD_ERROR_SETUP_ZEROFRAMECOUNT      = -16;
     private static final int AUDIORECORD_ERROR_SETUP_INVALIDCHANNELMASK  = -17;
     private static final int AUDIORECORD_ERROR_SETUP_INVALIDFORMAT       = -18;
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 327c10c..5ddb198 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -199,6 +199,17 @@
         }
     }
 
+    /*
+     * Error codes used by public APIs (AudioTrack, AudioRecord, AudioManager ...)
+     * Must be kept in sync with frameworks/base/core/jni/android_media_AudioErrors.h
+     */
+    public static final int SUCCESS            = 0;
+    public static final int ERROR              = -1;
+    public static final int BAD_VALUE          = -2;
+    public static final int INVALID_OPERATION  = -3;
+    public static final int PERMISSION_DENIED  = -4;
+    public static final int NO_INIT            = -5;
+    public static final int DEAD_OBJECT        = -6;
 
     /*
      * AudioPolicyService methods
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 1a64cff..1baaaa4 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -125,25 +125,25 @@
      */
     public static final int STATE_NO_STATIC_DATA = 2;
 
-    // Error codes:
-    // to keep in sync with frameworks/base/core/jni/android_media_AudioTrack.cpp
     /**
      * Denotes a successful operation.
      */
-    public  static final int SUCCESS                               = 0;
+    public  static final int SUCCESS                               = AudioSystem.SUCCESS;
     /**
      * Denotes a generic operation failure.
      */
-    public  static final int ERROR                                 = -1;
+    public  static final int ERROR                                 = AudioSystem.ERROR;
     /**
      * Denotes a failure due to the use of an invalid value.
      */
-    public  static final int ERROR_BAD_VALUE                       = -2;
+    public  static final int ERROR_BAD_VALUE                       = AudioSystem.BAD_VALUE;
     /**
      * Denotes a failure due to the improper use of a method.
      */
-    public  static final int ERROR_INVALID_OPERATION               = -3;
+    public  static final int ERROR_INVALID_OPERATION               = AudioSystem.INVALID_OPERATION;
 
+    // Error codes:
+    // to keep in sync with frameworks/base/core/jni/android_media_AudioTrack.cpp
     private static final int ERROR_NATIVESETUP_AUDIOSYSTEM         = -16;
     private static final int ERROR_NATIVESETUP_INVALIDCHANNELMASK  = -17;
     private static final int ERROR_NATIVESETUP_INVALIDFORMAT       = -18;
diff --git a/media/java/android/media/DngCreator.java b/media/java/android/media/DngCreator.java
index b2a38ab..76c6d46 100644
--- a/media/java/android/media/DngCreator.java
+++ b/media/java/android/media/DngCreator.java
@@ -17,9 +17,12 @@
 package android.media;
 
 import android.graphics.Bitmap;
+import android.graphics.ImageFormat;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.impl.CameraMetadataNative;
 import android.location.Location;
+import android.util.Size;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -50,7 +53,7 @@
  * Adobe DNG 1.4.0.0 specification</a>.
  * </p>
  */
-public final class DngCreator {
+public final class DngCreator implements AutoCloseable {
 
     /**
      * Create a new DNG object.
@@ -68,7 +71,12 @@
      *          {@link android.hardware.camera2.CameraCharacteristics}.
      * @param metadata a metadata object to generate tags from.
      */
-    public DngCreator(CameraCharacteristics characteristics, CaptureResult metadata) {/*TODO*/}
+    public DngCreator(CameraCharacteristics characteristics, CaptureResult metadata) {
+        if (characteristics == null || metadata == null) {
+            throw new NullPointerException("Null argument to DngCreator constructor");
+        }
+        nativeInit(characteristics.getNativeCopy(), metadata.getNativeCopy());
+    }
 
     /**
      * Set the orientation value to write.
@@ -92,6 +100,13 @@
      * @return this {@link #DngCreator} object.
      */
     public DngCreator setOrientation(int orientation) {
+
+        if (orientation < ExifInterface.ORIENTATION_UNDEFINED ||
+                orientation > ExifInterface.ORIENTATION_ROTATE_270) {
+            throw new IllegalArgumentException("Orientation " + orientation +
+                    " is not a valid EXIF orientation value");
+        }
+        nativeSetOrientation(orientation);
         return this;
     }
 
@@ -111,6 +126,20 @@
      * @return this {@link #DngCreator} object.
      */
     public DngCreator setThumbnail(Bitmap pixels) {
+        if (pixels == null) {
+            throw new NullPointerException("Null argument to setThumbnail");
+        }
+
+        Bitmap.Config config = pixels.getConfig();
+
+        if (config != Bitmap.Config.ARGB_8888) {
+            pixels = pixels.copy(Bitmap.Config.ARGB_8888, false);
+            if (pixels == null) {
+                throw new IllegalArgumentException("Unsupported Bitmap format " + config);
+            }
+            nativeSetThumbnailBitmap(pixels);
+        }
+
         return this;
     }
 
@@ -130,6 +159,21 @@
      * @return this {@link #DngCreator} object.
      */
     public DngCreator setThumbnail(Image pixels) {
+        if (pixels == null) {
+            throw new NullPointerException("Null argument to setThumbnail");
+        }
+
+        int format = pixels.getFormat();
+        if (format != ImageFormat.YUV_420_888) {
+            throw new IllegalArgumentException("Unsupported image format " + format);
+        }
+
+        Image.Plane[] planes = pixels.getPlanes();
+        nativeSetThumbnailImage(pixels.getWidth(), pixels.getHeight(), planes[0].getBuffer(),
+                planes[0].getRowStride(), planes[0].getPixelStride(), planes[1].getBuffer(),
+                planes[1].getRowStride(), planes[1].getPixelStride(), planes[1].getBuffer(),
+                planes[1].getRowStride(), planes[1].getPixelStride());
+
         return this;
     }
 
@@ -150,7 +194,10 @@
      * @throws java.lang.IllegalArgumentException if the given location object doesn't
      *          contain enough information to set location metadata.
      */
-    public DngCreator setLocation(Location location) { return this; }
+    public DngCreator setLocation(Location location) {
+        /*TODO*/
+        return this;
+    }
 
     /**
      * Set the user description string to write.
@@ -163,6 +210,7 @@
      * @return this {@link #DngCreator} object.
      */
     public DngCreator setDescription(String description) {
+        /*TODO*/
         return this;
     }
 
@@ -172,32 +220,33 @@
      *
      * <p>
      * Raw pixel data must have 16 bits per pixel, and the input must contain at least
-     * {@code offset + 2 * (stride * (height - 1) + width * height)} bytes.  The width and height of
+     * {@code offset + 2 * width * height)} bytes.  The width and height of
      * the input are taken from the width and height set in the {@link DngCreator} metadata tags,
      * and will typically be equal to the width and height of
-     * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}.
-     * If insufficient metadata is set to write a well-formatted DNG file, and
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}.
+     * The pixel layout in the input is determined from the reported color filter arrangement (CFA)
+     * set in {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT}.  If insufficient
+     * metadata is available to write a well-formatted DNG file, an
      * {@link java.lang.IllegalStateException} will be thrown.
      * </p>
      *
-     * <p>
-     * When reading from the pixel input, {@code stride} pixels will be skipped
-     * after each row (excluding the last).
-     * </p>
-     *
      * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
+     * @param size the {@link Size} of the image to write, in pixels.
      * @param pixels an {@link java.io.InputStream} of pixel data to write.
-     * @param stride the stride of the raw image in pixels.
      * @param offset the offset of the raw image in bytes.  This indicates how many bytes will
      *               be skipped in the input before any pixel data is read.
      *
      * @throws IOException if an error was encountered in the input or output stream.
      * @throws java.lang.IllegalStateException if not enough metadata information has been
      *          set to write a well-formatted DNG file.
+     * @throws java.lang.IllegalArgumentException if the size passed in does not match the
      */
-    public void writeInputStream(OutputStream dngOutput, InputStream pixels, int stride,
-                                 long offset) throws IOException {
-        /*TODO*/
+    public void writeInputStream(OutputStream dngOutput, Size size, InputStream pixels, long offset)
+            throws IOException {
+        if (dngOutput == null || pixels == null) {
+            throw new NullPointerException("Null argument to writeImage");
+        }
+        nativeWriteInputStream(dngOutput, pixels, offset);
     }
 
     /**
@@ -206,22 +255,18 @@
      *
      * <p>
      * Raw pixel data must have 16 bits per pixel, and the input must contain at least
-     * {@code offset + 2 * (stride * (height - 1) + width * height)} bytes.  The width and height of
+     * {@code offset + 2 * width * height)} bytes.  The width and height of
      * the input are taken from the width and height set in the {@link DngCreator} metadata tags,
      * and will typically be equal to the width and height of
-     * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}.
-     * If insufficient metadata is set to write a well-formatted DNG file, and
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}.
+     * The pixel layout in the input is determined from the reported color filter arrangement (CFA)
+     * set in {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT}.  If insufficient
+     * metadata is available to write a well-formatted DNG file, an
      * {@link java.lang.IllegalStateException} will be thrown.
      * </p>
      *
-     * <p>
-     * When reading from the pixel input, {@code stride} pixels will be skipped
-     * after each row (excluding the last).
-     * </p>
-     *
      * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
      * @param pixels an {@link java.nio.ByteBuffer} of pixel data to write.
-     * @param stride the stride of the raw image in pixels.
      * @param offset the offset of the raw image in bytes.  This indicates how many bytes will
      *               be skipped in the input before any pixel data is read.
      *
@@ -229,8 +274,13 @@
      * @throws java.lang.IllegalStateException if not enough metadata information has been
      *          set to write a well-formatted DNG file.
      */
-    public void writeByteBuffer(OutputStream dngOutput, ByteBuffer pixels, int stride,
-                                long offset) throws IOException {/*TODO*/}
+    public void writeByteBuffer(OutputStream dngOutput, Size size, ByteBuffer pixels, long offset)
+            throws IOException {
+        if (dngOutput == null || pixels == null) {
+            throw new NullPointerException("Null argument to writeImage");
+        }
+        nativeWriteByteBuffer(dngOutput, pixels, offset);
+    }
 
     /**
      * Write the pixel data to a DNG file with the currently configured metadata.
@@ -249,6 +299,70 @@
      * @throws java.lang.IllegalStateException if not enough metadata information has been
      *          set to write a well-formatted DNG file.
      */
-    public void writeImage(OutputStream dngOutput, Image pixels) throws IOException {/*TODO*/}
+    public void writeImage(OutputStream dngOutput, Image pixels) throws IOException {
+        if (dngOutput == null || pixels == null) {
+            throw new NullPointerException("Null argument to writeImage");
+        }
 
+        int format = pixels.getFormat();
+        if (format != ImageFormat.RAW_SENSOR) {
+            throw new IllegalArgumentException("Unsupported image format " + format);
+        }
+
+        Image.Plane[] planes = pixels.getPlanes();
+        nativeWriteImage(dngOutput, pixels.getWidth(), pixels.getHeight(), planes[0].getBuffer(),
+                planes[0].getRowStride(), planes[0].getPixelStride());
+    }
+
+    @Override
+    public void close() {
+        nativeDestroy();
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * This field is used by native code, do not access or modify.
+     */
+    private long mNativeContext;
+
+    private static native void nativeClassInit();
+
+    private synchronized native void nativeInit(CameraMetadataNative nativeCharacteristics,
+                                                CameraMetadataNative nativeResult);
+
+    private synchronized native void nativeDestroy();
+
+    private synchronized native void nativeSetOrientation(int orientation);
+
+    private synchronized native void nativeSetThumbnailBitmap(Bitmap bitmap);
+
+    private synchronized native void nativeSetThumbnailImage(int width, int height,
+                                                             ByteBuffer yBuffer, int yRowStride,
+                                                             int yPixStride, ByteBuffer uBuffer,
+                                                             int uRowStride, int uPixStride,
+                                                             ByteBuffer vBuffer, int vRowStride,
+                                                             int vPixStride);
+
+    private synchronized native void nativeWriteImage(OutputStream out, int width, int height,
+                                                      ByteBuffer rawBuffer, int rowStride,
+                                                      int pixStride) throws IOException;
+
+    private synchronized native void nativeWriteByteBuffer(OutputStream out, ByteBuffer rawBuffer,
+                                                           long offset) throws IOException;
+
+    private synchronized native void nativeWriteInputStream(OutputStream out, InputStream rawStream,
+                                                            long offset) throws IOException;
+
+    static {
+        System.loadLibrary("media_jni");
+        nativeClassInit();
+    }
 }
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index 90fe695..d658654 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -2,6 +2,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:= \
+    android_media_DngCreator.cpp \
     android_media_ImageReader.cpp \
     android_media_MediaCrypto.cpp \
     android_media_MediaCodec.cpp \
@@ -41,6 +42,7 @@
     libjhead \
     libexif \
     libstagefright_amrnb_common \
+    libimg_utils \
 
 LOCAL_REQUIRED_MODULES := \
     libjhead_jni
@@ -53,6 +55,7 @@
     external/tremor/Tremor \
     frameworks/base/core/jni \
     frameworks/av/media/libmedia \
+    frameworks/av/media/img_utils/include \
     frameworks/av/media/libstagefright \
     frameworks/av/media/libstagefright/codecs/amrnb/enc/src \
     frameworks/av/media/libstagefright/codecs/amrnb/common \
diff --git a/media/jni/android_media_DngCreator.cpp b/media/jni/android_media_DngCreator.cpp
new file mode 100644
index 0000000..860d896
--- /dev/null
+++ b/media/jni/android_media_DngCreator.cpp
@@ -0,0 +1,772 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "DngCreator_JNI"
+
+#include <system/camera_metadata.h>
+#include <camera/CameraMetadata.h>
+#include <img_utils/DngUtils.h>
+#include <img_utils/TagDefinitions.h>
+#include <img_utils/TiffIfd.h>
+#include <img_utils/TiffWriter.h>
+#include <img_utils/Output.h>
+
+#include <utils/Log.h>
+#include <utils/Errors.h>
+#include <utils/StrongPointer.h>
+#include <utils/RefBase.h>
+#include <cutils/properties.h>
+
+#include "android_runtime/AndroidRuntime.h"
+#include "android_runtime/android_hardware_camera2_CameraMetadata.h"
+
+#include <jni.h>
+#include <JNIHelp.h>
+
+using namespace android;
+using namespace img_utils;
+
+#define BAIL_IF_INVALID(expr, jnienv, tagId) \
+    if ((expr) != OK) { \
+        jniThrowExceptionFmt(jnienv, "java/lang/IllegalArgumentException", \
+                "Invalid metadata for tag %x", tagId); \
+        return; \
+    }
+
+#define BAIL_IF_EMPTY(entry, jnienv, tagId) \
+    if (entry.count == 0) { \
+        jniThrowExceptionFmt(jnienv, "java/lang/IllegalArgumentException", \
+                "Missing metadata fields for tag %x", tagId); \
+        return; \
+    }
+
+#define ANDROID_MEDIA_DNGCREATOR_CTX_JNI_ID     "mNativeContext"
+
+static struct {
+    jfieldID mNativeContext;
+} gDngCreatorClassInfo;
+
+static struct {
+    jmethodID mWriteMethod;
+} gOutputStreamClassInfo;
+
+enum {
+    BITS_PER_SAMPLE = 16,
+    BYTES_PER_SAMPLE = 2,
+    TIFF_IFD_0 = 0
+};
+
+// ----------------------------------------------------------------------------
+
+// This class is not intended to be used across JNI calls.
+class JniOutputStream : public Output, public LightRefBase<JniOutputStream> {
+public:
+    JniOutputStream(JNIEnv* env, jobject outStream);
+
+    virtual ~JniOutputStream();
+
+    status_t open();
+    status_t write(const uint8_t* buf, size_t offset, size_t count);
+    status_t close();
+private:
+    enum {
+        BYTE_ARRAY_LENGTH = 1024
+    };
+    jobject mOutputStream;
+    JNIEnv* mEnv;
+    jbyteArray mByteArray;
+};
+
+JniOutputStream::JniOutputStream(JNIEnv* env, jobject outStream) : mOutputStream(outStream),
+        mEnv(env) {
+    mByteArray = env->NewByteArray(BYTE_ARRAY_LENGTH);
+    if (mByteArray == NULL) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", "Could not allocate byte array.");
+    }
+}
+
+JniOutputStream::~JniOutputStream() {
+    mEnv->DeleteLocalRef(mByteArray);
+}
+
+status_t JniOutputStream::open() {
+    // Do nothing
+    return OK;
+}
+
+status_t JniOutputStream::write(const uint8_t* buf, size_t offset, size_t count) {
+    while(count > 0) {
+        size_t len = BYTE_ARRAY_LENGTH;
+        len = (count > len) ? len : count;
+        mEnv->SetByteArrayRegion(mByteArray, 0, len, reinterpret_cast<const jbyte*>(buf + offset));
+
+        if (mEnv->ExceptionCheck()) {
+            return BAD_VALUE;
+        }
+
+        mEnv->CallVoidMethod(mOutputStream, gOutputStreamClassInfo.mWriteMethod, mByteArray,
+                0, len);
+
+        if (mEnv->ExceptionCheck()) {
+            return BAD_VALUE;
+        }
+
+        count -= len;
+        offset += len;
+    }
+    return OK;
+}
+
+status_t JniOutputStream::close() {
+    // Do nothing
+    return OK;
+}
+
+// ----------------------------------------------------------------------------
+
+extern "C" {
+
+static TiffWriter* DngCreator_getCreator(JNIEnv* env, jobject thiz) {
+    ALOGV("%s:", __FUNCTION__);
+    return reinterpret_cast<TiffWriter*>(env->GetLongField(thiz,
+            gDngCreatorClassInfo.mNativeContext));
+}
+
+static void DngCreator_setCreator(JNIEnv* env, jobject thiz, sp<TiffWriter> writer) {
+    ALOGV("%s:", __FUNCTION__);
+    TiffWriter* current = DngCreator_getCreator(env, thiz);
+    if (writer != NULL) {
+        writer->incStrong((void*) DngCreator_setCreator);
+    }
+    if (current) {
+        current->decStrong((void*) DngCreator_setCreator);
+    }
+    env->SetLongField(thiz, gDngCreatorClassInfo.mNativeContext,
+            reinterpret_cast<jlong>(writer.get()));
+}
+
+static void DngCreator_nativeClassInit(JNIEnv* env, jclass clazz) {
+    ALOGV("%s:", __FUNCTION__);
+
+    gDngCreatorClassInfo.mNativeContext = env->GetFieldID(clazz,
+            ANDROID_MEDIA_DNGCREATOR_CTX_JNI_ID, "J");
+    LOG_ALWAYS_FATAL_IF(gDngCreatorClassInfo.mNativeContext == NULL,
+            "can't find android/media/DngCreator.%s", ANDROID_MEDIA_DNGCREATOR_CTX_JNI_ID);
+
+    jclass outputStreamClazz = env->FindClass("java/io/OutputStream");
+    LOG_ALWAYS_FATAL_IF(outputStreamClazz == NULL, "Can't find java/io/OutputStream class");
+    gOutputStreamClassInfo.mWriteMethod = env->GetMethodID(outputStreamClazz, "write", "([BII)V");
+    LOG_ALWAYS_FATAL_IF(gOutputStreamClassInfo.mWriteMethod == NULL, "Can't find write method");
+}
+
+static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPtr,
+        jobject resultsPtr) {
+    ALOGV("%s:", __FUNCTION__);
+    CameraMetadata characteristics;
+    CameraMetadata results;
+    if (CameraMetadata_getNativeMetadata(env, characteristicsPtr, &characteristics) != OK) {
+         jniThrowException(env, "java/lang/AssertionError",
+                "No native metadata defined for camera characteristics.");
+         return;
+    }
+    if (CameraMetadata_getNativeMetadata(env, resultsPtr, &results) != OK) {
+        jniThrowException(env, "java/lang/AssertionError",
+                "No native metadata defined for capture results.");
+        return;
+    }
+
+    sp<TiffWriter> writer = new TiffWriter();
+
+    writer->addIfd(TIFF_IFD_0);
+
+    status_t err = OK;
+
+    const uint32_t samplesPerPixel = 1;
+    const uint32_t bitsPerSample = BITS_PER_SAMPLE;
+    const uint32_t bitsPerByte = BITS_PER_SAMPLE / BYTES_PER_SAMPLE;
+    uint32_t imageWidth = 0;
+    uint32_t imageHeight = 0;
+
+    OpcodeListBuilder::CfaLayout opcodeCfaLayout = OpcodeListBuilder::CFA_RGGB;
+
+    // TODO: Greensplit.
+    // TODO: UniqueCameraModel
+    // TODO: Add remaining non-essential tags
+    {
+        // Set orientation
+        uint16_t orientation = 1; // Normal
+        BAIL_IF_INVALID(writer->addEntry(TAG_ORIENTATION, 1, &orientation, TIFF_IFD_0), env,
+                TAG_ORIENTATION);
+    }
+
+    {
+        // Set subfiletype
+        uint32_t subfileType = 0; // Main image
+        BAIL_IF_INVALID(writer->addEntry(TAG_NEWSUBFILETYPE, 1, &subfileType, TIFF_IFD_0), env,
+                TAG_NEWSUBFILETYPE);
+    }
+
+    {
+        // Set bits per sample
+        uint16_t bits = static_cast<uint16_t>(bitsPerSample);
+        BAIL_IF_INVALID(writer->addEntry(TAG_BITSPERSAMPLE, 1, &bits, TIFF_IFD_0), env,
+                TAG_BITSPERSAMPLE);
+    }
+
+    {
+        // Set compression
+        uint16_t compression = 1; // None
+        BAIL_IF_INVALID(writer->addEntry(TAG_COMPRESSION, 1, &compression, TIFF_IFD_0), env,
+                TAG_COMPRESSION);
+    }
+
+    {
+        // Set dimensions
+        camera_metadata_entry entry =
+                characteristics.find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+        BAIL_IF_EMPTY(entry, env, TAG_IMAGEWIDTH);
+        uint32_t width = static_cast<uint32_t>(entry.data.i32[2]);
+        uint32_t height = static_cast<uint32_t>(entry.data.i32[3]);
+        BAIL_IF_INVALID(writer->addEntry(TAG_IMAGEWIDTH, 1, &width, TIFF_IFD_0), env,
+                TAG_IMAGEWIDTH);
+        BAIL_IF_INVALID(writer->addEntry(TAG_IMAGELENGTH, 1, &height, TIFF_IFD_0), env,
+                TAG_IMAGELENGTH);
+        imageWidth = width;
+        imageHeight = height;
+    }
+
+    {
+        // Set photometric interpretation
+        uint16_t interpretation = 32803;
+        BAIL_IF_INVALID(writer->addEntry(TAG_PHOTOMETRICINTERPRETATION, 1, &interpretation,
+                TIFF_IFD_0), env, TAG_PHOTOMETRICINTERPRETATION);
+    }
+
+    {
+        // Set blacklevel tags
+        camera_metadata_entry entry =
+                characteristics.find(ANDROID_SENSOR_BLACK_LEVEL_PATTERN);
+        BAIL_IF_EMPTY(entry, env, TAG_BLACKLEVEL);
+        const uint32_t* blackLevel = reinterpret_cast<const uint32_t*>(entry.data.i32);
+        BAIL_IF_INVALID(writer->addEntry(TAG_BLACKLEVEL, entry.count, blackLevel, TIFF_IFD_0), env,
+                TAG_BLACKLEVEL);
+
+        uint16_t repeatDim[2] = {2, 2};
+        BAIL_IF_INVALID(writer->addEntry(TAG_BLACKLEVELREPEATDIM, 2, repeatDim, TIFF_IFD_0), env,
+                TAG_BLACKLEVELREPEATDIM);
+    }
+
+    {
+        // Set samples per pixel
+        uint16_t samples = static_cast<uint16_t>(samplesPerPixel);
+        BAIL_IF_INVALID(writer->addEntry(TAG_SAMPLESPERPIXEL, 1, &samples, TIFF_IFD_0),
+                env, TAG_SAMPLESPERPIXEL);
+    }
+
+    {
+        // Set planar configuration
+        uint16_t config = 1; // Chunky
+        BAIL_IF_INVALID(writer->addEntry(TAG_PLANARCONFIGURATION, 1, &config, TIFF_IFD_0),
+                env, TAG_PLANARCONFIGURATION);
+    }
+
+    {
+        // Set CFA pattern dimensions
+        uint16_t repeatDim[2] = {2, 2};
+        BAIL_IF_INVALID(writer->addEntry(TAG_CFAREPEATPATTERNDIM, 2, repeatDim, TIFF_IFD_0),
+                env, TAG_CFAREPEATPATTERNDIM);
+    }
+
+    {
+        // Set CFA pattern
+        camera_metadata_entry entry =
+                        characteristics.find(ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT);
+        BAIL_IF_EMPTY(entry, env, TAG_CFAPATTERN);
+        camera_metadata_enum_android_sensor_info_color_filter_arrangement_t cfa =
+                static_cast<camera_metadata_enum_android_sensor_info_color_filter_arrangement_t>(
+                entry.data.u8[0]);
+        switch(cfa) {
+            case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGGB: {
+                uint8_t cfa[4] = {0, 1, 1, 2};
+                BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0),
+                                                env, TAG_CFAPATTERN);
+                opcodeCfaLayout = OpcodeListBuilder::CFA_RGGB;
+                break;
+            }
+            case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GRBG: {
+                uint8_t cfa[4] = {1, 0, 2, 1};
+                BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0),
+                                                env, TAG_CFAPATTERN);
+                opcodeCfaLayout = OpcodeListBuilder::CFA_GRBG;
+                break;
+            }
+            case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GBRG: {
+                uint8_t cfa[4] = {1, 2, 0, 1};
+                BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0),
+                                                env, TAG_CFAPATTERN);
+                opcodeCfaLayout = OpcodeListBuilder::CFA_GBRG;
+                break;
+            }
+            case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_BGGR: {
+                uint8_t cfa[4] = {2, 1, 1, 0};
+                BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0),
+                                env, TAG_CFAPATTERN);
+                opcodeCfaLayout = OpcodeListBuilder::CFA_BGGR;
+                break;
+            }
+            default: {
+                jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+                            "Invalid metadata for tag %d", TAG_CFAPATTERN);
+                return;
+            }
+        }
+    }
+
+    {
+        // Set CFA plane color
+        uint8_t cfaPlaneColor[3] = {0, 1, 2};
+        BAIL_IF_INVALID(writer->addEntry(TAG_CFAPLANECOLOR, 3, cfaPlaneColor, TIFF_IFD_0),
+                env, TAG_CFAPLANECOLOR);
+    }
+
+    {
+        // Set CFA layout
+        uint16_t cfaLayout = 1;
+        BAIL_IF_INVALID(writer->addEntry(TAG_CFALAYOUT, 1, &cfaLayout, TIFF_IFD_0),
+                env, TAG_CFALAYOUT);
+    }
+
+    {
+        // Set DNG version information
+        uint8_t version[4] = {1, 4, 0, 0};
+        BAIL_IF_INVALID(writer->addEntry(TAG_DNGVERSION, 4, version, TIFF_IFD_0),
+                env, TAG_DNGVERSION);
+
+        uint8_t backwardVersion[4] = {1, 1, 0, 0};
+        BAIL_IF_INVALID(writer->addEntry(TAG_DNGBACKWARDVERSION, 4, backwardVersion, TIFF_IFD_0),
+                env, TAG_DNGBACKWARDVERSION);
+    }
+
+    {
+        // Set whitelevel
+        camera_metadata_entry entry =
+                characteristics.find(ANDROID_SENSOR_INFO_WHITE_LEVEL);
+        BAIL_IF_EMPTY(entry, env, TAG_WHITELEVEL);
+        uint32_t whiteLevel = static_cast<uint32_t>(entry.data.i32[0]);
+        BAIL_IF_INVALID(writer->addEntry(TAG_WHITELEVEL, 1, &whiteLevel, TIFF_IFD_0), env,
+                TAG_WHITELEVEL);
+    }
+
+    {
+        // Set default scale
+        uint32_t defaultScale[4] = {1, 1, 1, 1};
+        BAIL_IF_INVALID(writer->addEntry(TAG_DEFAULTSCALE, 2, defaultScale, TIFF_IFD_0),
+                env, TAG_DEFAULTSCALE);
+    }
+
+    bool singleIlluminant = false;
+    {
+        // Set calibration illuminants
+        camera_metadata_entry entry1 =
+            characteristics.find(ANDROID_SENSOR_REFERENCE_ILLUMINANT1);
+        BAIL_IF_EMPTY(entry1, env, TAG_CALIBRATIONILLUMINANT1);
+        camera_metadata_entry entry2 =
+            characteristics.find(ANDROID_SENSOR_REFERENCE_ILLUMINANT2);
+        if (entry2.count == 0) {
+            singleIlluminant = true;
+        }
+        uint16_t ref1 = entry1.data.u8[0];
+
+        BAIL_IF_INVALID(writer->addEntry(TAG_CALIBRATIONILLUMINANT1, 1, &ref1,
+                TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT1);
+
+        if (!singleIlluminant) {
+            uint16_t ref2 = entry2.data.u8[0];
+            BAIL_IF_INVALID(writer->addEntry(TAG_CALIBRATIONILLUMINANT2, 1, &ref2,
+                    TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT2);
+        }
+    }
+
+    {
+        // Set color transforms
+        camera_metadata_entry entry1 =
+            characteristics.find(ANDROID_SENSOR_COLOR_TRANSFORM1);
+        BAIL_IF_EMPTY(entry1, env, TAG_COLORMATRIX1);
+
+        int32_t colorTransform1[entry1.count * 2];
+
+        size_t ctr = 0;
+        for(size_t i = 0; i < entry1.count; ++i) {
+            colorTransform1[ctr++] = entry1.data.r[i].numerator;
+            colorTransform1[ctr++] = entry1.data.r[i].denominator;
+        }
+
+        BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX1, entry1.count, colorTransform1, TIFF_IFD_0),
+                env, TAG_COLORMATRIX1);
+
+        if (!singleIlluminant) {
+            camera_metadata_entry entry2 = characteristics.find(ANDROID_SENSOR_COLOR_TRANSFORM2);
+            BAIL_IF_EMPTY(entry2, env, TAG_COLORMATRIX2);
+            int32_t colorTransform2[entry2.count * 2];
+
+            ctr = 0;
+            for(size_t i = 0; i < entry2.count; ++i) {
+                colorTransform2[ctr++] = entry2.data.r[i].numerator;
+                colorTransform2[ctr++] = entry2.data.r[i].denominator;
+            }
+
+            BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX2, entry2.count, colorTransform2, TIFF_IFD_0),
+                    env, TAG_COLORMATRIX2);
+        }
+    }
+
+    {
+        // Set calibration transforms
+        camera_metadata_entry entry1 =
+            characteristics.find(ANDROID_SENSOR_CALIBRATION_TRANSFORM1);
+        BAIL_IF_EMPTY(entry1, env, TAG_CAMERACALIBRATION1);
+
+        int32_t calibrationTransform1[entry1.count * 2];
+
+        size_t ctr = 0;
+        for(size_t i = 0; i < entry1.count; ++i) {
+            calibrationTransform1[ctr++] = entry1.data.r[i].numerator;
+            calibrationTransform1[ctr++] = entry1.data.r[i].denominator;
+        }
+
+        BAIL_IF_INVALID(writer->addEntry(TAG_CAMERACALIBRATION1, entry1.count, calibrationTransform1,
+                TIFF_IFD_0), env, TAG_CAMERACALIBRATION1);
+
+        if (!singleIlluminant) {
+            camera_metadata_entry entry2 =
+                characteristics.find(ANDROID_SENSOR_CALIBRATION_TRANSFORM2);
+            BAIL_IF_EMPTY(entry2, env, TAG_CAMERACALIBRATION2);
+            int32_t calibrationTransform2[entry2.count * 2];
+
+            ctr = 0;
+            for(size_t i = 0; i < entry2.count; ++i) {
+                calibrationTransform2[ctr++] = entry2.data.r[i].numerator;
+                calibrationTransform2[ctr++] = entry2.data.r[i].denominator;
+            }
+
+            BAIL_IF_INVALID(writer->addEntry(TAG_CAMERACALIBRATION2, entry2.count, calibrationTransform1,
+                    TIFF_IFD_0),  env, TAG_CAMERACALIBRATION2);
+        }
+    }
+
+    {
+        // Set forward transforms
+        camera_metadata_entry entry1 =
+            characteristics.find(ANDROID_SENSOR_FORWARD_MATRIX1);
+        BAIL_IF_EMPTY(entry1, env, TAG_FORWARDMATRIX1);
+
+        int32_t forwardTransform1[entry1.count * 2];
+
+        size_t ctr = 0;
+        for(size_t i = 0; i < entry1.count; ++i) {
+            forwardTransform1[ctr++] = entry1.data.r[i].numerator;
+            forwardTransform1[ctr++] = entry1.data.r[i].denominator;
+        }
+
+        BAIL_IF_INVALID(writer->addEntry(TAG_FORWARDMATRIX1, entry1.count, forwardTransform1,
+                TIFF_IFD_0), env, TAG_FORWARDMATRIX1);
+
+        if (!singleIlluminant) {
+            camera_metadata_entry entry2 =
+                characteristics.find(ANDROID_SENSOR_FORWARD_MATRIX2);
+            BAIL_IF_EMPTY(entry2, env, TAG_FORWARDMATRIX2);
+            int32_t forwardTransform2[entry2.count * 2];
+
+            ctr = 0;
+            for(size_t i = 0; i < entry2.count; ++i) {
+                forwardTransform2[ctr++] = entry2.data.r[i].numerator;
+                forwardTransform2[ctr++] = entry2.data.r[i].denominator;
+            }
+
+            BAIL_IF_INVALID(writer->addEntry(TAG_FORWARDMATRIX2, entry2.count, forwardTransform2,
+                    TIFF_IFD_0),  env, TAG_FORWARDMATRIX2);
+        }
+    }
+
+    {
+        // Set camera neutral
+        camera_metadata_entry entry =
+            results.find(ANDROID_SENSOR_NEUTRAL_COLOR_POINT);
+        BAIL_IF_EMPTY(entry, env, TAG_ASSHOTNEUTRAL);
+        uint32_t cameraNeutral[entry.count * 2];
+
+        size_t ctr = 0;
+        for(size_t i = 0; i < entry.count; ++i) {
+            cameraNeutral[ctr++] =
+                    static_cast<uint32_t>(entry.data.r[i].numerator);
+            cameraNeutral[ctr++] =
+                    static_cast<uint32_t>(entry.data.r[i].denominator);
+        }
+
+        BAIL_IF_INVALID(writer->addEntry(TAG_ASSHOTNEUTRAL, entry.count, cameraNeutral,
+                TIFF_IFD_0), env, TAG_ASSHOTNEUTRAL);
+    }
+
+    {
+        // Setup data strips
+        // TODO: Switch to tiled implementation.
+        uint32_t offset = 0;
+        BAIL_IF_INVALID(writer->addEntry(TAG_STRIPOFFSETS, 1, &offset, TIFF_IFD_0), env,
+                TAG_STRIPOFFSETS);
+
+        BAIL_IF_INVALID(writer->addEntry(TAG_ROWSPERSTRIP, 1, &imageHeight, TIFF_IFD_0), env,
+                TAG_ROWSPERSTRIP);
+
+        uint32_t byteCount = imageWidth * imageHeight * bitsPerSample * samplesPerPixel /
+                bitsPerByte;
+        BAIL_IF_INVALID(writer->addEntry(TAG_STRIPBYTECOUNTS, 1, &byteCount, TIFF_IFD_0), env,
+                TAG_STRIPBYTECOUNTS);
+    }
+
+    {
+        // Setup default crop + crop origin tags
+        uint32_t margin = 8; // Default margin recommended by Adobe for interpolation.
+        uint32_t dimensionLimit = 128; // Smallest image dimension crop margin from.
+        if (imageWidth >= dimensionLimit && imageHeight >= dimensionLimit) {
+            uint32_t defaultCropOrigin[] = {margin, margin};
+            uint32_t defaultCropSize[] = {imageWidth - margin, imageHeight - margin};
+            BAIL_IF_INVALID(writer->addEntry(TAG_DEFAULTCROPORIGIN, 2, defaultCropOrigin,
+                    TIFF_IFD_0), env, TAG_DEFAULTCROPORIGIN);
+            BAIL_IF_INVALID(writer->addEntry(TAG_DEFAULTCROPSIZE, 2, defaultCropSize,
+                    TIFF_IFD_0), env, TAG_DEFAULTCROPSIZE);
+        }
+    }
+
+    {
+        // Setup unique camera model tag
+        char model[PROPERTY_VALUE_MAX];
+        property_get("ro.product.model", model, "");
+
+        char manufacturer[PROPERTY_VALUE_MAX];
+        property_get("ro.product.manufacturer", manufacturer, "");
+
+        char brand[PROPERTY_VALUE_MAX];
+        property_get("ro.product.brand", brand, "");
+
+        String8 cameraModel(model);
+        cameraModel += "-";
+        cameraModel += manufacturer;
+        cameraModel += "-";
+        cameraModel += brand;
+
+        BAIL_IF_INVALID(writer->addEntry(TAG_UNIQUECAMERAMODEL, cameraModel.size() + 1,
+                reinterpret_cast<const uint8_t*>(cameraModel.string()), TIFF_IFD_0), env,
+                TAG_UNIQUECAMERAMODEL);
+    }
+
+    {
+        // Setup opcode List 2
+        camera_metadata_entry entry1 =
+                characteristics.find(ANDROID_LENS_INFO_SHADING_MAP_SIZE);
+        BAIL_IF_EMPTY(entry1, env, TAG_OPCODELIST2);
+        uint32_t lsmWidth = static_cast<uint32_t>(entry1.data.i32[0]);
+        uint32_t lsmHeight = static_cast<uint32_t>(entry1.data.i32[1]);
+
+        camera_metadata_entry entry2 =
+                results.find(ANDROID_STATISTICS_LENS_SHADING_MAP);
+        BAIL_IF_EMPTY(entry2, env, TAG_OPCODELIST2);
+        if (entry2.count == lsmWidth * lsmHeight * 4) {
+
+            OpcodeListBuilder builder;
+            status_t err = builder.addGainMapsForMetadata(lsmWidth,
+                                                          lsmHeight,
+                                                          0,
+                                                          0,
+                                                          imageHeight,
+                                                          imageWidth,
+                                                          opcodeCfaLayout,
+                                                          entry2.data.f);
+            if (err == OK) {
+                size_t listSize = builder.getSize();
+                uint8_t opcodeListBuf[listSize];
+                err = builder.buildOpList(opcodeListBuf);
+                if (err == OK) {
+                    BAIL_IF_INVALID(writer->addEntry(TAG_OPCODELIST2, listSize, opcodeListBuf,
+                            TIFF_IFD_0), env, TAG_OPCODELIST2);
+                } else {
+                    ALOGE("%s: Could not build Lens shading map opcode.", __FUNCTION__);
+                    jniThrowRuntimeException(env, "failed to construct lens shading map opcode.");
+                }
+            } else {
+                ALOGE("%s: Could not add Lens shading map.", __FUNCTION__);
+                jniThrowRuntimeException(env, "failed to add lens shading map.");
+            }
+        } else {
+            ALOGW("%s: Lens shading map not present in results, skipping...", __FUNCTION__);
+        }
+    }
+
+    DngCreator_setCreator(env, thiz, writer);
+}
+
+static void DngCreator_destroy(JNIEnv* env, jobject thiz) {
+    ALOGV("%s:", __FUNCTION__);
+    DngCreator_setCreator(env, thiz, NULL);
+}
+
+static void DngCreator_nativeSetOrientation(JNIEnv* env, jobject thiz) {
+    ALOGV("%s:", __FUNCTION__);
+    jniThrowRuntimeException(env, "nativeSetOrientation is not implemented");
+}
+
+static void DngCreator_nativeSetThumbnailBitmap(JNIEnv* env, jobject thiz, jobject bitmap) {
+    ALOGV("%s:", __FUNCTION__);
+    jniThrowRuntimeException(env, "nativeSetThumbnailBitmap is not implemented");
+}
+
+static void DngCreator_nativeSetThumbnailImage(JNIEnv* env, jobject thiz, jint width, jint height,
+        jobject yBuffer, jint yRowStride, jint yPixStride, jobject uBuffer, jint uRowStride,
+        jint uPixStride, jobject vBuffer, jint vRowStride, jint vPixStride) {
+    ALOGV("%s:", __FUNCTION__);
+    jniThrowRuntimeException(env, "nativeSetThumbnailImage is not implemented");
+}
+
+static void DngCreator_nativeWriteImage(JNIEnv* env, jobject thiz, jobject outStream, jint width,
+        jint height, jobject inBuffer, jint rowStride, jint pixStride) {
+    ALOGV("%s:", __FUNCTION__);
+
+    sp<JniOutputStream> out = new JniOutputStream(env, outStream);
+    if(env->ExceptionCheck()) {
+        ALOGE("%s: Could not allocate buffers for output stream", __FUNCTION__);
+        return;
+    }
+
+    uint8_t* pixelBytes = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(inBuffer));
+    if (pixelBytes == NULL) {
+        ALOGE("%s: Could not get native byte buffer", __FUNCTION__);
+        jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid bytebuffer");
+        return;
+    }
+
+    TiffWriter* writer = DngCreator_getCreator(env, thiz);
+    if (writer == NULL) {
+        ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
+        jniThrowException(env, "java/lang/AssertionError",
+                "Write called with uninitialized DngCreator");
+        return;
+    }
+    // TODO: handle lens shading map, etc. conversions for other raw buffer sizes.
+    uint32_t metadataWidth = *(writer->getEntry(TAG_IMAGEWIDTH, TIFF_IFD_0)->getData<uint32_t>());
+    uint32_t metadataHeight = *(writer->getEntry(TAG_IMAGELENGTH, TIFF_IFD_0)->getData<uint32_t>());
+    if (metadataWidth != width) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalStateException", \
+                        "Metadata width %d doesn't match image width %d", metadataWidth, width);
+        return;
+    }
+
+    if (metadataHeight != height) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalStateException", \
+                        "Metadata height %d doesn't match image height %d", metadataHeight, height);
+        return;
+    }
+
+    uint32_t stripOffset = writer->getTotalSize();
+
+    BAIL_IF_INVALID(writer->addEntry(TAG_STRIPOFFSETS, 1, &stripOffset, TIFF_IFD_0), env,
+                    TAG_STRIPOFFSETS);
+
+    if (writer->write(out.get()) != OK) {
+        if (!env->ExceptionCheck()) {
+            jniThrowException(env, "java/io/IOException", "Failed to write metadata");
+        }
+        return;
+    }
+
+    size_t fullSize = rowStride * height;
+    jlong capacity = env->GetDirectBufferCapacity(inBuffer);
+    if (capacity < 0 || fullSize > capacity) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+                "Invalid size %d for Image, size given in metadata is %d at current stride",
+                capacity, fullSize);
+        return;
+    }
+
+    if (pixStride == BYTES_PER_SAMPLE && rowStride == width * BYTES_PER_SAMPLE) {
+        if (out->write(pixelBytes, 0, fullSize) != OK || env->ExceptionCheck()) {
+            if (!env->ExceptionCheck()) {
+                jniThrowException(env, "java/io/IOException", "Failed to write pixel data");
+            }
+            return;
+        }
+    } else if (pixStride == BYTES_PER_SAMPLE) {
+        for (size_t i = 0; i < height; ++i) {
+            if (out->write(pixelBytes, i * rowStride, pixStride * width) != OK ||
+                        env->ExceptionCheck()) {
+                if (!env->ExceptionCheck()) {
+                    jniThrowException(env, "java/io/IOException", "Failed to write pixel data");
+                }
+                return;
+            }
+        }
+    } else {
+        for (size_t i = 0; i < height; ++i) {
+            for (size_t j = 0; j < width; ++j) {
+                if (out->write(pixelBytes, i * rowStride + j * pixStride,
+                        BYTES_PER_SAMPLE) != OK || !env->ExceptionCheck()) {
+                    if (env->ExceptionCheck()) {
+                        jniThrowException(env, "java/io/IOException", "Failed to write pixel data");
+                    }
+                    return;
+                }
+            }
+        }
+    }
+
+}
+
+static void DngCreator_nativeWriteByteBuffer(JNIEnv* env, jobject thiz, jobject outStream,
+        jobject rawBuffer, jlong offset) {
+    ALOGV("%s:", __FUNCTION__);
+    jniThrowRuntimeException(env, "nativeWriteByteBuffer is not implemented.");
+}
+
+static void DngCreator_nativeWriteInputStream(JNIEnv* env, jobject thiz, jobject outStream,
+        jobject inStream, jlong offset) {
+    ALOGV("%s:", __FUNCTION__);
+    jniThrowRuntimeException(env, "nativeWriteInputStream is not implemented.");
+}
+
+} /*extern "C" */
+
+static JNINativeMethod gDngCreatorMethods[] = {
+    {"nativeClassInit",        "()V", (void*) DngCreator_nativeClassInit},
+    {"nativeInit", "(Landroid/hardware/camera2/impl/CameraMetadataNative;"
+            "Landroid/hardware/camera2/impl/CameraMetadataNative;)V", (void*) DngCreator_init},
+    {"nativeDestroy",           "()V",      (void*) DngCreator_destroy},
+    {"nativeSetOrientation",    "(I)V",     (void*) DngCreator_nativeSetOrientation},
+    {"nativeSetThumbnailBitmap","(Landroid/graphics/Bitmap;)V",
+            (void*) DngCreator_nativeSetThumbnailBitmap},
+    {"nativeSetThumbnailImage",
+            "(IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;II)V",
+            (void*) DngCreator_nativeSetThumbnailImage},
+    {"nativeWriteImage",        "(Ljava/io/OutputStream;IILjava/nio/ByteBuffer;II)V",
+            (void*) DngCreator_nativeWriteImage},
+    {"nativeWriteByteBuffer",    "(Ljava/io/OutputStream;Ljava/nio/ByteBuffer;J)V",
+            (void*) DngCreator_nativeWriteByteBuffer},
+    {"nativeWriteInputStream",    "(Ljava/io/OutputStream;Ljava/io/InputStream;J)V",
+            (void*) DngCreator_nativeWriteInputStream},
+};
+
+int register_android_media_DngCreator(JNIEnv *env) {
+    return AndroidRuntime::registerNativeMethods(env,
+                   "android/media/DngCreator", gDngCreatorMethods, NELEM(gDngCreatorMethods));
+}
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 6f42057..9d03cc38 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -884,6 +884,7 @@
                 "android/media/MediaPlayer", gMethods, NELEM(gMethods));
 }
 
+extern int register_android_media_DngCreator(JNIEnv *env);
 extern int register_android_media_ImageReader(JNIEnv *env);
 extern int register_android_media_Crypto(JNIEnv *env);
 extern int register_android_media_Drm(JNIEnv *env);
@@ -913,6 +914,11 @@
     }
     assert(env != NULL);
 
+    if (register_android_media_DngCreator(env) < 0) {
+        ALOGE("ERROR: ImageReader native registration failed");
+        goto bail;
+    }
+
     if (register_android_media_ImageReader(env) < 0) {
         ALOGE("ERROR: ImageReader native registration failed");
         goto bail;
diff --git a/packages/InputDevices/res/raw/keyboard_layout_latvian_qwerty.kcm b/packages/InputDevices/res/raw/keyboard_layout_latvian_qwerty.kcm
new file mode 100644
index 0000000..d4bc0c0
--- /dev/null
+++ b/packages/InputDevices/res/raw/keyboard_layout_latvian_qwerty.kcm
@@ -0,0 +1,362 @@
+# Copyright (C) 2014 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.
+
+#
+# Latvian (QWERTY-US-intl based) keyboard layout.
+#
+
+type OVERLAY
+
+map key 86 BACKSLASH
+map key 43 POUND
+
+### ROW 1
+
+key GRAVE {
+    label:                              '\u0300'
+    base:                               '\u0300'
+    shift:                              '\u0303'
+    ralt:                               '-'
+}
+
+key 1 {
+    label:                              '1'
+    base:                               '1'
+    shift:                              '!'
+    ralt:                               '\u00a0'
+}
+
+key 2 {
+    label:                              '2'
+    base:                               '2'
+    shift:                              '@'
+    ralt:                               '\u00ab'
+}
+
+key 3 {
+    label:                              '3'
+    base:                               '3'
+    shift:                              '#'
+    ralt:                               '\u00bb'
+}
+
+key 4 {
+    label:                              '4'
+    base:                               '4'
+    shift:                              '$'
+    ralt:                               '\u20ac'
+    ralt+shift:                         '\u00a7'
+}
+
+key 5 {
+    label:                              '5'
+    base:                               '5'
+    shift:                              '%'
+    ralt+shift:                         '\u00b0'
+}
+
+key 6 {
+    label:                              '6'
+    base:                               '6'
+    shift:                              '\u0302'
+    ralt:                               '\u2019'
+}
+
+key 7 {
+    label:                              '7'
+    base:                               '7'
+    shift:                              '&'
+    ralt+shift:                         '\u00b1'
+}
+
+key 8 {
+    label:                              '8'
+    base:                               '8'
+    shift:                              '*'
+    ralt+shift:                         '\u00d7'
+}
+
+key 9 {
+    label:                              '9'
+    base:                               '9'
+    shift:                              '('
+}
+
+key 0 {
+    label:                              '0'
+    base:                               '0'
+    shift:                              ')'
+}
+
+key MINUS {
+    label:                              '-'
+    base:                               '-'
+    shift:                              '_'
+    ralt:                               '\u2013'
+    ralt+shift:                         '\u2014'
+}
+
+key EQUALS {
+    label:                              '='
+    base:                               '='
+    shift:                              '+'
+}
+
+### ROW 2
+
+key Q {
+    label:                              'Q'
+    base:                               'q'
+    shift, capslock:                    'Q'
+}
+
+key W {
+    label:                              'W'
+    base:                               'w'
+    shift, capslock:                    'W'
+}
+
+key E {
+    label:                              'E'
+    base:                               'e'
+    shift, capslock:                    'E'
+    ralt:                               '\u0113'
+    shift+ralt, ralt+capslock:          '\u0112'
+}
+
+key R {
+    label:                              'R'
+    base:                               'r'
+    shift, capslock:                    'R'
+    ralt:                               '\u0157'
+    shift+ralt, ralt+capslock:          '\u0156'
+}
+
+key T {
+    label:                              'T'
+    base:                               't'
+    shift, capslock:                    'T'
+}
+
+key Y {
+    label:                              'Y'
+    base:                               'y'
+    shift, capslock:                    'Y'
+}
+
+key U {
+    label:                              'U'
+    base:                               'u'
+    shift, capslock:                    'U'
+    ralt:                               '\u016b'
+    shift+ralt, ralt+capslock:          '\u016a'
+}
+
+key I {
+    label:                              'I'
+    base:                               'i'
+    shift, capslock:                    'I'
+    ralt:                               '\u012b'
+    shift+ralt, ralt+capslock:          '\u012a'
+}
+
+key O {
+    label:                              'O'
+    base:                               'o'
+    shift, capslock:                    'O'
+    ralt:                               '\u00f5'
+    shift+ralt, ralt+capslock:          '\u00d5'
+}
+
+key P {
+    label:                              'P'
+    base:                               'p'
+    shift, capslock:                    'P'
+}
+
+key LEFT_BRACKET {
+    label:                              '['
+    base:                               '['
+    shift:                              '{'
+}
+
+key RIGHT_BRACKET {
+    label:                              ']'
+    base:                               ']'
+    shift:                              '}'
+}
+
+
+### ROW 3
+
+key A {
+    label:                              'A'
+    base:                               'a'
+    shift, capslock:                    'A'
+    ralt:                               '\u0101'
+    shift+ralt, ralt+capslock:          '\u0100'
+}
+
+key S {
+    label:                              'S'
+    base:                               's'
+    shift, capslock:                    'S'
+    ralt:                               '\u0161'
+    shift+ralt, ralt+capslock:          '\u0160'
+}
+
+key D {
+    label:                              'D'
+    base:                               'd'
+    shift, capslock:                    'D'
+}
+
+key F {
+    label:                              'F'
+    base:                               'f'
+    shift, capslock:                    'F'
+}
+
+key G {
+    label:                              'G'
+    base:                               'g'
+    shift, capslock:                    'G'
+    ralt:                               '\u0123'
+    shift+ralt, ralt+capslock:          '\u0122'
+}
+
+key H {
+    label:                              'H'
+    base:                               'h'
+    shift, capslock:                    'H'
+}
+
+key J {
+    label:                              'J'
+    base:                               'j'
+    shift, capslock:                    'J'
+}
+
+key K {
+    label:                              'K'
+    base:                               'k'
+    shift, capslock:                    'K'
+    ralt:                               '\u0137'
+    shift+ralt, ralt+capslock:          '\u0136'
+}
+
+key L {
+    label:                              'L'
+    base:                               'l'
+    shift, capslock:                    'L'
+    ralt:                               '\u013c'
+    shift+ralt, ralt+capslock:          '\u013b'
+}
+
+key SEMICOLON {
+    label:                              ';'
+    base:                               ';'
+    shift:                              ':'
+}
+
+key APOSTROPHE {
+    label:                              '\''
+    base:                               '\''
+    shift:                              '"'
+    ralt:                               '\u0301'
+    shift+ralt:                         '\u0308'
+}
+
+key POUND {
+    label:                              '\u00b0'
+    base:                               '\u00b0'
+    shift:                              '|'
+}
+
+### ROW 4
+
+key BACKSLASH {
+    label:                              '\\'
+    base:                               '\\'
+    shift:                              '|'
+    ralt:                               '\u00ac'
+    shift+ralt:                         '\u00a6'
+}
+
+key Z {
+    label:                              'Z'
+    base:                               'z'
+    shift, capslock:                    'Z'
+    ralt:                               '\u017e'
+    shift+ralt, ralt+capslock:          '\u017d'
+}
+
+key X {
+    label:                              'X'
+    base:                               'x'
+    shift, capslock:                    'X'
+}
+
+key C {
+    label:                              'C'
+    base:                               'c'
+    shift, capslock:                    'C'
+    ralt:                               '\u010d'
+    shift+ralt, ralt+capslock:          '\u010c'
+}
+
+key V {
+    label:                              'V'
+    base:                               'v'
+    shift, capslock:                    'V'
+}
+
+key B {
+    label:                              'B'
+    base:                               'b'
+    shift, capslock:                    'B'
+}
+
+key N {
+    label:                              'N'
+    base:                               'n'
+    shift, capslock:                    'N'
+    ralt:                               '\u0146'
+    shift+ralt, ralt+capslock:          '\u0145'
+}
+
+key M {
+    label:                              'M'
+    base:                               'm'
+    shift, capslock:                    'M'
+}
+
+key COMMA {
+    label:                              ','
+    base:                               ','
+    shift:                              '<'
+}
+
+key PERIOD {
+    label:                              '.'
+    base:                               '.'
+    shift:                              '>'
+}
+
+key SLASH {
+    label:                              '/'
+    base:                               '/'
+    shift:                              '?'
+    ralt:                               '\u00bf'
+}
diff --git a/packages/InputDevices/res/values/strings.xml b/packages/InputDevices/res/values/strings.xml
index 6239336..968961a 100644
--- a/packages/InputDevices/res/values/strings.xml
+++ b/packages/InputDevices/res/values/strings.xml
@@ -113,4 +113,7 @@
 
     <!-- Spanish (Latin) keyboard layout label. [CHAR LIMIT=35] -->
     <string name="keyboard_layout_spanish_latin">Spanish (Latin)</string>
+
+    <!-- Latvian keyboard layout label. [CHAR LIMIT=35] -->
+    <string name="keyboard_layout_latvian">Latvian</string>
 </resources>
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index dc1db0b..6f7253c 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -143,4 +143,8 @@
     <keyboard-layout android:name="keyboard_layout_spanish_latin"
             android:label="@string/keyboard_layout_spanish_latin"
             android:keyboardLayout="@raw/keyboard_layout_spanish_latin" />
+
+    <keyboard-layout android:name="keyboard_layout_latvian"
+            android:label="@string/keyboard_layout_latvian"
+            android:keyboardLayout="@raw/keyboard_layout_latvian_qwerty" />
 </keyboard-layouts>
diff --git a/packages/Keyguard/res/layout/keyguard_status_view.xml b/packages/Keyguard/res/layout/keyguard_status_view.xml
index 0d943ed..f79819f 100644
--- a/packages/Keyguard/res/layout/keyguard_status_view.xml
+++ b/packages/Keyguard/res/layout/keyguard_status_view.xml
@@ -28,8 +28,6 @@
     androidprv:layout_maxWidth="@dimen/keyguard_security_width"
     androidprv:layout_maxHeight="@dimen/keyguard_security_height"
     android:gravity="center_horizontal|top"
-    android:layout_marginTop="48dp"
-    android:layout_marginBottom="32dp"
     android:contentDescription="@string/keyguard_accessibility_status">
     <LinearLayout
         android:layout_width="match_parent"
diff --git a/packages/Keyguard/res/values-sw600dp-land/dimens.xml b/packages/Keyguard/res/values-sw600dp-land/dimens.xml
index 5507e5f..5615ff7 100644
--- a/packages/Keyguard/res/values-sw600dp-land/dimens.xml
+++ b/packages/Keyguard/res/values-sw600dp-land/dimens.xml
@@ -23,4 +23,8 @@
 
     <!-- Size of margin on the right of keyguard's status view -->
     <dimen name="kg_status_line_font_right_margin">16dp</dimen>
+
+    <!-- Overload default clock widget parameters -->
+    <dimen name="widget_big_font_size">88dp</dimen>
+    <dimen name="bottom_text_spacing_digital">-24dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/Keyguard/res/values-sw600dp/dimens.xml b/packages/Keyguard/res/values-sw600dp/dimens.xml
index 25e86e1..a5e93dc 100644
--- a/packages/Keyguard/res/values-sw600dp/dimens.xml
+++ b/packages/Keyguard/res/values-sw600dp/dimens.xml
@@ -63,8 +63,9 @@
     <dimen name="keyguard_muliuser_selector_margin">12dp</dimen>
 
     <!-- Overload default clock widget parameters -->
-    <dimen name="widget_label_font_size">16dp</dimen>
-    <dimen name="widget_big_font_size">141dp</dimen>
+    <dimen name="widget_big_font_size">96dp</dimen>
+    <dimen name="widget_label_font_size">16sp</dimen>
+    <dimen name="bottom_text_spacing_digital">-24dp</dimen>
 
     <!-- EmergencyCarrierArea overlap - amount to overlap the emergency button and carrier text.
          Should be 0 on devices with plenty of room (e.g. tablets) -->
diff --git a/packages/Keyguard/res/values-sw720dp/dimens.xml b/packages/Keyguard/res/values-sw720dp/dimens.xml
index 30b979c..c487072 100644
--- a/packages/Keyguard/res/values-sw720dp/dimens.xml
+++ b/packages/Keyguard/res/values-sw720dp/dimens.xml
@@ -61,7 +61,4 @@
     <!-- Height of the sliding KeyguardSecurityContainer (includes 2x keyguard_security_view_margin) -->
     <dimen name="keyguard_security_height">420dp</dimen>
 
-    <!-- Default clock parameters -->
-    <dimen name="widget_label_font_size">19dp</dimen>
-
 </resources>
diff --git a/packages/Keyguard/res/values/dimens.xml b/packages/Keyguard/res/values/dimens.xml
index c05f834..6224aed 100644
--- a/packages/Keyguard/res/values/dimens.xml
+++ b/packages/Keyguard/res/values/dimens.xml
@@ -155,10 +155,10 @@
     <dimen name="eca_overlap">-10dip</dimen>
 
     <!-- Default clock parameters -->
-    <dimen name="bottom_text_spacing_digital">-8dp</dimen>
+    <dimen name="bottom_text_spacing_digital">-18dp</dimen>
     <dimen name="label_font_size">14dp</dimen>
-    <dimen name="widget_label_font_size">14dp</dimen>
-    <dimen name="widget_big_font_size">60dp</dimen>
+    <dimen name="widget_label_font_size">14sp</dimen>
+    <dimen name="widget_big_font_size">68dp</dimen>
     <dimen name="big_font_size">120dp</dimen>
 
 </resources>
diff --git a/packages/Keyguard/test/SampleTrustAgent/AndroidManifest.xml b/packages/Keyguard/test/SampleTrustAgent/AndroidManifest.xml
index 1511911..7904927 100644
--- a/packages/Keyguard/test/SampleTrustAgent/AndroidManifest.xml
+++ b/packages/Keyguard/test/SampleTrustAgent/AndroidManifest.xml
@@ -22,6 +22,7 @@
       <service
           android:name=".SampleTrustAgent"
           android:label="@string/app_name"
+          android:permission="android.permission.BIND_TRUST_AGENT"
           android:exported="true">
         <intent-filter>
           <action android:name="android.service.trust.TrustAgentService" />
diff --git a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
index 30eedee..8348e9b 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
@@ -18,7 +18,7 @@
 <com.android.systemui.statusbar.NotificationOverflowContainer
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="40dp"
+    android:layout_height="@dimen/notification_summary_height"
     android:focusable="true"
     android:clickable="true"
     >
diff --git a/packages/SystemUI/res/layout/status_bar_notification_speed_bump.xml b/packages/SystemUI/res/layout/status_bar_notification_speed_bump.xml
new file mode 100644
index 0000000..ff8800c
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_notification_speed_bump.xml
@@ -0,0 +1,65 @@
+<!--
+  ~ Copyright (C) 2014 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
+  -->
+
+<!-- Extends FrameLayout -->
+<com.android.systemui.statusbar.SpeedBumpView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:focusable="true"
+    android:clickable="true"
+    android:visibility="gone"
+    >
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/speed_bump_height_collapsed"
+            android:layout_gravity="top"
+            android:orientation="horizontal">
+        <View
+            android:id="@+id/speedbump_line_left"
+            android:layout_width="0dp"
+            android:layout_height="1dp"
+            android:layout_weight="1"
+            android:background="#6fdddddd"
+            android:layout_gravity="center_vertical"/>
+        <com.android.systemui.statusbar.SpeedBumpDotsLayout
+                android:id="@+id/speed_bump_dots_layout"
+                android:layout_width="34dp"
+                android:layout_marginStart="8dp"
+                android:layout_marginEnd="8dp"
+                android:layout_height="match_parent"
+                android:layout_weight="0"/>
+        <View
+            android:id="@+id/speedbump_line_right"
+            android:layout_width="0dp"
+            android:layout_height="1dp"
+            android:layout_weight="1"
+            android:background="#6fdddddd"
+            android:layout_gravity="center_vertical"/>
+    </LinearLayout>
+    <TextView
+            android:id="@+id/speed_bump_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top|center_horizontal"
+            android:fontFamily="sans-serif-condensed"
+            android:textSize="15sp"
+            android:singleLine="true"
+            android:textColor="#eeeeee"
+            android:visibility="invisible"
+            android:text="@string/speed_bump_explanation"
+            android:paddingTop="4dp" />
+</com.android.systemui.statusbar.SpeedBumpView>
diff --git a/packages/SystemUI/res/values-sw600dp-land/values-land-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
similarity index 85%
rename from packages/SystemUI/res/values-sw600dp-land/values-land-sw600dp/dimens.xml
rename to packages/SystemUI/res/values-sw600dp-land/dimens.xml
index e440de1..b510fef 100644
--- a/packages/SystemUI/res/values-sw600dp-land/values-land-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -18,4 +18,7 @@
 <resources>
     <!-- Recent Applications parameters -->
     <dimen name="status_bar_recents_app_label_width">190dip</dimen>
+
+    <fraction name="keyguard_clock_y_fraction_max">37%</fraction>
+    <fraction name="keyguard_clock_y_fraction_min">14%</fraction>
 </resources>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 7372181..22815f3 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -48,4 +48,15 @@
 
     <!-- Width of the zen mode interstitial dialog. -->
     <dimen name="zen_mode_dialog_width">384dp</dimen>
+
+    <!-- The fraction of the screen height where the clock on the Keyguard has its center. The
+         max value is used when no notifications are displaying, and the min value is when the
+         highest possible number of notifications are showing. -->
+    <fraction name="keyguard_clock_y_fraction_max">34%</fraction>
+    <fraction name="keyguard_clock_y_fraction_min">25%</fraction>
+
+    <!-- The margin between the clock and the notifications on Keyguard. See
+         keyguard_clock_height_fraction_* for the difference between min and max.-->
+    <dimen name="keyguard_clock_notifications_margin_min">32dp</dimen>
+    <dimen name="keyguard_clock_notifications_margin_max">32dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
new file mode 100644
index 0000000..2532dd6
--- /dev/null
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2014 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>
+    <!-- The fraction of the screen height where the clock on the Keyguard has its center. The
+         min value is used when no notifications are displaying, and the max value is when the
+         highest possible number of notifications are showing. -->
+    <fraction name="keyguard_clock_y_fraction_max">35%</fraction>
+    <fraction name="keyguard_clock_y_fraction_min">20%</fraction>
+</resources>
diff --git a/packages/SystemUI/res/values-sw720dp/config.xml b/packages/SystemUI/res/values-sw720dp/config.xml
index 7cf711a..d8bb8d7d 100644
--- a/packages/SystemUI/res/values-sw720dp/config.xml
+++ b/packages/SystemUI/res/values-sw720dp/config.xml
@@ -35,5 +35,9 @@
 
     <!-- Transposes the search bar layout in landscape -->
     <bool name="recents_transpose_search_layout_with_orientation">false</bool>
+
+    <!-- The maximum count of notifications on Keyguard. The rest will be collapsed in an overflow
+         card. -->
+    <integer name="keyguard_max_notification_count">5</integer>
 </resources>
 
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index a1c5b66..7695b12 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -65,5 +65,11 @@
     <!-- Where to place the app icon over the thumbnail -->
     <dimen name="status_bar_recents_app_icon_left_margin">0dp</dimen>
     <dimen name="status_bar_recents_app_icon_top_margin">8dp</dimen>
+
+    <!-- The fraction of the screen height where the clock on the Keyguard has its center. The
+     max value is used when no notifications are displaying, and the min value is when the
+     highest possible number of notifications are showing. -->
+    <fraction name="keyguard_clock_y_fraction_max">35%</fraction>
+    <fraction name="keyguard_clock_y_fraction_min">25%</fraction>
 </resources>
 
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 9582b21..cba13004 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -58,6 +58,18 @@
     <!-- Tint color for the content on the notification overflow card. -->
     <color name="keyguard_overflow_content_color">#ff666666</color>
 
+    <!-- The color of the red speed bump dot -->
+    <color name="speed_bump_dot_red">#ffd50000</color>
+
+    <!-- The color of the blue speed bump dot -->
+    <color name="speed_bump_dot_blue">#ff2962ff</color>
+
+    <!-- The color of the yellow speed bump dot -->
+    <color name="speed_bump_dot_yellow">#ffffd600</color>
+
+    <!-- The color of the green speed bump dot -->
+    <color name="speed_bump_dot_green">#ff00c853</color>
+
     <!-- The default recents task bar background color. -->
     <color name="recents_task_bar_default_background_color">#e6444444</color>
     <!-- The default recents task bar text color. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 79612e0..c15f25f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -82,6 +82,9 @@
     <!-- Height of a medium notification in the status bar -->
     <dimen name="notification_mid_height">128dp</dimen>
 
+    <!-- Height of a the summary ("more card") notification on keyguard. -->
+    <dimen name="notification_summary_height">40dp</dimen>
+
     <!-- size at which Notification icons will be drawn in the status bar -->
     <dimen name="status_bar_icon_drawing_size">18dip</dimen>
 
@@ -260,6 +263,15 @@
     <!-- The padding between the individual notification cards. -->
     <dimen name="notification_padding">4dp</dimen>
 
+    <!-- The height of the collapsed speed bump view. -->
+    <dimen name="speed_bump_height_collapsed">24dp</dimen>
+
+    <!-- The padding inset the explanation text needs compared to the collapsed height -->
+    <dimen name="speed_bump_text_padding_inset">10dp</dimen>
+
+    <!-- The height of the speed bump dots. -->
+    <dimen name="speed_bump_dots_height">5dp</dimen>
+
     <!-- The total height of the stack in its collapsed size (i.e. when quick settings is open) -->
     <dimen name="collapsed_stack_height">94dp</dimen>
 
@@ -280,4 +292,16 @@
     
     <!-- Minimum distance the user has to drag down to go to the full shade. -->
     <dimen name="keyguard_drag_down_min_distance">100dp</dimen>
+
+    <!-- The fraction of the screen height where the clock on the Keyguard has its center. The
+         max value is used when no notifications are displaying, and the min value is when the
+         highest possible number of notifications are showing. -->
+    <fraction name="keyguard_clock_y_fraction_max">29.5%</fraction>
+    <fraction name="keyguard_clock_y_fraction_min">18%</fraction>
+
+    <!-- The margin between the clock and the notifications on Keyguard. See
+         keyguard_clock_height_fraction_* for the difference between min and max.-->
+    <dimen name="keyguard_clock_notifications_margin_min">22dp</dimen>
+    <dimen name="keyguard_clock_notifications_margin_max">36dp</dimen>
+
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a50a0ac..b3829a3 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -553,6 +553,9 @@
         <item quantity="other">%d more</item>
     </plurals>
 
+    <!-- An explanation for the visual speed bump in the notifications, which will appear when you click on it. [CHAR LIMIT=50] -->
+    <string name="speed_bump_explanation">Less urgent notifications below</string>
+
     <!-- Shows to explain the double tap interaction with notifications: After tapping a notification on Keyguard, this will explain users to tap again to launch a notification. [CHAR LIMIT=60] -->
     <string name="notification_tap_again">Tap again to open</string>
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
index 1ca0476..1c12ac2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
@@ -400,15 +400,14 @@
             ActivityInfo info = ssp.getActivityInfo(t.baseIntent.getComponent(), t.userId);
             if (info == null) continue;
 
-            ActivityManager.RecentsActivityValues av = t.activityValues;
+            ActivityManager.TaskDescription av = t.taskDescription;
             String activityLabel = null;
             BitmapDrawable activityIcon = null;
             int activityColor = 0;
             if (av != null) {
-                activityLabel = (av.label != null ? av.label.toString() :
-                        ssp.getActivityLabel(info));
-                activityIcon = (av.icon != null) ? new BitmapDrawable(res, av.icon) : null;
-                activityColor = av.colorPrimary;
+                activityLabel = (av.getLabel() != null ? av.getLabel() : ssp.getActivityLabel(info));
+                activityIcon = (av.getIcon() != null) ? new BitmapDrawable(res, av.getIcon()) : null;
+                activityColor = av.getPrimaryColor();
             } else {
                 activityLabel = ssp.getActivityLabel(info);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java
index 8d82883..59d0ea6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java
@@ -96,18 +96,19 @@
                 int packageIndex = i % Constants.DebugFlags.App.SystemServicesProxyMockPackageCount;
                 ComponentName cn = new ComponentName("com.android.test" + packageIndex,
                         "com.android.test" + i + ".Activity");
+                String description = "" + i + " - " +
+                        Long.toString(Math.abs(new Random().nextLong()), 36);
                 // Create the recent task info
                 ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
                 rti.id = rti.persistentId = i;
                 rti.baseIntent = new Intent();
                 rti.baseIntent.setComponent(cn);
-                rti.activityValues = new ActivityManager.RecentsActivityValues();
-                rti.description = "" + i + " - " +
-                        Long.toString(Math.abs(new Random().nextLong()), 36);
+                rti.description = description;
                 if (i % 2 == 0) {
-                    rti.activityValues.label = rti.description;
-                    rti.activityValues.icon = Bitmap.createBitmap(mDummyIcon);
-                    rti.activityValues.colorPrimary = new Random().nextInt();
+                    rti.taskDescription = new ActivityManager.TaskDescription(description,
+                        Bitmap.createBitmap(mDummyIcon), new Random().nextInt());
+                } else {
+                    rti.taskDescription = new ActivityManager.TaskDescription();
                 }
                 tasks.add(rti);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 6090948..c1228d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -877,6 +877,7 @@
         entry.row = row;
         entry.row.setHeightRange(mRowMinHeight, mRowMaxHeight);
         entry.row.setOnActivatedListener(this);
+        entry.row.setIsBelowSpeedBump(isBelowSpeedBump(entry.notification));
         entry.expanded = contentViewLocal;
         entry.expandedPublic = publicViewLocal;
         entry.setBigContentView(bigContentViewLocal);
@@ -1039,8 +1040,8 @@
         if (DEBUG) {
             Log.d(TAG, "addNotificationViews: added at " + pos);
         }
-        updateRowStates();
         updateNotificationIcons();
+        updateRowStates();
     }
 
     private void addNotificationViews(IBinder key, StatusBarNotification notification) {
@@ -1060,6 +1061,7 @@
         mKeyguardIconOverflowContainer.getIconsView().removeAllViews();
         int n = mNotificationData.size();
         int visibleNotifications = 0;
+        int speedBumpIndex = -1;
         boolean onKeyguard = mState == StatusBarState.KEYGUARD;
         for (int i = n-1; i >= 0; i--) {
             NotificationData.Entry entry = mNotificationData.get(i);
@@ -1087,6 +1089,10 @@
                 entry.row.setVisibility(View.VISIBLE);
                 visibleNotifications++;
             }
+            if (entry.row.getVisibility() != View.GONE && speedBumpIndex == -1
+                    && entry.row.isBelowSpeedBump() ) {
+                speedBumpIndex = n - 1 - i;
+            }
         }
 
         if (onKeyguard && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0) {
@@ -1094,6 +1100,8 @@
         } else {
             mKeyguardIconOverflowContainer.setVisibility(View.GONE);
         }
+
+        mStackScroller.updateSpeedBumpIndex(speedBumpIndex);
     }
 
     private boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
@@ -1309,9 +1317,19 @@
         } else {
             entry.row.setOnClickListener(null);
         }
+        boolean wasBelow = entry.row.isBelowSpeedBump();
+        boolean nowBelow = isBelowSpeedBump(notification);
+        if (wasBelow != nowBelow) {
+            entry.row.setIsBelowSpeedBump(nowBelow);
+        }
         entry.row.notifyContentUpdated();
     }
 
+    private boolean isBelowSpeedBump(StatusBarNotification notification) {
+        return notification.getNotification().priority ==
+                Notification.PRIORITY_MIN;
+    }
+
     protected void notifyHeadsUpScreenOn(boolean screenOn) {
         if (!screenOn && mInterruptingNotificationEntry != null) {
             mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 39f2bb9..f6c80fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -52,6 +52,7 @@
     private NotificationContentView mPublicLayout;
     private NotificationContentView mPrivateLayout;
     private int mMaxExpandHeight;
+    private boolean mIsBelowSpeedBump;
 
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -244,6 +245,14 @@
         mPrivateLayout.setClipTopAmount(clipTopAmount);
     }
 
+    public boolean isBelowSpeedBump() {
+        return mIsBelowSpeedBump;
+    }
+
+    public void setIsBelowSpeedBump(boolean isBelow) {
+        this.mIsBelowSpeedBump = isBelow;
+    }
+
     public void notifyContentUpdated() {
         mPrivateLayout.notifyContentUpdated();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index 4bd0e1c..061396d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -40,11 +40,15 @@
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
         if (!mActualHeightInitialized && mActualHeight == 0) {
-            mActualHeight = getHeight();
+            mActualHeight = getInitialHeight();
         }
         mActualHeightInitialized = true;
     }
 
+    protected int getInitialHeight() {
+        return getHeight();
+    }
+
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         if (filterMotionEvent(ev)) {
@@ -146,6 +150,10 @@
         }
     }
 
+    public boolean isTransparent() {
+        return false;
+    }
+
     /**
      * A listener notifying when {@link #getActualHeight} changes.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotView.java
new file mode 100644
index 0000000..3ca021a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotView.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * An single dot of the {@link com.android.systemui.statusbar.SpeedBumpDotsLayout}
+ */
+public class SpeedBumpDotView extends View {
+
+    private final Paint mPaint = new Paint();
+
+    public SpeedBumpDotView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mPaint.setAntiAlias(true);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        float radius = getWidth() / 2.0f;
+        canvas.drawCircle(radius, radius, radius, mPaint);
+    }
+
+    public void setColor(int color) {
+        mPaint.setColor(color);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsAlgorithm.java
new file mode 100644
index 0000000..cac6327
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsAlgorithm.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.view.View;
+import com.android.systemui.R;
+
+/**
+ * The Algorithm of the {@link com.android.systemui.statusbar.SpeedBumpDotsLayout} which can be
+ * queried for {@link * com.android.systemui.statusbar.SpeedBumpDotsState}
+ */
+public class SpeedBumpDotsAlgorithm {
+
+    private final float mDotRadius;
+
+    public SpeedBumpDotsAlgorithm(Context context) {
+        mDotRadius = context.getResources().getDimensionPixelSize(R.dimen.speed_bump_dots_height)
+                / 2.0f;
+    }
+
+    public void getState(SpeedBumpDotsState resultState) {
+
+        // First reset the current state and ensure that every View has a ViewState
+        resultState.resetViewStates();
+
+        SpeedBumpDotsLayout hostView = resultState.getHostView();
+        boolean currentlyVisible = hostView.isCurrentlyVisible();
+        resultState.setActiveState(currentlyVisible
+                ? SpeedBumpDotsState.SHOWN
+                : SpeedBumpDotsState.HIDDEN);
+        int hostWidth = hostView.getWidth();
+        float layoutWidth = hostWidth - 2 * mDotRadius;
+        int childCount = hostView.getChildCount();
+        float paddingBetween = layoutWidth / (childCount - 1);
+        float centerY = hostView.getHeight() / 2.0f;
+        for (int i = 0; i < childCount; i++) {
+            View child = hostView.getChildAt(i);
+            SpeedBumpDotsState.ViewState viewState = resultState.getViewStateForView(child);
+            if (currentlyVisible) {
+                float xTranslation = i * paddingBetween;
+                viewState.xTranslation = xTranslation;
+                viewState.yTranslation = calculateYTranslation(hostView, centerY, xTranslation,
+                        layoutWidth);
+            } else {
+                viewState.xTranslation = layoutWidth / 2;
+                viewState.yTranslation = centerY - mDotRadius;
+            }
+            viewState.alpha = currentlyVisible ? 1.0f : 0.0f;
+            viewState.scale = currentlyVisible ? 1.0f : 0.5f;
+        }
+    }
+
+    private float calculateYTranslation(SpeedBumpDotsLayout hostView, float centerY,
+            float xTranslation, float layoutWidth) {
+        float t = hostView.getAnimationProgress();
+        if (t == 0.0f || t == 1.0f) {
+            return centerY - mDotRadius;
+        }
+        float damping = (0.5f -Math.abs(0.5f - t)) * 1.3f;
+        float partialOffset = xTranslation / layoutWidth;
+        float indentFactor = (float) (Math.sin((t + partialOffset * 1.5f) * - Math.PI) * damping);
+        return (1.0f - indentFactor) * centerY - mDotRadius;
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsLayout.java
new file mode 100644
index 0000000..ddf5215
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsLayout.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.animation.TimeAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import com.android.systemui.R;
+
+/**
+ * A layout with a certain number of dots which are integrated in the
+ * {@link com.android.systemui.statusbar.SpeedBumpView}
+ */
+public class SpeedBumpDotsLayout extends ViewGroup {
+
+    private static final float DOT_CLICK_ANIMATION_LENGTH = 300;
+    private final int mDotSize;
+    private final SpeedBumpDotsAlgorithm mAlgorithm = new SpeedBumpDotsAlgorithm(getContext());
+    private final SpeedBumpDotsState mCurrentState = new SpeedBumpDotsState(this);
+    private boolean mIsCurrentlyVisible = true;
+    private final ValueAnimator mClickAnimator;
+    private float mAnimationProgress;
+    private ValueAnimator.AnimatorUpdateListener mClickUpdateListener
+            = new ValueAnimator.AnimatorUpdateListener() {
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            mAnimationProgress = animation.getAnimatedFraction();
+            updateChildren();
+        }
+    };
+
+    public SpeedBumpDotsLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mDotSize = getResources().getDimensionPixelSize(R.dimen.speed_bump_dots_height);
+        createDots(context, attrs);
+        mClickAnimator = TimeAnimator.ofFloat(0, DOT_CLICK_ANIMATION_LENGTH);
+        mClickAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
+        mClickAnimator.addUpdateListener(mClickUpdateListener);
+    }
+
+    private void createDots(Context context, AttributeSet attrs) {
+        SpeedBumpDotView blueDot = new SpeedBumpDotView(context, attrs);
+        blueDot.setColor(getResources().getColor(R.color.speed_bump_dot_blue));
+        addView(blueDot);
+
+        SpeedBumpDotView redDot = new SpeedBumpDotView(context, attrs);
+        redDot.setColor(getResources().getColor(R.color.speed_bump_dot_red));
+        addView(redDot);
+
+        SpeedBumpDotView yellowDot = new SpeedBumpDotView(context, attrs);
+        yellowDot.setColor(getResources().getColor(R.color.speed_bump_dot_yellow));
+        addView(yellowDot);
+
+        SpeedBumpDotView greenDot = new SpeedBumpDotView(context, attrs);
+        greenDot.setColor(getResources().getColor(R.color.speed_bump_dot_green));
+        addView(greenDot);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        int childWidthSpec = MeasureSpec.makeMeasureSpec(mDotSize,
+                MeasureSpec.getMode(widthMeasureSpec));
+        int childHeightSpec = MeasureSpec.makeMeasureSpec(mDotSize,
+                MeasureSpec.getMode(heightMeasureSpec));
+        measureChildren(childWidthSpec, childHeightSpec);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            child.layout(0, 0, mDotSize, mDotSize);
+        }
+        if (changed) {
+            updateChildren();
+        }
+    }
+
+    private void updateChildren() {
+        mAlgorithm.getState(mCurrentState);
+        mCurrentState.apply();
+    }
+
+    public void performVisibilityAnimation(boolean visible) {
+        if (mClickAnimator.isRunning()) {
+            mClickAnimator.cancel();
+        }
+        mIsCurrentlyVisible = visible;
+        mAlgorithm.getState(mCurrentState);
+        mCurrentState.animateToState();
+    }
+
+    public void setInvisible() {
+        mIsCurrentlyVisible = false;
+        mAlgorithm.getState(mCurrentState);
+        mCurrentState.apply();
+    }
+
+    public boolean isCurrentlyVisible() {
+        return mIsCurrentlyVisible;
+    }
+
+    public void performDotClickAnimation() {
+        if (mClickAnimator.isRunning()) {
+            // don't perform an animation if it's running already
+            return;
+        }
+        mClickAnimator.start();
+    }
+
+
+    public float getAnimationProgress() {
+        return mAnimationProgress;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsState.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsState.java
new file mode 100644
index 0000000..06a7f95
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsState.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A state of a {@link com.android.systemui.statusbar.SpeedBumpDotsLayout}
+ */
+public class SpeedBumpDotsState {
+
+    public static final int HIDDEN = 1;
+    public static final int SHOWN = 2;
+    private static final int VISIBILITY_ANIMATION_DELAY_PER_ELEMENT = 80;
+
+    private final SpeedBumpDotsLayout mHostView;
+    private final HashMap<View, ViewState> mStateMap = new HashMap<View, ViewState>();
+    private final Interpolator mFastOutSlowInInterpolator;
+    private int mActiveState = 0;
+
+    public SpeedBumpDotsState(SpeedBumpDotsLayout hostLayout) {
+        mHostView = hostLayout;
+        mFastOutSlowInInterpolator = AnimationUtils
+                .loadInterpolator(hostLayout.getContext(),
+                        android.R.interpolator.fast_out_slow_in);
+    }
+
+    public SpeedBumpDotsLayout getHostView() {
+        return mHostView;
+    }
+
+    public void resetViewStates() {
+        int numChildren = mHostView.getChildCount();
+        for (int i = 0; i < numChildren; i++) {
+            View child = mHostView.getChildAt(i);
+            ViewState viewState = mStateMap.get(child);
+            if (viewState == null) {
+                viewState = new ViewState();
+                mStateMap.put(child, viewState);
+            }
+        }
+    }
+
+    public ViewState getViewStateForView(View requestedView) {
+        return mStateMap.get(requestedView);
+    }
+
+    public void apply() {
+        int childCount = mHostView.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = mHostView.getChildAt(i);
+            ViewState viewState = mStateMap.get(child);
+            float translationX = child.getTranslationX();
+            float translationY = child.getTranslationY();
+            float scale = child.getScaleX();
+            float alpha = child.getAlpha();
+            if (translationX != viewState.xTranslation) {
+                child.setTranslationX(viewState.xTranslation);
+            }
+            if (translationY != viewState.yTranslation) {
+                child.setTranslationY(viewState.yTranslation);
+            }
+            if (scale != viewState.scale) {
+                child.setScaleX(viewState.scale);
+                child.setScaleY(viewState.scale);
+            }
+            if (alpha != viewState.alpha) {
+                child.setAlpha(viewState.alpha);
+            }
+        }
+    }
+
+    public void animateToState() {
+        int childCount = mHostView.getChildCount();
+        int middleIndex = (childCount - 1) / 2;
+        long delayPerElement = VISIBILITY_ANIMATION_DELAY_PER_ELEMENT;
+        boolean isAppearing = getActiveState() == SHOWN;
+        boolean isDisappearing = getActiveState() == HIDDEN;
+        for (int i = 0; i < childCount; i++) {
+            int delayIndex;
+            if (i <= middleIndex) {
+                delayIndex = i * 2;
+            } else {
+                int distToMiddle = i - middleIndex;
+                delayIndex = (childCount - 1) - (distToMiddle - 1) * 2;
+            }
+            long startDelay = 0;
+            if (isAppearing || isDisappearing) {
+                if (isDisappearing) {
+                    delayIndex = childCount - 1 - delayIndex;
+                }
+                startDelay = delayIndex * delayPerElement;
+            }
+            View child = mHostView.getChildAt(i);
+            ViewState viewState = mStateMap.get(child);
+            child.animate().setInterpolator(mFastOutSlowInInterpolator)
+                    .setStartDelay(startDelay)
+                    .alpha(viewState.alpha).withLayer()
+                    .translationX(viewState.xTranslation)
+                    .translationY(viewState.yTranslation)
+                    .scaleX(viewState.scale).scaleY(viewState.scale);
+        }
+    }
+
+    public int getActiveState() {
+        return mActiveState;
+    }
+
+    public void setActiveState(int mActiveState) {
+        this.mActiveState = mActiveState;
+    }
+
+    public static class ViewState {
+        float xTranslation;
+        float yTranslation;
+        float alpha;
+        float scale;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java
new file mode 100644
index 0000000..8ae503a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Outline;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.TextView;
+import com.android.systemui.R;
+
+/**
+ * The view representing the separation between important and less important notifications
+ */
+public class SpeedBumpView extends ExpandableView implements View.OnClickListener {
+
+    private final int mCollapsedHeight;
+    private final int mDotsHeight;
+    private final int mTextPaddingInset;
+    private SpeedBumpDotsLayout mDots;
+    private View mLineLeft;
+    private View mLineRight;
+    private boolean mIsExpanded;
+    private boolean mDividerVisible = true;
+    private ValueAnimator mCurrentAnimator;
+    private final Interpolator mFastOutSlowInInterpolator;
+    private float mCenterX;
+    private TextView mExplanationText;
+    private boolean mExplanationTextVisible = false;
+    private AnimatorListenerAdapter mHideExplanationListener = new AnimatorListenerAdapter() {
+        private boolean mCancelled;
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            if (!mCancelled) {
+                mExplanationText.setVisibility(View.INVISIBLE);
+            }
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            mCancelled = true;
+        }
+
+        @Override
+        public void onAnimationStart(Animator animation) {
+            mCancelled = false;
+        }
+    };
+    private Animator.AnimatorListener mAnimationFinishedListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mCurrentAnimator = null;
+        }
+    };
+
+    public SpeedBumpView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mCollapsedHeight = getResources()
+                .getDimensionPixelSize(R.dimen.speed_bump_height_collapsed);
+        mTextPaddingInset = getResources().getDimensionPixelSize(
+                R.dimen.speed_bump_text_padding_inset);
+        mDotsHeight = getResources().getDimensionPixelSize(R.dimen.speed_bump_dots_height);
+        setOnClickListener(this);
+        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
+                android.R.interpolator.fast_out_slow_in);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mDots = (SpeedBumpDotsLayout) findViewById(R.id.speed_bump_dots_layout);
+        mLineLeft = findViewById(R.id.speedbump_line_left);
+        mLineRight = findViewById(R.id.speedbump_line_right);
+        mExplanationText = (TextView) findViewById(R.id.speed_bump_text);
+        resetExplanationText();
+
+    }
+
+    @Override
+    protected int getInitialHeight() {
+        return mCollapsedHeight;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return getActualHeight();
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        Outline outline = new Outline();
+        mCenterX = getWidth() / 2;
+        float centerY = getHeight() / 2;
+        // TODO: hide outline better
+        // Temporary workaround to hide outline on a transparent view
+        int outlineLeft = (int) (mCenterX - getResources().getDisplayMetrics().densityDpi * 8);
+        int outlineTop = (int) (centerY - mDotsHeight / 2);
+        outline.setOval(outlineLeft, outlineTop, outlineLeft + mDotsHeight,
+                outlineTop + mDotsHeight);
+        setOutline(outline);
+        mLineLeft.setPivotX(mLineLeft.getWidth());
+        mLineLeft.setPivotY(mLineLeft.getHeight() / 2);
+        mLineRight.setPivotX(0);
+        mLineRight.setPivotY(mLineRight.getHeight() / 2);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        measureChildren(widthMeasureSpec, heightMeasureSpec);
+        int height = mCollapsedHeight + mExplanationText.getMeasuredHeight() - mTextPaddingInset;
+        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), height);
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (mCurrentAnimator != null) {
+            return;
+        }
+        int startValue = mIsExpanded ? getMaxHeight() : mCollapsedHeight;
+        int endValue = mIsExpanded ? mCollapsedHeight : getMaxHeight();
+        mCurrentAnimator = ValueAnimator.ofInt(startValue, endValue);
+        mCurrentAnimator.setInterpolator(mFastOutSlowInInterpolator);
+        mCurrentAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                setActualHeight((int) animation.getAnimatedValue());
+            }
+        });
+        mCurrentAnimator.addListener(mAnimationFinishedListener);
+        mCurrentAnimator.start();
+        mIsExpanded = !mIsExpanded;
+        mDots.performDotClickAnimation();
+        animateExplanationTextInternal(mIsExpanded);
+    }
+
+    private void animateExplanationTextInternal(boolean visible) {
+        if (mExplanationTextVisible != visible) {
+            float translationY = 0.0f;
+            float scale = 0.5f;
+            float alpha = 0.0f;
+            boolean needsHideListener = true;
+            if (visible) {
+                mExplanationText.setVisibility(VISIBLE);
+                translationY = mDots.getBottom() - mTextPaddingInset;
+                scale = 1.0f;
+                alpha = 1.0f;
+                needsHideListener = false;
+            }
+            mExplanationText.animate().setInterpolator(mFastOutSlowInInterpolator)
+                    .alpha(alpha)
+                    .scaleX(scale)
+                    .scaleY(scale)
+                    .translationY(translationY)
+                    .setListener(needsHideListener ? mHideExplanationListener : null)
+                    .withLayer();
+            mExplanationTextVisible = visible;
+        }
+    }
+
+    @Override
+    public boolean isTransparent() {
+        return true;
+    }
+
+    public void performVisibilityAnimation(boolean nowVisible) {
+        animateDivider(nowVisible);
+
+        // Animate explanation Text
+        if (mIsExpanded) {
+            animateExplanationTextInternal(nowVisible);
+        }
+    }
+
+    public void animateDivider(boolean nowVisible) {
+        if (nowVisible != mDividerVisible) {
+            // Animate dividers
+            float endValue = nowVisible ? 1.0f : 0.0f;
+            float endTranslationXLeft = nowVisible ? 0.0f : mCenterX - mLineLeft.getRight();
+            float endTranslationXRight = nowVisible ? 0.0f : mCenterX - mLineRight.getLeft();
+            mLineLeft.animate()
+                    .alpha(endValue)
+                    .withLayer()
+                    .scaleX(endValue)
+                    .scaleY(endValue)
+                    .translationX(endTranslationXLeft)
+                    .setInterpolator(mFastOutSlowInInterpolator);
+            mLineRight.animate()
+                    .alpha(endValue)
+                    .withLayer()
+                    .scaleX(endValue)
+                    .scaleY(endValue)
+                    .translationX(endTranslationXRight)
+                    .setInterpolator(mFastOutSlowInInterpolator);
+
+            // Animate dots
+            mDots.performVisibilityAnimation(nowVisible);
+            mDividerVisible = nowVisible;
+        }
+    }
+
+    public void setInvisible() {
+        float endTranslationXLeft = mCenterX - mLineLeft.getRight();
+        float endTranslationXRight = mCenterX - mLineRight.getLeft();
+        mLineLeft.setAlpha(0.0f);
+        mLineLeft.setScaleX(0.0f);
+        mLineLeft.setScaleY(0.0f);
+        mLineLeft.setTranslationX(endTranslationXLeft);
+        mLineRight.setAlpha(0.0f);
+        mLineRight.setScaleX(0.0f);
+        mLineRight.setScaleY(0.0f);
+        mLineRight.setTranslationX(endTranslationXRight);
+        mDots.setInvisible();
+        resetExplanationText();
+
+        mDividerVisible = false;
+    }
+
+    public void collapse() {
+        if (mIsExpanded) {
+            setActualHeight(mCollapsedHeight);
+            mIsExpanded = false;
+        }
+        resetExplanationText();
+    }
+
+    public void animateExplanationText(boolean nowVisible) {
+        if (mIsExpanded) {
+            animateExplanationTextInternal(nowVisible);
+        }
+    }
+
+    private void resetExplanationText() {
+        mExplanationText.setTranslationY(0);
+        mExplanationText.setVisibility(INVISIBLE);
+        mExplanationText.setAlpha(0.0f);
+        mExplanationText.setScaleX(0.5f);
+        mExplanationText.setScaleY(0.5f);
+        mExplanationTextVisible = false;
+    }
+
+    public boolean isExpanded() {
+        return mIsExpanded;
+    }
+}
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 b9f5ab2..19252c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -18,6 +18,8 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.util.AttributeSet;
@@ -25,6 +27,7 @@
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
@@ -36,12 +39,11 @@
 import com.android.systemui.statusbar.GestureRecorder;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
 
 public class NotificationPanelView extends PanelView implements
         ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener,
         View.OnClickListener {
-    public static final boolean DEBUG_GESTURES = true;
-    private static final int EXPANSION_ANIMATION_LENGTH = 375;
 
     PhoneStatusBar mStatusBar;
     private StatusBarHeaderView mHeader;
@@ -80,6 +82,20 @@
     private FlingAnimationUtils mFlingAnimationUtils;
     private int mStatusBarMinHeight;
 
+    private int mClockNotificationsMarginMin;
+    private int mClockNotificationsMarginMax;
+    private float mClockYFractionMin;
+    private float mClockYFractionMax;
+    private Interpolator mFastOutSlowInInterpolator;
+    private ObjectAnimator mClockAnimator;
+    private int mClockAnimationTarget = -1;
+
+    /**
+     * The number (fractional) of notifications the "more" card counts when calculating how many
+     * notifications are currently visible for the y positioning of the clock.
+     */
+    private float mMoreCardNotificationAmount;
+
     public NotificationPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
@@ -113,9 +129,27 @@
         mNotificationStackScroller = (NotificationStackScrollLayout)
                 findViewById(R.id.notification_stack_scroller);
         mNotificationStackScroller.setOnHeightChangedListener(this);
+        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
+                android.R.interpolator.fast_out_slow_in);
+    }
+
+    @Override
+    protected void loadDimens() {
+        super.loadDimens();
         mNotificationTopPadding = getResources().getDimensionPixelSize(
                 R.dimen.notifications_top_padding);
         mMinStackHeight = getResources().getDimensionPixelSize(R.dimen.collapsed_stack_height);
+        mClockNotificationsMarginMin = getResources().getDimensionPixelSize(
+                R.dimen.keyguard_clock_notifications_margin_min);
+        mClockNotificationsMarginMax = getResources().getDimensionPixelSize(
+                R.dimen.keyguard_clock_notifications_margin_max);
+        mClockYFractionMin =
+                getResources().getFraction(R.fraction.keyguard_clock_y_fraction_min, 1, 1);
+        mClockYFractionMax =
+                getResources().getFraction(R.fraction.keyguard_clock_y_fraction_max, 1, 1);
+        mMoreCardNotificationAmount =
+                (float) getResources().getDimensionPixelSize(R.dimen.notification_summary_height) /
+                        getResources().getDimensionPixelSize(R.dimen.notification_min_height);
         mFlingAnimationUtils = new FlingAnimationUtils(getContext());
         mStatusBarMinHeight = getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.status_bar_height);
@@ -124,15 +158,8 @@
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
-        int keyguardBottomMargin =
-                ((MarginLayoutParams) mKeyguardStatusView.getLayoutParams()).bottomMargin;
         if (!mQsExpanded) {
-            mStackScrollerIntrinsicPadding = mStatusBar.getBarState() == StatusBarState.KEYGUARD
-                    ? mKeyguardStatusView.getBottom() + keyguardBottomMargin
-                    : mHeader.getBottom() + mNotificationTopPadding;
-            mNotificationStackScroller.setTopPadding(mStackScrollerIntrinsicPadding,
-                    mAnimateNextTopPaddingChange);
-            mAnimateNextTopPaddingChange = false;
+            positionClockAndNotifications();
         }
 
         // Calculate quick setting heights.
@@ -143,8 +170,81 @@
         }
     }
 
-    public void animateNextTopPaddingChange() {
+    /**
+     * Positions the clock and notifications dynamically depending on how many notifications are
+     * showing.
+     */
+    private void positionClockAndNotifications() {
+        boolean animateClock = mNotificationStackScroller.isAddOrRemoveAnimationPending();
+        if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
+            mStackScrollerIntrinsicPadding = mHeader.getBottom() + mNotificationTopPadding;
+        } else {
+            int notificationCount = mNotificationStackScroller.getNotGoneChildCount();
+            int y = getClockY(notificationCount) - mKeyguardStatusView.getHeight()/2;
+            int padding = getClockNotificationsPadding(notificationCount);
+            if (animateClock || mClockAnimator != null) {
+                startClockAnimation(y);
+            } else {
+                mKeyguardStatusView.setY(y);
+            }
+            mStackScrollerIntrinsicPadding = y + mKeyguardStatusView.getHeight() + padding;
+        }
+        mNotificationStackScroller.setTopPadding(mStackScrollerIntrinsicPadding,
+                mAnimateNextTopPaddingChange || animateClock);
+        mAnimateNextTopPaddingChange = false;
+    }
+
+    private void startClockAnimation(int y) {
+        if (mClockAnimationTarget == y) {
+            return;
+        }
+        mClockAnimationTarget = y;
+        getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+            @Override
+            public boolean onPreDraw() {
+                getViewTreeObserver().removeOnPreDrawListener(this);
+                if (mClockAnimator != null) {
+                    mClockAnimator.removeAllListeners();
+                    mClockAnimator.cancel();
+                }
+                mClockAnimator =
+                        ObjectAnimator.ofFloat(mKeyguardStatusView, View.Y, mClockAnimationTarget);
+                mClockAnimator.setInterpolator(mFastOutSlowInInterpolator);
+                mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+                mClockAnimator.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mClockAnimator = null;
+                        mClockAnimationTarget = -1;
+                    }
+                });
+                StackStateAnimator.startInstantly(mClockAnimator);
+                return true;
+            }
+        });
+    }
+
+    private int getClockNotificationsPadding(int notificationCount) {
+        float t = notificationCount
+                / (mStatusBar.getMaxKeyguardNotifications() + mMoreCardNotificationAmount);
+        t = Math.min(t, 1.0f);
+        return (int) (t * mClockNotificationsMarginMin + (1 - t) * mClockNotificationsMarginMax);
+    }
+
+    private float getClockYFraction(int notificationCount) {
+        float t = notificationCount
+                / (mStatusBar.getMaxKeyguardNotifications() + mMoreCardNotificationAmount);
+        t = Math.min(t, 1.0f);
+        return (1 - t) * mClockYFractionMax + t * mClockYFractionMin;
+    }
+
+    private int getClockY(int notificationCount) {
+        return (int) (getClockYFraction(notificationCount) * getHeight());
+    }
+
+    public void animateToFullShade() {
         mAnimateNextTopPaddingChange = true;
+        mNotificationStackScroller.goToFullShade();
         requestLayout();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index dde2ebb..517f763 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -322,7 +322,7 @@
         setOnHierarchyChangeListener(mHierarchyListener);
     }
 
-    private void loadDimens() {
+    protected void loadDimens() {
         final Resources res = getContext().getResources();
 
         mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity);
@@ -583,10 +583,16 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-
         loadDimens();
     }
 
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        loadDimens();
+        mMaxPanelHeight = -1;
+    }
+
     public void fling(float vel, boolean always) {
         if (DEBUG) logf("fling: vel=%.3f, this=%s", vel, this);
         mVel = vel;
@@ -697,12 +703,6 @@
         mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : h / fh);
     }
 
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        mMaxPanelHeight = -1;
-    }
-
     protected void onHeightUpdated(float expandedHeight) {
         requestLayout();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index d3b5f96..a92061f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -106,6 +106,7 @@
 import com.android.systemui.statusbar.NotificationData.Entry;
 import com.android.systemui.statusbar.NotificationOverflowContainer;
 import com.android.systemui.statusbar.SignalClusterView;
+import com.android.systemui.statusbar.SpeedBumpView;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -634,6 +635,10 @@
         mKeyguardIconOverflowContainer.setOnClickListener(mOverflowClickListener);
         mStackScroller.addView(mKeyguardIconOverflowContainer);
 
+        SpeedBumpView speedBump = (SpeedBumpView) LayoutInflater.from(mContext).inflate(
+                        R.layout.status_bar_notification_speed_bump, mStackScroller, false);
+        mStackScroller.setSpeedBumpView(speedBump);
+
         mExpandedContents = mStackScroller;
 
         mHeader = (StatusBarHeaderView) mStatusBarWindow.findViewById(R.id.header);
@@ -1150,7 +1155,7 @@
         ArrayList<View> toRemove = new ArrayList<View>();
         for (int i=0; i< mStackScroller.getChildCount(); i++) {
             View child = mStackScroller.getChildAt(i);
-            if (!toShow.contains(child) && child != mKeyguardIconOverflowContainer) {
+            if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
                 toRemove.add(child);
             }
         }
@@ -2722,7 +2727,7 @@
         setBarState(StatusBarState.SHADE);
         if (mLeaveOpenOnKeyguardHide) {
             mLeaveOpenOnKeyguardHide = false;
-            mNotificationPanel.animateNextTopPaddingChange();
+            mNotificationPanel.animateToFullShade();
         } else {
             instantCollapseNotificationPanel();
         }
@@ -2894,7 +2899,7 @@
             mLeaveOpenOnKeyguardHide = true;
             showBouncer();
         } else {
-            mNotificationPanel.animateNextTopPaddingChange();
+            mNotificationPanel.animateToFullShade();
             setBarState(StatusBarState.SHADE_LOCKED);
             updateKeyguardState();
         }
@@ -2917,4 +2922,12 @@
     public void reattachSystemIcons() {
         mSystemIconArea.addView(mSystemIcons, 0);
     }
+
+    public void onScreenTurnedOff() {
+        mStackScroller.setAnimationsEnabled(false);
+    }
+
+    public void onScreenTurnedOn() {
+        mStackScroller.setAnimationsEnabled(true);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 77b760e..1040c15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -125,11 +125,13 @@
 
     public void onScreenTurnedOff() {
         mScreenOn = false;
+        mPhoneStatusBar.onScreenTurnedOff();
         mBouncer.onScreenTurnedOff();
     }
 
     public void onScreenTurnedOn(final IKeyguardShowCallback callback) {
         mScreenOn = true;
+        mPhoneStatusBar.onScreenTurnedOn();
         if (callback != null) {
             callbackAfterDraw(callback);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index deab757..b21e12c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -30,6 +30,7 @@
     private View mActivatedChild;
     private float mOverScrollTopAmount;
     private float mOverScrollBottomAmount;
+    private int mSpeedBumpIndex = -1;
 
     public int getScrollY() {
         return mScrollY;
@@ -86,4 +87,12 @@
     public float getOverScrollAmount(boolean top) {
         return top ? mOverScrollTopAmount : mOverScrollBottomAmount;
     }
+
+    public int getSpeedBumpIndex() {
+        return mSpeedBumpIndex;
+    }
+
+    public void setSpeedBumpIndex(int speedBumpIndex) {
+        mSpeedBumpIndex = speedBumpIndex;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index fbb6695..58ada75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -39,6 +39,7 @@
 import com.android.systemui.SwipeHelper;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.ExpandableView;
+import com.android.systemui.statusbar.SpeedBumpView;
 import com.android.systemui.statusbar.stack.StackScrollState.ViewState;
 import com.android.systemui.statusbar.policy.ScrollAdapter;
 
@@ -107,6 +108,7 @@
             = new ArrayList<AnimationEvent>();
     private ArrayList<View> mSwipedOutViews = new ArrayList<View>();
     private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
+    private boolean mAnimationsEnabled;
 
     /**
      * The raw amount of the overScroll on the top, which is not rubber-banded.
@@ -126,6 +128,8 @@
     private boolean mActivateNeedsAnimation;
     private boolean mIsExpanded = true;
     private boolean mChildrenUpdateRequested;
+    private SpeedBumpView mSpeedBumpView;
+    private boolean mIsExpansionChanging;
     private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
             = new ViewTreeObserver.OnPreDrawListener() {
         @Override
@@ -244,6 +248,22 @@
         requestChildrenUpdate();
     }
 
+    public void updateSpeedBumpIndex(int newIndex) {
+        int currentIndex = indexOfChild(mSpeedBumpView);
+
+        // If we are currently layouted before the new speed bump index, we have to decrease it.
+        boolean validIndex = newIndex > 0;
+        if (newIndex > getChildCount() - 1) {
+            validIndex = false;
+            newIndex = -1;
+        }
+        if (validIndex && currentIndex != newIndex) {
+            changeViewPosition(mSpeedBumpView, newIndex);
+        }
+        updateSpeedBump(validIndex);
+        mAmbientState.setSpeedBumpIndex(newIndex);
+    }
+
     public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
         mListener = listener;
     }
@@ -333,7 +353,7 @@
             mTopPadding = topPadding;
             updateAlgorithmHeightAndPadding();
             updateContentHeight();
-            if (animate) {
+            if (animate && mAnimationsEnabled && mIsExpanded) {
                 mTopPaddingNeedsAnimation = true;
                 mNeedsAnimation =  true;
             }
@@ -421,9 +441,11 @@
     public void onChildSnappedBack(View animView) {
         mAmbientState.onDragFinished(animView);
         if (!mDragAnimPendingChildren.contains(animView)) {
-            mSnappedBackChildren.add(animView);
+            if (mAnimationsEnabled) {
+                mSnappedBackChildren.add(animView);
+                mNeedsAnimation = true;
+            }
             requestChildrenUpdate();
-            mNeedsAnimation = true;
         } else {
             // We start the swipe and snap back in the same frame, we don't want any animation
             mDragAnimPendingChildren.remove(animView);
@@ -432,10 +454,12 @@
 
     public void onBeginDrag(View v) {
         setSwipingInProgress(true);
-        mDragAnimPendingChildren.add(v);
         mAmbientState.onBeginDrag(v);
+        if (mAnimationsEnabled) {
+            mDragAnimPendingChildren.add(v);
+            mNeedsAnimation = true;
+        }
         requestChildrenUpdate();
-        mNeedsAnimation = true;
     }
 
     public void onDragCancelled(View v) {
@@ -942,6 +966,21 @@
         return null;
     }
 
+    /**
+     * @return the number of children which have visibility unequal to GONE
+     */
+    public int getNotGoneChildCount() {
+        int childCount = getChildCount();
+        int count = 0;
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            if (child.getVisibility() != View.GONE) {
+                count++;
+            }
+        }
+        return count;
+    }
+
     private int getMaxExpandHeight(View view) {
         if (view instanceof ExpandableNotificationRow) {
             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
@@ -1044,8 +1083,11 @@
         mCurrentStackScrollState.removeViewStateForView(child);
         mStackScrollAlgorithm.notifyChildrenChanged(this);
         updateScrollStateForRemovedChild(child);
-        if (mIsExpanded) {
+        generateRemoveAnimation(child);
+    }
 
+    private void generateRemoveAnimation(View child) {
+        if (mIsExpanded && mAnimationsEnabled) {
             if (!mChildrenToAddAnimated.contains(child)) {
                 // Generate Animations
                 mChildrenToRemoveAnimated.add(child);
@@ -1103,8 +1145,17 @@
         }
     }
 
+    public void setAnimationsEnabled(boolean animationsEnabled) {
+        mAnimationsEnabled = animationsEnabled;
+    }
+
+    public boolean isAddOrRemoveAnimationPending() {
+        return mNeedsAnimation
+                && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
+    }
+
     public void generateAddAnimation(View child) {
-        if (mIsExpanded) {
+        if (mIsExpanded && mAnimationsEnabled) {
 
             // Generate Animations
             mChildrenToAddAnimated.add(child);
@@ -1120,7 +1171,9 @@
      */
     public void changeViewPosition(View child, int newIndex) {
         if (child != null && child.getParent() == this) {
-            // TODO: handle this
+            removeView(child);
+            addView(child, newIndex);
+            // TODO: handle events
         }
     }
 
@@ -1362,10 +1415,12 @@
     }
 
     public void onExpansionStarted() {
+        mIsExpansionChanging = true;
         mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState);
     }
 
     public void onExpansionStopped() {
+        mIsExpansionChanging = false;
         mStackScrollAlgorithm.onExpansionStopped();
     }
 
@@ -1374,6 +1429,7 @@
         mStackScrollAlgorithm.setIsExpanded(isExpanded);
         if (!isExpanded) {
             mOwnScrollY = 0;
+            mSpeedBumpView.collapse();
         }
     }
 
@@ -1404,7 +1460,7 @@
         mStackScrollAlgorithm.setDimmed(dimmed);
         mAmbientState.setDimmed(dimmed);
         updatePadding(dimmed);
-        if (animate) {
+        if (animate && mAnimationsEnabled) {
             mDimmedNeedsAnimation = true;
             mNeedsAnimation =  true;
         }
@@ -1416,8 +1472,10 @@
      */
     public void setActivatedChild(View activatedChild) {
         mAmbientState.setActivatedChild(activatedChild);
-        mActivateNeedsAnimation = true;
-        mNeedsAnimation =  true;
+        if (mAnimationsEnabled) {
+            mActivateNeedsAnimation = true;
+            mNeedsAnimation =  true;
+        }
         requestChildrenUpdate();
     }
 
@@ -1432,6 +1490,34 @@
         }
     }
 
+    public void setSpeedBumpView(SpeedBumpView speedBumpView) {
+        mSpeedBumpView = speedBumpView;
+        addView(speedBumpView);
+    }
+
+    private void updateSpeedBump(boolean visible) {
+        int newVisibility = visible ? VISIBLE : GONE;
+        int oldVisibility = mSpeedBumpView.getVisibility();
+        if (newVisibility != oldVisibility) {
+            mSpeedBumpView.setVisibility(newVisibility);
+            if (visible) {
+                mSpeedBumpView.collapse();
+                // Make invisible to ensure that the appear animation is played.
+                mSpeedBumpView.setInvisible();
+                if (!mIsExpansionChanging) {
+                    generateAddAnimation(mSpeedBumpView);
+                }
+            } else {
+                mSpeedBumpView.performVisibilityAnimation(false);
+                generateRemoveAnimation(mSpeedBumpView);
+            }
+        }
+    }
+
+    public void goToFullShade() {
+        updateSpeedBump(true);
+    }
+
     /**
      * A listener that is notified when some child locations might have changed.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
index 8fc26d8..011411c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
@@ -23,6 +23,7 @@
 import android.view.ViewGroup;
 
 import com.android.systemui.statusbar.ExpandableView;
+import com.android.systemui.statusbar.SpeedBumpView;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -167,13 +168,50 @@
                         clipHeight,
                         (int) (newHeight - (previousNotificationStart - newYTranslation)));
 
-                previousNotificationStart = newYTranslation + child.getClipTopAmount();
-                previousNotificationEnd = newNotificationEnd;
-                previousNotificationIsSwiped = child.getTranslationX() != 0;
+                if (!child.isTransparent()) {
+                    // Only update the previous values if we are not transparent,
+                    // otherwise we would clip to a transparent view.
+                    previousNotificationStart = newYTranslation + child.getClipTopAmount();
+                    previousNotificationEnd = newNotificationEnd;
+                    previousNotificationIsSwiped = child.getTranslationX() != 0;
+                }
+
+                if(child instanceof SpeedBumpView) {
+                    performSpeedBumpAnimation(i, (SpeedBumpView) child, newNotificationEnd,
+                            newYTranslation);
+                }
             }
         }
     }
 
+    private void performSpeedBumpAnimation(int i, SpeedBumpView speedBump, float speedBumpEnd,
+            float speedBumpStart) {
+        View nextChild = getNextChildNotGone(i);
+        if (nextChild != null) {
+            ViewState nextState = getViewStateForView(nextChild);
+            boolean startIsAboveNext = nextState.yTranslation > speedBumpStart;
+            speedBump.animateDivider(startIsAboveNext);
+
+            // handle expanded case
+            if (speedBump.isExpanded()) {
+                boolean endIsAboveNext = nextState.yTranslation > speedBumpEnd;
+                speedBump.animateExplanationText(endIsAboveNext);
+            }
+
+        }
+    }
+
+    private View getNextChildNotGone(int childIndex) {
+        int childCount = mHostView.getChildCount();
+        for (int i = childIndex + 1; i < childCount; i++) {
+            View child = mHostView.getChildAt(i);
+            if (child.getVisibility() != View.GONE) {
+                return child;
+            }
+        }
+        return null;
+    }
+
     /**
      * Updates the shadow outline and the clipping for a view.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index 5ac51f8..a9dcdd6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -434,7 +434,7 @@
     /**
      * Start an animator instantly instead of waiting on the next synchronization frame
      */
-    private void startInstantly(ValueAnimator animator) {
+    public static void startInstantly(ValueAnimator animator) {
         animator.start();
         animator.setCurrentPlayTime(0);
     }
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index 1b96f1f..e204cb2 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -204,6 +204,8 @@
     private int mTextColor = 0;
     private int mStatusBarColor = 0;
     private int mNavigationBarColor = 0;
+    private boolean mForcedStatusBarColor = false;
+    private boolean mForcedNavigationBarColor = false;
 
     private CharSequence mTitle = null;
 
@@ -3084,8 +3086,12 @@
             }
             decor.setOnSystemUiVisibilityChangeListener(decor);
         }
-        mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
-        mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
+        if (!mForcedStatusBarColor) {
+            mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
+        }
+        if (!mForcedNavigationBarColor) {
+            mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
+        }
 
         if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion
                 >= android.os.Build.VERSION_CODES.HONEYCOMB) {
@@ -4245,19 +4251,29 @@
 
     @Override
     public int getStatusBarColor() {
-        return 0;
+        return mStatusBarColor;
     }
 
     @Override
     public void setStatusBarColor(int color) {
+        mStatusBarColor = color;
+        mForcedStatusBarColor = true;
+        if (mDecor != null) {
+            mDecor.updateColorViews(null);
+        }
     }
 
     @Override
     public int getNavigationBarColor() {
-        return 0;
+        return mNavigationBarColor;
     }
 
     @Override
     public void setNavigationBarColor(int color) {
+        mNavigationBarColor = color;
+        mForcedNavigationBarColor = true;
+        if (mDecor != null) {
+            mDecor.updateColorViews(null);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bab5b9c..7abc75f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -7087,50 +7087,7 @@
         rti.description = tr.lastDescription;
         rti.stackId = tr.stack.mStackId;
         rti.userId = tr.userId;
-
-        // Traverse upwards looking for any break between main task activities and
-        // utility activities.
-        final ArrayList<ActivityRecord> activities = tr.mActivities;
-        int activityNdx;
-        final int numActivities = activities.size();
-        for (activityNdx = Math.min(numActivities, 1); activityNdx < numActivities;
-             ++activityNdx) {
-            final ActivityRecord r = activities.get(activityNdx);
-            if (r.intent != null &&
-                    (r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
-                            != 0) {
-                break;
-            }
-        }
-        if (activityNdx > 0) {
-            // Traverse downwards starting below break looking for set label, icon.
-            // Note that if there are activities in the task but none of them set the
-            // recent activity values, then we do not fall back to the last set
-            // values in the TaskRecord.
-            rti.activityValues = new ActivityManager.RecentsActivityValues();
-            for (--activityNdx; activityNdx >= 0; --activityNdx) {
-                final ActivityRecord r = activities.get(activityNdx);
-                if (r.activityValues != null) {
-                    if (rti.activityValues.label == null) {
-                        rti.activityValues.label = r.activityValues.label;
-                        tr.lastActivityValues.label = r.activityValues.label;
-                    }
-                    if (rti.activityValues.icon == null) {
-                        rti.activityValues.icon = r.activityValues.icon;
-                        tr.lastActivityValues.icon = r.activityValues.icon;
-                    }
-                    if (rti.activityValues.colorPrimary == 0) {
-                        rti.activityValues.colorPrimary = r.activityValues.colorPrimary;
-                        tr.lastActivityValues.colorPrimary = r.activityValues.colorPrimary;
-                    }
-                }
-            }
-        } else {
-            // If there are no activity records in this task, then we use the last
-            // resolved values
-            rti.activityValues =
-                    new ActivityManager.RecentsActivityValues(tr.lastActivityValues);
-        }
+        rti.taskDescription = new ActivityManager.TaskDescription(tr.lastTaskDescription);
         return rti;
     }
 
@@ -7261,11 +7218,12 @@
     }
 
     @Override
-    public void setRecentsActivityValues(IBinder token, ActivityManager.RecentsActivityValues rav) {
+    public void setTaskDescription(IBinder token, ActivityManager.TaskDescription td) {
         synchronized (this) {
             ActivityRecord r = ActivityRecord.isInStackLocked(token);
             if (r != null) {
-                r.activityValues = rav;
+                r.taskDescription = td;
+                r.task.updateTaskDescription();
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 9582ac7..dbe2ca1 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -149,7 +149,7 @@
     boolean mStartingWindowShown = false;
     ActivityContainer mInitialActivityContainer;
 
-    ActivityManager.RecentsActivityValues activityValues; // the recents information for this activity
+    ActivityManager.TaskDescription taskDescription; // the recents information for this activity
 
     void dump(PrintWriter pw, String prefix) {
         final long now = SystemClock.uptimeMillis();
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 5d744e6..ef9c711 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1816,7 +1816,7 @@
             }
             targetStack = sourceTask.stack;
             targetStack.moveToFront();
-            mWindowManager.moveTaskToTop(sourceTask.taskId);
+            mWindowManager.moveTaskToTop(targetStack.topTask().taskId);
             if (!addingToTask &&
                     (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {
                 // In this case, we are adding the activity to an existing
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index be884e7..6d66b29 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -58,8 +58,8 @@
 
     // This represents the last resolved activity values for this task
     // NOTE: This value needs to be persisted with each task
-    ActivityManager.RecentsActivityValues lastActivityValues =
-            new ActivityManager.RecentsActivityValues();
+    ActivityManager.TaskDescription lastTaskDescription =
+            new ActivityManager.TaskDescription();
 
     /** List of all activities in the task arranged in history order */
     final ArrayList<ActivityRecord> mActivities = new ArrayList<ActivityRecord>();
@@ -486,6 +486,48 @@
         return null;
     }
 
+    /** Updates the last task description values. */
+    void updateTaskDescription() {
+        // Traverse upwards looking for any break between main task activities and
+        // utility activities.
+        int activityNdx;
+        final int numActivities = mActivities.size();
+        for (activityNdx = Math.min(numActivities, 1); activityNdx < numActivities;
+             ++activityNdx) {
+            final ActivityRecord r = mActivities.get(activityNdx);
+            if (r.intent != null &&
+                    (r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
+                            != 0) {
+                break;
+            }
+        }
+        if (activityNdx > 0) {
+            // Traverse downwards starting below break looking for set label, icon.
+            // Note that if there are activities in the task but none of them set the
+            // recent activity values, then we do not fall back to the last set
+            // values in the TaskRecord.
+            String label = null;
+            Bitmap icon = null;
+            int colorPrimary = 0;
+            for (--activityNdx; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = mActivities.get(activityNdx);
+                if (r.taskDescription != null) {
+                    if (label == null) {
+                        label = r.taskDescription.getLabel();
+                    }
+                    if (icon == null) {
+                        icon = r.taskDescription.getIcon();
+                    }
+                    if (colorPrimary == 0) {
+                        colorPrimary = r.taskDescription.getPrimaryColor();
+
+                    }
+                }
+            }
+            lastTaskDescription = new ActivityManager.TaskDescription(label, icon, colorPrimary);
+        }
+    }
+
     void dump(PrintWriter pw, String prefix) {
         if (numActivities != 0 || rootWasReset || userId != 0 || numFullscreen != 0) {
             pw.print(prefix); pw.print("numActivities="); pw.print(numActivities);
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index 3884ab0..096ab66 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -74,6 +74,14 @@
         mIsStarted = false;
         mIsRunning = false;
         mLP = new LinkProperties();
+
+        // If this is a runtime restart, it's possible that clatd is already
+        // running, but we don't know about it. If so, stop it.
+        try {
+            if (mNMService.isClatdStarted()) {
+                mNMService.stopClatd();
+            }
+        } catch(RemoteException e) {}  // Well, we tried.
     }
 
     /**
@@ -198,13 +206,13 @@
                 NetworkUtils.resetConnections(
                     CLAT_INTERFACE_NAME,
                     NetworkUtils.RESET_IPV4_ADDRESSES);
+                mBaseLP.removeStackedLink(mLP);
+                updateConnectivityService();
             }
             Slog.i(TAG, "interface " + CLAT_INTERFACE_NAME +
                    " removed, mIsRunning = " + mIsRunning + " -> false");
             mIsRunning = false;
-            mBaseLP.removeStackedLink(mLP);
             mLP.clear();
-            updateConnectivityService();
             Slog.i(TAG, "mLP = " + mLP);
         }
     }
diff --git a/services/core/java/com/android/server/task/StateChangedListener.java b/services/core/java/com/android/server/task/StateChangedListener.java
index a87bf95..db2d4ee 100644
--- a/services/core/java/com/android/server/task/StateChangedListener.java
+++ b/services/core/java/com/android/server/task/StateChangedListener.java
@@ -19,9 +19,9 @@
 import com.android.server.task.controllers.TaskStatus;
 
 /**
- * Interface through which a {@link StateController} informs the
- * {@link com.android.server.task.TaskManagerService} that there are some tasks potentially ready
- * to be run.
+ * Interface through which a {@link com.android.server.task.controllers.StateController} informs
+ * the {@link com.android.server.task.TaskManagerService} that there are some tasks potentially
+ * ready to be run.
  */
 public interface StateChangedListener {
     /**
diff --git a/services/core/java/com/android/server/task/TaskCompletedListener.java b/services/core/java/com/android/server/task/TaskCompletedListener.java
new file mode 100644
index 0000000..0210442
--- /dev/null
+++ b/services/core/java/com/android/server/task/TaskCompletedListener.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.task;
+
+/**
+ * Used for communication between {@link com.android.server.task.TaskServiceContext} and the
+ * {@link com.android.server.task.TaskManagerService}.
+ */
+public interface TaskCompletedListener {
+
+    /**
+     * Callback for when a task is completed.
+     * @param needsReschedule Whether the implementing class should reschedule this task.
+     */
+    public void onTaskCompleted(int serviceToken, int taskId, boolean needsReschedule);
+
+    /**
+     * Callback for when the implementing class needs to clean up the
+     * {@link com.android.server.task.TaskServiceContext}. The scheduler can get this callback
+     * several times if the TaskServiceContext got into a bad state (for e.g. the client crashed
+     * and it needs to clean up).
+     */
+    public void onAllTasksCompleted(int serviceToken);
+}
diff --git a/services/core/java/com/android/server/task/TaskList.java b/services/core/java/com/android/server/task/TaskList.java
deleted file mode 100644
index d2b8440..0000000
--- a/services/core/java/com/android/server/task/TaskList.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.task;
-
-import android.content.ComponentName;
-import android.content.Task;
-
-import com.android.server.task.controllers.TaskStatus;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Maintain a list of classes, and accessor methods/logic for these tasks.
- * This class offers the following functionality:
- *     - When a task is added, it will determine if the task requirements have changed (update) and
- *       whether the controllers need to be updated.
- *     - Persists Tasks, figures out when to to rewrite the Task to disk.
- *     - Is threadsafe.
- *     - Handles rescheduling of tasks.
- *       - When a periodic task is executed and must be re-added.
- *       - When a task fails and the client requests that it be retried with backoff.
- */
-public class TaskList {
-
-    final List<TaskStatus> mTasks;
-
-    TaskList() {
-        mTasks = intialiseTaskMapFromDisk();
-    }
-
-    /**
-     * Add a task to the master list, persisting it if necessary.
-     * @param task Task to add.
-     * @param persistable true if the TaskQueue should persist this task to the disk.
-     * @return true if this operation was successful. If false, this task was neither added nor
-     * persisted.
-     */
-    // TODO: implement this when i decide whether i want to key by TaskStatus
-    public boolean add(Task task, boolean persistable) {
-        return true;
-    }
-
-    /**
-     * Remove the provided task. Will also delete the task if it was persisted. Note that this
-     * function does not return the validity of the operation, as we assume a delete will always
-     * succeed.
-     * @param task Task to remove.
-     */
-    public void remove(Task task) {
-
-    }
-
-    /**
-     *
-     * @return
-     */
-    // TODO: Implement this.
-    private List<TaskStatus> intialiseTaskMapFromDisk() {
-        return new ArrayList<TaskStatus>();
-    }
-}
diff --git a/services/core/java/com/android/server/task/TaskManagerService.java b/services/core/java/com/android/server/task/TaskManagerService.java
index 5df4b2a..6d208ff 100644
--- a/services/core/java/com/android/server/task/TaskManagerService.java
+++ b/services/core/java/com/android/server/task/TaskManagerService.java
@@ -17,16 +17,15 @@
 package com.android.server.task;
 
 import android.content.Context;
+import android.content.Task;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.server.task.controllers.TaskStatus;
 
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * Responsible for taking tasks representing work to be performed by a client app, and determining
  * based on the criteria specified when that task should be run against the client application's
@@ -34,25 +33,29 @@
  * @hide
  */
 public class TaskManagerService extends com.android.server.SystemService
-        implements StateChangedListener {
+        implements StateChangedListener, TaskCompletedListener {
+    static final String TAG = "TaskManager";
 
     /** Master list of tasks. */
-    private final TaskList mTaskList;
+    private final TaskStore mTasks;
+
+    /** Check the pending queue and start any tasks. */
+    static final int MSG_RUN_PENDING = 0;
+    /** Initiate the stop task flow. */
+    static final int MSG_STOP_TASK = 1;
+    /** */
+    static final int MSG_CHECK_TASKS = 2;
 
     /**
      * Track Services that have currently active or pending tasks. The index is provided by
      * {@link TaskStatus#getServiceToken()}
      */
-    private final SparseArray<TaskServiceContext> mPendingTaskServices =
+    private final SparseArray<TaskServiceContext> mActiveServices =
             new SparseArray<TaskServiceContext>();
 
     private final TaskHandler mHandler;
 
     private class TaskHandler extends Handler {
-        /** Check the pending queue and start any tasks. */
-        static final int MSG_RUN_PENDING = 0;
-        /** Initiate the stop task flow. */
-        static final int MSG_STOP_TASK = 1;
 
         public TaskHandler(Looper looper) {
             super(looper);
@@ -67,21 +70,42 @@
                 case MSG_STOP_TASK:
 
                     break;
+                case MSG_CHECK_TASKS:
+                    checkTasks();
+                    break;
             }
         }
 
         /**
-         * Helper to post a message to this handler that will run through the pending queue and
-         * start any tasks it can.
+         * Called when we need to run through the list of all tasks and start/stop executing one or
+         * more of them.
          */
-        void sendRunPendingTasksMessage() {
-            Message m = Message.obtain(this, MSG_RUN_PENDING);
-            m.sendToTarget();
+        private void checkTasks() {
+            synchronized (mTasks) {
+                final SparseArray<TaskStatus> tasks = mTasks.getTasks();
+                for (int i = 0; i < tasks.size(); i++) {
+                    TaskStatus ts = tasks.valueAt(i);
+                    if (ts.isReady() && ! isCurrentlyActive(ts)) {
+                        assignTaskToServiceContext(ts);
+                    }
+                }
+            }
         }
+    }
 
-        void sendOnStopMessage(TaskStatus taskStatus) {
-
-        }
+    /**
+     * Entry point from client to schedule the provided task.
+     * This will add the task to the
+     * @param task Task object containing execution parameters
+     * @param userId The id of the user this task is for.
+     * @param uId The package identifier of the application this task is for.
+     * @param canPersistTask Whether or not the client has the appropriate permissions for persisting
+     *                    of this task.
+     * @return Result of this operation. See <code>TaskManager#RESULT_*</code> return codes.
+     */
+    public int schedule(Task task, int userId, int uId, boolean canPersistTask) {
+        TaskStatus taskStatus = mTasks.addNewTaskForUser(task, userId, uId, canPersistTask);
+        return 0;
     }
 
     /**
@@ -95,7 +119,7 @@
      */
     public TaskManagerService(Context context) {
         super(context);
-        mTaskList = new TaskList();
+        mTasks = new TaskStore(context);
         mHandler = new TaskHandler(context.getMainLooper());
     }
 
@@ -104,20 +128,18 @@
 
     }
 
+    // StateChangedListener implementations.
+
     /**
-     * Offboard work to our handler thread as quickly as possible, b/c this call is probably being
+     * Off-board work to our handler thread as quickly as possible, b/c this call is probably being
      * made on the main thread.
-     * @param taskStatus The state of the task which has changed.
+     * For now this takes the task and if it's ready to run it will run it. In future we might not
+     * provide the task, so that the StateChangedListener has to run through its list of tasks to
+     * see which are ready. This will further decouple the controllers from the execution logic.
      */
     @Override
     public void onTaskStateChanged(TaskStatus taskStatus) {
-        if (taskStatus.isReady()) {
-
-        } else {
-            if (mPendingTaskServices.get(taskStatus.getServiceToken()) != null) {
-                // The task is either pending or being executed, which we have to cancel.
-            }
-        }
+        postCheckTasksMessage();
 
     }
 
@@ -125,4 +147,60 @@
     public void onTaskDeadlineExpired(TaskStatus taskStatus) {
 
     }
+
+    // TaskCompletedListener implementations.
+
+    /**
+     * A task just finished executing. We fetch the
+     * {@link com.android.server.task.controllers.TaskStatus} from the store and depending on
+     * whether we want to reschedule we readd it to the controllers.
+     * @param serviceToken key for the service context in {@link #mActiveServices}.
+     * @param taskId Id of the task that is complete.
+     * @param needsReschedule Whether the implementing class should reschedule this task.
+     */
+    @Override
+    public void onTaskCompleted(int serviceToken, int taskId, boolean needsReschedule) {
+        final TaskServiceContext serviceContext = mActiveServices.get(serviceToken);
+        if (serviceContext == null) {
+            Log.e(TAG, "Task completed for invalid service context; " + serviceToken);
+            return;
+        }
+
+    }
+
+    @Override
+    public void onAllTasksCompleted(int serviceToken) {
+        
+    }
+
+    private void assignTaskToServiceContext(TaskStatus ts) {
+        TaskServiceContext serviceContext =
+                mActiveServices.get(ts.getServiceToken());
+        if (serviceContext == null) {
+            serviceContext = new TaskServiceContext(this, mHandler.getLooper(), ts);
+            mActiveServices.put(ts.getServiceToken(), serviceContext);
+        }
+        serviceContext.addPendingTask(ts);
+    }
+
+    /**
+     * @param ts TaskStatus we are querying against.
+     * @return Whether or not the task represented by the status object is currently being run or
+     * is pending.
+     */
+    private boolean isCurrentlyActive(TaskStatus ts) {
+        TaskServiceContext serviceContext = mActiveServices.get(ts.getServiceToken());
+        if (serviceContext == null) {
+            return false;
+        }
+        return serviceContext.hasTaskPending(ts);
+    }
+
+    /**
+     * Post a message to {@link #mHandler} to run through the list of tasks and start/stop any that
+     * are eligible.
+     */
+    private void postCheckTasksMessage() {
+        mHandler.obtainMessage(MSG_CHECK_TASKS).sendToTarget();
+    }
 }
diff --git a/services/core/java/com/android/server/task/TaskServiceContext.java b/services/core/java/com/android/server/task/TaskServiceContext.java
index 65c6fa5..b51cbb3 100644
--- a/services/core/java/com/android/server/task/TaskServiceContext.java
+++ b/services/core/java/com/android/server/task/TaskServiceContext.java
@@ -16,79 +16,500 @@
 
 package com.android.server.task;
 
+import android.app.ActivityManager;
 import android.app.task.ITaskCallback;
 import android.app.task.ITaskService;
+import android.app.task.TaskParams;
 import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
 import android.content.ServiceConnection;
-import android.content.Task;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
 
 import com.android.server.task.controllers.TaskStatus;
 
+import java.util.concurrent.atomic.AtomicBoolean;
+
 /**
  * Maintains information required to bind to a {@link android.app.task.TaskService}. This binding
- * can then be reused to start concurrent tasks on the TaskService. Information here is unique
- * within this service.
+ * is reused to start concurrent tasks on the TaskService. Information here is unique
+ * to the service.
  * Functionality provided by this class:
  *     - Managages wakelock for the service.
  *     - Sends onStartTask() and onStopTask() messages to client app, and handles callbacks.
  *     -
  */
 public class TaskServiceContext extends ITaskCallback.Stub implements ServiceConnection {
+    private static final String TAG = "TaskServiceContext";
+    /** Define the maximum # of tasks allowed to run on a service at once. */
+    private static final int defaultMaxActiveTasksPerService =
+            ActivityManager.isLowRamDeviceStatic() ? 1 : 3;
+    /** Amount of time a task is allowed to execute for before being considered timed-out. */
+    private static final long EXECUTING_TIMESLICE_MILLIS = 5 * 60 * 1000;
+    /** Amount of time the TaskManager will wait for a response from an app for a message. */
+    private static final long OP_TIMEOUT_MILLIS = 8 * 1000;
+    /** String prefix for all wakelock names. */
+    private static final String TM_WAKELOCK_PREFIX = "*task*/";
 
+    private static final String[] VERB_STRINGS = {
+            "VERB_STARTING", "VERB_EXECUTING", "VERB_STOPPING", "VERB_PENDING"
+    };
+
+    // States that a task occupies while interacting with the client.
+    private static final int VERB_STARTING = 0;
+    private static final int VERB_EXECUTING = 1;
+    private static final int VERB_STOPPING = 2;
+    private static final int VERB_PENDING = 3;
+
+    // Messages that result from interactions with the client service.
+    /** System timed out waiting for a response. */
+    private static final int MSG_TIMEOUT = 0;
+    /** Received a callback from client. */
+    private static final int MSG_CALLBACK = 1;
+    /** Run through list and start any ready tasks.*/
+    private static final int MSG_CHECK_PENDING = 2;
+    /** Cancel an active task. */
+    private static final int MSG_CANCEL = 3;
+    /** Add a pending task. */
+    private static final int MSG_ADD_PENDING = 4;
+    /** Client crashed, so we need to wind things down. */
+    private static final int MSG_SHUTDOWN = 5;
+
+    /** Used to identify this task service context when communicating with the TaskManager. */
+    final int token;
     final ComponentName component;
-    int uid;
+    final int userId;
     ITaskService service;
+    private final Handler mCallbackHandler;
+    /** Tasks that haven't been sent to the client for execution yet. */
+    private final SparseArray<ActiveTask> mPending;
+    /** Used for service binding, etc. */
+    private final Context mContext;
+    /** Make callbacks to {@link TaskManagerService} to inform on task completion status. */
+    final private TaskCompletedListener mCompletedListener;
+    private final PowerManager.WakeLock mWakeLock;
 
     /** Whether this service is actively bound. */
     boolean mBound;
 
-    TaskServiceContext(Task task) {
-        this.component = task.getService();
-    }
-
-    public void stopTask() {
-
-    }
-
-    public void startTask(Task task) {
-
+    TaskServiceContext(TaskManagerService taskManager, Looper looper, TaskStatus taskStatus) {
+        mContext = taskManager.getContext();
+        this.component = taskStatus.getServiceComponent();
+        this.token = taskStatus.getServiceToken();
+        this.userId = taskStatus.getUserId();
+        mCallbackHandler = new TaskServiceHandler(looper);
+        mPending = new SparseArray<ActiveTask>();
+        mCompletedListener = taskManager;
+        final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                TM_WAKELOCK_PREFIX + component.getPackageName());
+        mWakeLock.setWorkSource(new WorkSource(taskStatus.getUid()));
+        mWakeLock.setReferenceCounted(false);
     }
 
     @Override
     public void taskFinished(int taskId, boolean reschedule) {
-
+        mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0)
+                .sendToTarget();
     }
 
     @Override
-    public void acknowledgeStopMessage(int taskId) {
-
+    public void acknowledgeStopMessage(int taskId, boolean reschedule) {
+        mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, reschedule ? 1 : 0)
+                .sendToTarget();
     }
 
     @Override
-    public void acknowledgeStartMessage(int taskId) {
+    public void acknowledgeStartMessage(int taskId, boolean ongoing) {
+        mCallbackHandler.obtainMessage(MSG_CALLBACK, taskId, ongoing ? 1 : 0).sendToTarget();
+    }
 
+    /**
+     * Queue up this task to run on the client. This will execute the task as quickly as possible.
+     * @param ts Status of the task to run.
+     */
+    public void addPendingTask(TaskStatus ts) {
+        final TaskParams params = new TaskParams(ts.getTaskId(), ts.getExtras(), this);
+        final ActiveTask newTask = new ActiveTask(params, VERB_PENDING);
+        mCallbackHandler.obtainMessage(MSG_ADD_PENDING, newTask).sendToTarget();
+        if (!mBound) {
+            Intent intent = new Intent().setComponent(component);
+            boolean binding = mContext.bindServiceAsUser(intent, this,
+                    Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
+                    new UserHandle(userId));
+            if (!binding) {
+                Log.e(TAG, component.getShortClassName() + " unavailable.");
+                cancelPendingTask(ts);
+            }
+        }
+    }
+
+    /**
+     * Called externally when a task that was scheduled for execution should be cancelled.
+     * @param ts The status of the task to cancel.
+     */
+    public void cancelPendingTask(TaskStatus ts) {
+        mCallbackHandler.obtainMessage(MSG_CANCEL, ts.getTaskId(), -1 /* arg2 */)
+                .sendToTarget();
+    }
+
+    /**
+     * MSG_TIMEOUT is sent with the {@link com.android.server.task.TaskServiceContext.ActiveTask}
+     * set in the {@link Message#obj} field. This makes it easier to remove timeouts for a given
+     * ActiveTask.
+     * @param op Operation that is taking place.
+     */
+    private void scheduleOpTimeOut(ActiveTask op) {
+        mCallbackHandler.removeMessages(MSG_TIMEOUT, op);
+
+        final long timeoutMillis = (op.verb == VERB_EXECUTING) ?
+                EXECUTING_TIMESLICE_MILLIS : OP_TIMEOUT_MILLIS;
+        if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
+            Slog.d(TAG, "Scheduling time out for '" + component.getShortClassName() + "' tId: " +
+                    op.params.getTaskId() + ", in " + (timeoutMillis / 1000) + " s");
+        }
+        Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT, op);
+        mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
     }
 
     /**
      * @return true if this task is pending or active within this context.
      */
     public boolean hasTaskPending(TaskStatus taskStatus) {
-        return true;
+        synchronized (mPending) {
+            return mPending.get(taskStatus.getTaskId()) != null;
+        }
     }
 
     public boolean isBound() {
         return mBound;
     }
 
+    /**
+     * We acquire/release the wakelock on onServiceConnected/unbindService. This mirrors the work
+     * we intend to send to the client - we stop sending work when the service is unbound so until
+     * then we keep the wakelock.
+     * @param name The concrete component name of the service that has
+     * been connected.
+     * @param service The IBinder of the Service's communication channel,
+     */
     @Override
     public void onServiceConnected(ComponentName name, IBinder service) {
-
         mBound = true;
+        this.service = ITaskService.Stub.asInterface(service);
+        // Remove all timeouts. We've just connected to the client so there are no other
+        // MSG_TIMEOUTs at this point.
+        mCallbackHandler.removeMessages(MSG_TIMEOUT);
+        mWakeLock.acquire();
+        mCallbackHandler.obtainMessage(MSG_CHECK_PENDING).sendToTarget();
     }
 
+    /**
+     * When the client service crashes we can have a couple tasks executing, in various stages of
+     * undress. We'll cancel all of them and request that they be rescheduled.
+     * @param name The concrete component name of the service whose
+     */
     @Override
     public void onServiceDisconnected(ComponentName name) {
-        mBound = false;
+        // Service disconnected... probably client crashed.
+        startShutdown();
+    }
+
+    /**
+     * We don't just shutdown outright - we make sure the scheduler isn't going to send us any more
+     * tasks, then we do the shutdown.
+     */
+    private void startShutdown() {
+        mCompletedListener.onAllTasksCompleted(token);
+        mCallbackHandler.obtainMessage(MSG_SHUTDOWN).sendToTarget();
+    }
+
+    /** Tracks a task across its various state changes. */
+    private static class ActiveTask {
+        final TaskParams params;
+        int verb;
+        AtomicBoolean cancelled = new AtomicBoolean();
+
+        ActiveTask(TaskParams params, int verb) {
+            this.params = params;
+            this.verb = verb;
+        }
+
+        @Override
+        public String toString() {
+            return params.getTaskId() + " " + VERB_STRINGS[verb];
+        }
+    }
+
+    /**
+     * Handles the lifecycle of the TaskService binding/callbacks, etc. The convention within this
+     * class is to append 'H' to each function name that can only be called on this handler. This
+     * isn't strictly necessary because all of these functions are private, but helps clarity.
+     */
+    private class TaskServiceHandler extends Handler {
+        TaskServiceHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message message) {
+            switch (message.what) {
+                case MSG_ADD_PENDING:
+                    if (message.obj != null) {
+                        ActiveTask pendingTask = (ActiveTask) message.obj;
+                        mPending.put(pendingTask.params.getTaskId(), pendingTask);
+                    }
+                    // fall through.
+                case MSG_CHECK_PENDING:
+                    checkPendingTasksH();
+                    break;
+                case MSG_CALLBACK:
+                    ActiveTask receivedCallback = mPending.get(message.arg1);
+                    removeMessages(MSG_TIMEOUT, receivedCallback);
+
+                    if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
+                        Log.d(TAG, "MSG_CALLBACK of : " + receivedCallback);
+                    }
+
+                    if (receivedCallback.verb == VERB_STARTING) {
+                        final boolean workOngoing = message.arg2 == 1;
+                        handleStartedH(receivedCallback, workOngoing);
+                    } else if (receivedCallback.verb == VERB_EXECUTING ||
+                            receivedCallback.verb == VERB_STOPPING) {
+                        final boolean reschedule = message.arg2 == 1;
+                        handleFinishedH(receivedCallback, reschedule);
+                    } else {
+                        if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
+                            Log.d(TAG, "Unrecognised callback: " + receivedCallback);
+                        }
+                    }
+                    break;
+                case MSG_CANCEL:
+                    ActiveTask cancelled = mPending.get(message.arg1);
+                    handleCancelH(cancelled);
+                    break;
+                case MSG_TIMEOUT:
+                    // Timeout msgs have the ActiveTask ref so we can remove them easily.
+                    handleOpTimeoutH((ActiveTask) message.obj);
+                    break;
+                case MSG_SHUTDOWN:
+                    handleShutdownH();
+                    break;
+                default:
+                    Log.e(TAG, "Unrecognised message: " + message);
+            }
+        }
+
+        /**
+         * State behaviours.
+         * VERB_STARTING   -> Successful start, change task to VERB_EXECUTING and post timeout.
+         *     _PENDING    -> Error
+         *     _EXECUTING  -> Error
+         *     _STOPPING   -> Error
+         */
+        private void handleStartedH(ActiveTask started, boolean workOngoing) {
+            switch (started.verb) {
+                case VERB_STARTING:
+                    started.verb = VERB_EXECUTING;
+                    if (!workOngoing) {
+                        // Task is finished already so fast-forward to handleFinished.
+                        handleFinishedH(started, false);
+                        return;
+                    } else if (started.cancelled.get()) {
+                        // Cancelled *while* waiting for acknowledgeStartMessage from client.
+                        handleCancelH(started);
+                        return;
+                    } else {
+                        scheduleOpTimeOut(started);
+                    }
+                    break;
+                default:
+                    Log.e(TAG, "Handling started task but task wasn't starting! " + started);
+                    return;
+            }
+        }
+
+        /**
+         * VERB_EXECUTING  -> Client called taskFinished(), clean up and notify done.
+         *     _STOPPING   -> Successful finish, clean up and notify done.
+         *     _STARTING   -> Error
+         *     _PENDING    -> Error
+         */
+        private void handleFinishedH(ActiveTask executedTask, boolean reschedule) {
+            switch (executedTask.verb) {
+                case VERB_EXECUTING:
+                case VERB_STOPPING:
+                    closeAndCleanupTaskH(executedTask, reschedule);
+                    break;
+                default:
+                    Log.e(TAG, "Got an execution complete message for a task that wasn't being" +
+                            "executed. " + executedTask);
+            }
+        }
+
+        /**
+         * A task can be in various states when a cancel request comes in:
+         * VERB_PENDING    -> Remove from queue.
+         *     _STARTING   -> Mark as cancelled and wait for {@link #acknowledgeStartMessage(int)}.
+         *     _EXECUTING  -> call {@link #sendStopMessageH}}.
+         *     _ENDING     -> No point in doing anything here, so we ignore.
+         */
+        private void handleCancelH(ActiveTask cancelledTask) {
+            switch (cancelledTask.verb) {
+                case VERB_PENDING:
+                    mPending.remove(cancelledTask.params.getTaskId());
+                    break;
+                case VERB_STARTING:
+                    cancelledTask.cancelled.set(true);
+                    break;
+                case VERB_EXECUTING:
+                    cancelledTask.verb = VERB_STOPPING;
+                    sendStopMessageH(cancelledTask);
+                    break;
+                case VERB_STOPPING:
+                    // Nada.
+                    break;
+                default:
+                    Log.e(TAG, "Cancelling a task without a valid verb: " + cancelledTask);
+                    break;
+            }
+        }
+
+        /**
+         * This TaskServiceContext is shutting down. Remove all the tasks from the pending queue
+         * and reschedule them as if they had failed.
+         * Before posting this message, caller must invoke
+         * {@link com.android.server.task.TaskCompletedListener#onAllTasksCompleted(int)}.
+         */
+        private void handleShutdownH() {
+            for (int i = 0; i < mPending.size(); i++) {
+                ActiveTask at = mPending.valueAt(i);
+                closeAndCleanupTaskH(at, true /* needsReschedule */);
+            }
+            mWakeLock.release();
+            mContext.unbindService(TaskServiceContext.this);
+            service = null;
+            mBound = false;
+        }
+
+        /**
+         * MSG_TIMEOUT gets processed here.
+         * @param timedOutTask The task that timed out.
+         */
+        private void handleOpTimeoutH(ActiveTask timedOutTask) {
+            if (Log.isLoggable(TaskManagerService.TAG, Log.DEBUG)) {
+                Log.d(TAG, "MSG_TIMEOUT of " + component.getShortClassName() + " : "
+                        + timedOutTask.params.getTaskId());
+            }
+
+            final int taskId = timedOutTask.params.getTaskId();
+            switch (timedOutTask.verb) {
+                case VERB_STARTING:
+                    // Client unresponsive - wedged or failed to respond in time. We don't really
+                    // know what happened so let's log it and notify the TaskManager
+                    // FINISHED/NO-RETRY.
+                    Log.e(TAG, "No response from client for onStartTask '" +
+                            component.getShortClassName() + "' tId: " + taskId);
+                    closeAndCleanupTaskH(timedOutTask, false /* needsReschedule */);
+                    break;
+                case VERB_STOPPING:
+                    // At least we got somewhere, so fail but ask the TaskManager to reschedule.
+                    Log.e(TAG, "No response from client for onStopTask, '" +
+                            component.getShortClassName() + "' tId: " + taskId);
+                    closeAndCleanupTaskH(timedOutTask, true /* needsReschedule */);
+                    break;
+                case VERB_EXECUTING:
+                    // Not an error - client ran out of time.
+                    Log.i(TAG, "Client timed out while executing (no taskFinished received)." +
+                            " Reporting failure and asking for reschedule. "  +
+                            component.getShortClassName() + "' tId: " + taskId);
+                    sendStopMessageH(timedOutTask);
+                    break;
+                default:
+                    Log.e(TAG, "Handling timeout for an unknown active task state: "
+                            + timedOutTask);
+                    return;
+            }
+        }
+
+        /**
+         * Called on the handler thread. Checks the state of the pending queue and starts the task
+         * if it can. The task only starts if there is capacity on the service.
+         */
+        private void checkPendingTasksH() {
+            if (!mBound) {
+                return;
+            }
+            for (int i = 0; i < mPending.size() && i < defaultMaxActiveTasksPerService; i++) {
+                ActiveTask at = mPending.valueAt(i);
+                if (at.verb != VERB_PENDING) {
+                    continue;
+                }
+                sendStartMessageH(at);
+            }
+        }
+
+        /**
+         * Already running, need to stop. Rund on handler.
+         * @param stoppingTask Task we are sending onStopMessage for. This task will be moved from
+         *                     VERB_EXECUTING -> VERB_STOPPING.
+         */
+        private void sendStopMessageH(ActiveTask stoppingTask) {
+            mCallbackHandler.removeMessages(MSG_TIMEOUT, stoppingTask);
+            if (stoppingTask.verb != VERB_EXECUTING) {
+                Log.e(TAG, "Sending onStopTask for a task that isn't started. " + stoppingTask);
+                // TODO: Handle error?
+                return;
+            }
+            try {
+                service.stopTask(stoppingTask.params);
+                stoppingTask.verb = VERB_STOPPING;
+                scheduleOpTimeOut(stoppingTask);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error sending onStopTask to client.", e);
+                closeAndCleanupTaskH(stoppingTask, false);
+            }
+        }
+
+        /** Start the task on the service. */
+        private void sendStartMessageH(ActiveTask pendingTask) {
+            if (pendingTask.verb != VERB_PENDING) {
+                Log.e(TAG, "Sending onStartTask for a task that isn't pending. " + pendingTask);
+                // TODO: Handle error?
+            }
+            try {
+                service.startTask(pendingTask.params);
+                pendingTask.verb = VERB_STARTING;
+                scheduleOpTimeOut(pendingTask);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error sending onStart message to '" + component.getShortClassName()
+                        + "' ", e);
+            }
+        }
+
+        /**
+         * The provided task has finished, either by calling
+         * {@link android.app.task.TaskService#taskFinished(android.app.task.TaskParams, boolean)}
+         * or from acknowledging the stop message we sent. Either way, we're done tracking it and
+         * we want to clean up internally.
+         */
+        private void closeAndCleanupTaskH(ActiveTask completedTask, boolean reschedule) {
+            removeMessages(MSG_TIMEOUT, completedTask);
+            mPending.remove(completedTask.params.getTaskId());
+            if (mPending.size() == 0) {
+                startShutdown();
+            }
+            mCompletedListener.onTaskCompleted(token, completedTask.params.getTaskId(), reschedule);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/task/TaskStore.java b/services/core/java/com/android/server/task/TaskStore.java
new file mode 100644
index 0000000..3bfc8a5
--- /dev/null
+++ b/services/core/java/com/android/server/task/TaskStore.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.task;
+
+import android.content.Context;
+import android.content.Task;
+import android.util.SparseArray;
+
+import com.android.server.task.controllers.TaskStatus;
+
+/**
+ * Maintain a list of classes, and accessor methods/logic for these tasks.
+ * This class offers the following functionality:
+ *     - When a task is added, it will determine if the task requirements have changed (update) and
+ *       whether the controllers need to be updated.
+ *     - Persists Tasks, figures out when to to rewrite the Task to disk.
+ *     - Is threadsafe.
+ *     - Handles rescheduling of tasks.
+ *       - When a periodic task is executed and must be re-added.
+ *       - When a task fails and the client requests that it be retried with backoff.
+ *       - This class is <strong>not</strong> thread-safe.
+ */
+public class TaskStore {
+
+    /**
+     * Master list, indexed by {@link com.android.server.task.controllers.TaskStatus#hashCode()}.
+     */
+    final SparseArray<TaskStatus> mTasks;
+    final Context mContext;
+
+    TaskStore(Context context) {
+        mTasks = intialiseTaskMapFromDisk();
+        mContext = context;
+    }
+
+    /**
+     * Add a task to the master list, persisting it if necessary.
+     * Will first check to see if the task already exists. If so, it will replace it.
+     * {@link android.content.pm.PackageManager} is queried to see if the calling package has
+     * permission to
+     * @param task Task to add.
+     * @return The initialised TaskStatus object if this operation was successful, null if it
+     * failed.
+     */
+    public TaskStatus addNewTaskForUser(Task task, int userId, int uId,
+                                                     boolean canPersistTask) {
+        TaskStatus taskStatus = TaskStatus.getForTaskAndUser(task, userId, uId);
+        if (canPersistTask && task.isPeriodic()) {
+            if (writeStatusToDisk()) {
+                mTasks.put(taskStatus.hashCode(), taskStatus);
+            }
+        }
+        return taskStatus;
+    }
+
+    /**
+     * Remove the provided task. Will also delete the task if it was persisted. Note that this
+     * function does not return the validity of the operation, as we assume a delete will always
+     * succeed.
+     * @param task Task to remove.
+     */
+    public void remove(Task task) {
+
+    }
+
+    /**
+     * Every time the state changes we write all the tasks in one swathe, instead of trying to
+     * track incremental changes.
+     */
+    private boolean writeStatusToDisk() {
+        return true;
+    }
+
+    /**
+     *
+     * @return
+     */
+    // TODO: Implement this.
+    private SparseArray<TaskStatus> intialiseTaskMapFromDisk() {
+        return new SparseArray<TaskStatus>();
+    }
+
+    /**
+     * @return The live array of TaskStatus objects.
+     */
+    public SparseArray<TaskStatus> getTasks() {
+        return mTasks;
+    }
+}
diff --git a/services/core/java/com/android/server/task/controllers/ConnectivityController.java b/services/core/java/com/android/server/task/controllers/ConnectivityController.java
index 5cca77c..6a4e1f3 100644
--- a/services/core/java/com/android/server/task/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/task/controllers/ConnectivityController.java
@@ -41,6 +41,11 @@
     private final BroadcastReceiver mConnectivityChangedReceiver =
             new ConnectivityChangedReceiver();
 
+    /** Track whether the latest active network is metered. */
+    private boolean mMetered;
+    /** Track whether the latest active network is connected. */
+    private boolean mConnectivity;
+
     public ConnectivityController(TaskManagerService service) {
         super(service);
         // Register connectivity changed BR.
@@ -53,6 +58,8 @@
     @Override
     public void maybeTrackTaskState(TaskStatus taskStatus) {
         if (taskStatus.hasConnectivityConstraint() || taskStatus.hasMeteredConstraint()) {
+            taskStatus.connectivityConstraintSatisfied.set(mConnectivity);
+            taskStatus.meteredConstraintSatisfied.set(mMetered);
             mTrackedTasks.add(taskStatus);
         }
     }
@@ -63,19 +70,16 @@
     }
 
     /**
-     * @param isConnected Whether the active network is connected for the given uid
-     * @param isMetered Whether the active network is metered for the given uid. This is
-     *                  necessarily false if <code>isConnected</code> is false.
      * @param userId Id of the user for whom we are updating the connectivity state.
      */
-    private void updateTrackedTasks(boolean isConnected, boolean isMetered, int userId) {
+    private void updateTrackedTasks(int userId) {
         for (TaskStatus ts : mTrackedTasks) {
             if (ts.userId != userId) {
                 continue;
             }
-            boolean prevIsConnected = ts.connectivityConstraintSatisfied.getAndSet(isConnected);
-            boolean prevIsMetered = ts.meteredConstraintSatisfied.getAndSet(isMetered);
-            if (prevIsConnected != isConnected || prevIsMetered != isMetered) {
+            boolean prevIsConnected = ts.connectivityConstraintSatisfied.getAndSet(mConnectivity);
+            boolean prevIsMetered = ts.meteredConstraintSatisfied.getAndSet(mMetered);
+            if (prevIsConnected != mConnectivity || prevIsMetered != mMetered) {
                     mStateChangedListener.onTaskStateChanged(ts);
             }
         }
@@ -83,12 +87,13 @@
 
     class ConnectivityChangedReceiver extends BroadcastReceiver {
         /**
-         * We'll receive connectivity changes for each user here, which we'll process independently.
+         * We'll receive connectivity changes for each user here, which we process independently.
          * We are only interested in the active network here. We're only interested in the active
          * network, b/c the end result of this will be for apps to try to hit the network.
          * @param context The Context in which the receiver is running.
          * @param intent The Intent being received.
          */
+        // TODO: Test whether this will be called twice for each user.
         @Override
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
@@ -103,13 +108,13 @@
                 // This broadcast gets sent a lot, only update if the active network has changed.
                 if (activeNetwork.getType() == networkType) {
                     final int userid = context.getUserId();
-                    boolean isMetered = false;
-                    boolean isConnected =
+                    mMetered = false;
+                    mConnectivity =
                             !intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
-                    if (isConnected) {  // No point making the call if we know there's no conn.
-                        isMetered = connManager.isActiveNetworkMetered();
+                    if (mConnectivity) {  // No point making the call if we know there's no conn.
+                        mMetered = connManager.isActiveNetworkMetered();
                     }
-                    updateTrackedTasks(isConnected, isMetered, userid);
+                    updateTrackedTasks(userid);
                 }
             } else {
                 Log.w(TAG, "Unrecognised action in intent: " + action);
diff --git a/services/core/java/com/android/server/task/controllers/IdleController.java b/services/core/java/com/android/server/task/controllers/IdleController.java
new file mode 100644
index 0000000..a319a31
--- /dev/null
+++ b/services/core/java/com/android/server/task/controllers/IdleController.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.task.controllers;
+
+import java.util.ArrayList;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import com.android.server.task.TaskManagerService;
+
+public class IdleController extends StateController {
+    private static final String TAG = "IdleController";
+    private static final boolean DEBUG = false;
+
+    // Policy: we decide that we're "idle" if the device has been unused /
+    // screen off or dreaming for at least this long
+    private static final long INACTIVITY_IDLE_THRESHOLD = 71 * 60 * 1000; // millis; 71 min
+    private static final long IDLE_WINDOW_SLOP = 5 * 60 * 1000; // 5 minute window, to be nice
+
+    private static final String ACTION_TRIGGER_IDLE =
+            "com.android.server.task.controllers.IdleController.ACTION_TRIGGER_IDLE";
+
+    final ArrayList<TaskStatus> mTrackedTasks = new ArrayList<TaskStatus>();
+    IdlenessTracker mIdleTracker;
+
+    // Singleton factory
+    private static Object sCreationLock = new Object();
+    private static volatile IdleController sController;
+
+    public IdleController getController(TaskManagerService service) {
+        synchronized (sCreationLock) {
+            if (sController == null) {
+                sController = new IdleController(service);
+            }
+            return sController;
+        }
+    }
+
+    private IdleController(TaskManagerService service) {
+        super(service);
+        initIdleStateTracking();
+    }
+
+    /**
+     * StateController interface
+     */
+    @Override
+    public void maybeTrackTaskState(TaskStatus taskStatus) {
+        if (taskStatus.hasIdleConstraint()) {
+            synchronized (mTrackedTasks) {
+                mTrackedTasks.add(taskStatus);
+                taskStatus.idleConstraintSatisfied.set(mIdleTracker.isIdle());
+            }
+        }
+    }
+
+    @Override
+    public void removeTaskStateIfTracked(TaskStatus taskStatus) {
+        synchronized (mTrackedTasks) {
+            mTrackedTasks.remove(taskStatus);
+        }
+    }
+
+    /**
+     * Interaction with the task manager service
+     */
+    void reportNewIdleState(boolean isIdle) {
+        synchronized (mTrackedTasks) {
+            for (TaskStatus task : mTrackedTasks) {
+                task.idleConstraintSatisfied.set(isIdle);
+                mStateChangedListener.onTaskStateChanged(task);
+            }
+        }
+    }
+
+    /**
+     * Idle state tracking, and messaging with the task manager when
+     * significant state changes occur
+     */
+    private void initIdleStateTracking() {
+        mIdleTracker = new IdlenessTracker();
+        mIdleTracker.startTracking();
+    }
+
+    class IdlenessTracker extends BroadcastReceiver {
+        private AlarmManager mAlarm;
+        private PendingIntent mIdleTriggerIntent;
+        boolean mIdle;
+
+        public IdlenessTracker() {
+            mAlarm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+
+            Intent intent = new Intent(ACTION_TRIGGER_IDLE);
+            intent.setComponent(new ComponentName(mContext, this.getClass()));
+            mIdleTriggerIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+
+            // at boot we presume that the user has just "interacted" with the
+            // device in some meaningful way
+            mIdle = false;
+        }
+
+        public boolean isIdle() {
+            return mIdle;
+        }
+
+        public void startTracking() {
+            IntentFilter filter = new IntentFilter();
+
+            // Screen state
+            filter.addAction(Intent.ACTION_SCREEN_ON);
+            filter.addAction(Intent.ACTION_SCREEN_OFF);
+
+            // Dreaming state
+            filter.addAction(Intent.ACTION_DREAMING_STARTED);
+            filter.addAction(Intent.ACTION_DREAMING_STOPPED);
+
+            mContext.registerReceiver(this, filter);
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+
+            if (action.equals(Intent.ACTION_SCREEN_ON)
+                    || action.equals(Intent.ACTION_DREAMING_STOPPED)) {
+                // possible transition to not-idle
+                if (mIdle) {
+                    if (DEBUG) {
+                        Slog.v(TAG, "exiting idle : " + action);
+                    }
+                    mAlarm.cancel(mIdleTriggerIntent);
+                    mIdle = false;
+                    reportNewIdleState(mIdle);
+                }
+            } else if (action.equals(Intent.ACTION_SCREEN_OFF)
+                    || action.equals(Intent.ACTION_DREAMING_STARTED)) {
+                // when the screen goes off or dreaming starts, we schedule the
+                // alarm that will tell us when we have decided the device is
+                // truly idle.
+                long when = SystemClock.elapsedRealtime() + INACTIVITY_IDLE_THRESHOLD;
+                if (DEBUG) {
+                    Slog.v(TAG, "Scheduling idle : " + action + " when=" + when);
+                }
+                mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                        when, IDLE_WINDOW_SLOP, mIdleTriggerIntent);
+            } else if (action.equals(ACTION_TRIGGER_IDLE)) {
+                // idle time starts now
+                if (!mIdle) {
+                    if (DEBUG) {
+                        Slog.v(TAG, "Idle trigger fired @ " + SystemClock.elapsedRealtime());
+                    }
+                    mIdle = true;
+                    reportNewIdleState(mIdle);
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/task/controllers/TaskStatus.java b/services/core/java/com/android/server/task/controllers/TaskStatus.java
index 230b049..d96fedc 100644
--- a/services/core/java/com/android/server/task/controllers/TaskStatus.java
+++ b/services/core/java/com/android/server/task/controllers/TaskStatus.java
@@ -18,6 +18,8 @@
 
 import android.content.ComponentName;
 import android.content.Task;
+import android.content.pm.PackageParser;
+import android.os.Bundle;
 import android.os.SystemClock;
 
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -36,7 +38,9 @@
 public class TaskStatus {
     final int taskId;
     final int userId;
-    ComponentName component;
+    final int uId;
+    final ComponentName component;
+    final Bundle extras;
 
     final AtomicBoolean chargingConstraintSatisfied = new AtomicBoolean();
     final AtomicBoolean timeConstraintSatisfied = new AtomicBoolean();
@@ -60,15 +64,17 @@
 
     /** Generate a TaskStatus object for a given task and uid. */
     // TODO: reimplement this to reuse these objects instead of creating a new one each time?
-    static TaskStatus getForTaskAndUid(Task task, int uId) {
-        return new TaskStatus(task, uId);
+    public static TaskStatus getForTaskAndUser(Task task, int userId, int uId) {
+        return new TaskStatus(task, userId, uId);
     }
 
     /** Set up the state of a newly scheduled task. */
-    TaskStatus(Task task, int userId) {
+    TaskStatus(Task task, int userId, int uId) {
         this.taskId = task.getTaskId();
         this.userId = userId;
         this.component = task.getService();
+        this.extras = task.getExtras();
+        this.uId = uId;
 
         hasChargingConstraint = task.isRequireCharging();
         hasIdleConstraint = task.isRequireDeviceIdle();
@@ -94,6 +100,26 @@
         hasConnectivityConstraint = task.getNetworkCapabilities() == Task.NetworkType.ANY;
     }
 
+    public int getTaskId() {
+        return taskId;
+    }
+
+    public ComponentName getServiceComponent() {
+        return component;
+    }
+
+    public int getUserId() {
+        return userId;
+    }
+
+    public int getUid() {
+        return uId;
+    }
+
+    public Bundle getExtras() {
+        return extras;
+    }
+
     boolean hasConnectivityConstraint() {
         return hasConnectivityConstraint;
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index edbbc9f..e2cd4e2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1224,7 +1224,7 @@
             }
             for (Integer i : mDeviceOwner.getProfileOwnerKeys()) {
                 try {
-                    mAppOpsService.setProfileOwner(mDeviceOwner.getProfileOwnerName(i), i);
+                    mAppOpsService.setProfileOwner(mDeviceOwner.getProfileOwnerPackageName(i), i);
                 } catch (RemoteException e) {
                     Log.w(LOG_TAG, "Unable to notify AppOpsService of ProfileOwner", e);
                 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 7002744..2bf9ef1 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1773,12 +1773,15 @@
      *
      * Input parameters equivalent to TS 27.007 AT+CCHO command.
      *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#SIM_COMMUNICATION SIM_COMMUNICATION}
+     *
      * @param AID Application id. See ETSI 102.221 and 101.220.
      * @return The logical channel id which is negative on error.
      */
     public int iccOpenLogicalChannel(String AID) {
         try {
-          return getITelephony().iccOpenLogicalChannel(AID);
+            return getITelephony().iccOpenLogicalChannel(AID);
         } catch (RemoteException ex) {
         } catch (NullPointerException ex) {
         }
@@ -1790,13 +1793,16 @@
      *
      * Input parameters equivalent to TS 27.007 AT+CCHC command.
      *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#SIM_COMMUNICATION SIM_COMMUNICATION}
+     *
      * @param channel is the channel id to be closed as retruned by a successful
      *            iccOpenLogicalChannel.
      * @return true if the channel was closed successfully.
      */
     public boolean iccCloseLogicalChannel(int channel) {
         try {
-          return getITelephony().iccCloseLogicalChannel(channel);
+            return getITelephony().iccCloseLogicalChannel(channel);
         } catch (RemoteException ex) {
         } catch (NullPointerException ex) {
         }
@@ -1808,6 +1814,9 @@
      *
      * Input parameters equivalent to TS 27.007 AT+CGLA command.
      *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#SIM_COMMUNICATION SIM_COMMUNICATION}
+     *
      * @param channel is the channel id to be closed as returned by a successful
      *            iccOpenLogicalChannel.
      * @param cla Class of the APDU command.
@@ -1823,8 +1832,30 @@
     public String iccTransmitApduLogicalChannel(int channel, int cla,
             int instruction, int p1, int p2, int p3, String data) {
         try {
-          return getITelephony().iccTransmitApduLogicalChannel(channel, cla,
-                  instruction, p1, p2, p3, data);
+            return getITelephony().iccTransmitApduLogicalChannel(channel, cla,
+                    instruction, p1, p2, p3, data);
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+        return "";
+    }
+
+    /**
+     * Send ENVELOPE to the SIM and return the response.
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#SIM_COMMUNICATION SIM_COMMUNICATION}
+     *
+     * @param content String containing SAT/USAT response in hexadecimal
+     *                format starting with command tag. See TS 102 223 for
+     *                details.
+     * @return The APDU response from the ICC card, with the last 4 bytes
+     *         being the status word. If the command fails, returns an empty
+     *         string.
+     */
+    public String sendEnvelopeWithStatus(String content) {
+        try {
+            return getITelephony().sendEnvelopeWithStatus(content);
         } catch (RemoteException ex) {
         } catch (NullPointerException ex) {
         }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 72398ad..8b80bfa 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -373,6 +373,18 @@
             int p1, int p2, int p3, String data);
 
     /**
+     * Send ENVELOPE to the SIM and returns the response.
+     *
+     * @param contents  String containing SAT/USAT response in hexadecimal
+     *                  format starting with command tag. See TS 102 223 for
+     *                  details.
+     * @return The APDU response from the ICC card, with the last 4 bytes
+     *         being the status word. If the command fails, returns an empty
+     *         string.
+     */
+    String sendEnvelopeWithStatus(String content);
+
+    /**
      * Read one of the NV items defined in {@link RadioNVItems} / {@code ril_nv_items.h}.
      * Used for device configuration by some CDMA operators.
      *
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/CirclePropActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/CirclePropActivity.java
index e4ea9367..5b0aa66 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/CirclePropActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/CirclePropActivity.java
@@ -130,7 +130,8 @@
                 RenderNodeAnimator anim = mRunningAnimations.get(i);
                 anim.setInterpolator(interp);
                 anim.setDuration(1000);
-                anim.start(this);
+                anim.setTarget(this);
+                anim.start();
             }
 
             if (mToggle) {
@@ -146,7 +147,6 @@
                     }
                 });
             }
-
             return true;
         }
     }
diff --git a/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
index b5b12d8..c7715ad 100644
--- a/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
+++ b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
@@ -77,7 +77,8 @@
                     RenderNodeAnimator.TRANSLATION_Y, dy * delta);
             animator.setDuration(DURATION);
             if (child == clickedView) logTranslationY(clickedView);
-            animator.start(child);
+            animator.setTarget(child);
+            animator.start();
             if (child == clickedView) logTranslationY(clickedView);
         }
         //mHandler.postDelayed(mLaunchActivity, (long) (DURATION * .4));