Merge "API Review: move the CreateRevealAnimator to ViewAnimationUtils" into lmp-preview-dev
diff --git a/api/current.txt b/api/current.txt
index d308691..bf6ea89 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5362,7 +5362,7 @@
   public class Task implements android.os.Parcelable {
     method public int describeContents();
     method public int getBackoffPolicy();
-    method public android.os.Bundle getExtras();
+    method public android.os.PersistableBundle getExtras();
     method public int getId();
     method public long getInitialBackoffMillis();
     method public long getIntervalMillis();
@@ -5386,7 +5386,7 @@
     ctor public Task.Builder(int, android.content.ComponentName);
     method public android.app.task.Task build();
     method public android.app.task.Task.Builder setBackoffCriteria(long, int);
-    method public android.app.task.Task.Builder setExtras(android.os.Bundle);
+    method public android.app.task.Task.Builder setExtras(android.os.PersistableBundle);
     method public android.app.task.Task.Builder setMinimumLatency(long);
     method public android.app.task.Task.Builder setOverrideDeadline(long);
     method public android.app.task.Task.Builder setPeriodic(long);
@@ -5413,7 +5413,7 @@
 
   public class TaskParams implements android.os.Parcelable {
     method public int describeContents();
-    method public android.os.Bundle getExtras();
+    method public android.os.PersistableBundle getExtras();
     method public int getTaskId();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator CREATOR;
@@ -15824,6 +15824,8 @@
 package android.media.tv {
 
   public final class TvContract {
+    method public static final android.net.Uri buildChannelLogoUri(long);
+    method public static final android.net.Uri buildChannelLogoUri(android.net.Uri);
     method public static final android.net.Uri buildChannelUri(long);
     method public static final android.net.Uri buildChannelsUriForInput(android.content.ComponentName);
     method public static final android.net.Uri buildChannelsUriForInput(android.content.ComponentName, boolean);
@@ -15881,6 +15883,10 @@
     field public static final int TYPE_T_DMB = 393216; // 0x60000
   }
 
+  public static final class TvContract.Channels.Logo {
+    field public static final java.lang.String CONTENT_DIRECTORY = "logo";
+  }
+
   public static final class TvContract.Programs implements android.media.tv.TvContract.BaseTvColumns {
     field public static final java.lang.String COLUMN_AUDIO_LANGUAGE = "audio_language";
     field public static final java.lang.String COLUMN_BROADCAST_GENRE = "broadcast_genre";
@@ -15889,8 +15895,10 @@
     field public static final java.lang.String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data";
     field public static final java.lang.String COLUMN_LONG_DESCRIPTION = "long_description";
+    field public static final java.lang.String COLUMN_POSTER_ART_URI = "poster_art_uri";
     field public static final java.lang.String COLUMN_SHORT_DESCRIPTION = "short_description";
     field public static final java.lang.String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
+    field public static final java.lang.String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
     field public static final java.lang.String COLUMN_TITLE = "title";
     field public static final java.lang.String COLUMN_VERSION_NUMBER = "version_number";
     field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/program";
@@ -22862,16 +22870,6 @@
     field public static final deprecated android.net.Uri DELETED_CONTENT_URI;
     field public static final deprecated java.lang.String GROUP_ANDROID_STARRED = "Starred in Android";
     field public static final deprecated java.lang.String GROUP_MY_CONTACTS = "Contacts";
-    field public static final java.lang.String NON_SYNCABLE_ACCOUNT = "non_syncable";
-    field public static final java.lang.String NON_SYNCABLE_ACCOUNT_TYPE = "android.local";
-    field public static final java.lang.String _SYNC_ACCOUNT = "_sync_account";
-    field public static final java.lang.String _SYNC_ACCOUNT_TYPE = "_sync_account_type";
-    field public static final java.lang.String _SYNC_DIRTY = "_sync_dirty";
-    field public static final java.lang.String _SYNC_ID = "_sync_id";
-    field public static final java.lang.String _SYNC_LOCAL_ID = "_sync_local_id";
-    field public static final java.lang.String _SYNC_MARK = "_sync_mark";
-    field public static final java.lang.String _SYNC_TIME = "_sync_time";
-    field public static final java.lang.String _SYNC_VERSION = "_sync_version";
   }
 
   public static abstract deprecated interface Contacts.GroupsColumns {
@@ -22973,19 +22971,9 @@
     field public static final deprecated android.net.Uri CONTENT_URI;
     field public static final deprecated java.lang.String DEFAULT_SORT_ORDER = "name ASC";
     field public static final deprecated android.net.Uri DELETED_CONTENT_URI;
-    field public static final java.lang.String NON_SYNCABLE_ACCOUNT = "non_syncable";
-    field public static final java.lang.String NON_SYNCABLE_ACCOUNT_TYPE = "android.local";
     field public static final deprecated java.lang.String PRIMARY_EMAIL_ID = "primary_email";
     field public static final deprecated java.lang.String PRIMARY_ORGANIZATION_ID = "primary_organization";
     field public static final deprecated java.lang.String PRIMARY_PHONE_ID = "primary_phone";
-    field public static final java.lang.String _SYNC_ACCOUNT = "_sync_account";
-    field public static final java.lang.String _SYNC_ACCOUNT_TYPE = "_sync_account_type";
-    field public static final java.lang.String _SYNC_DIRTY = "_sync_dirty";
-    field public static final java.lang.String _SYNC_ID = "_sync_id";
-    field public static final java.lang.String _SYNC_LOCAL_ID = "_sync_local_id";
-    field public static final java.lang.String _SYNC_MARK = "_sync_mark";
-    field public static final java.lang.String _SYNC_TIME = "_sync_time";
-    field public static final java.lang.String _SYNC_VERSION = "_sync_version";
   }
 
   public static final deprecated class Contacts.People.ContactMethods implements android.provider.BaseColumns android.provider.Contacts.ContactMethodsColumns android.provider.Contacts.PeopleColumns {
@@ -23048,16 +23036,6 @@
     field public static final deprecated java.lang.String CONTENT_DIRECTORY = "photo";
     field public static final deprecated android.net.Uri CONTENT_URI;
     field public static final deprecated java.lang.String DEFAULT_SORT_ORDER = "person ASC";
-    field public static final java.lang.String NON_SYNCABLE_ACCOUNT = "non_syncable";
-    field public static final java.lang.String NON_SYNCABLE_ACCOUNT_TYPE = "android.local";
-    field public static final java.lang.String _SYNC_ACCOUNT = "_sync_account";
-    field public static final java.lang.String _SYNC_ACCOUNT_TYPE = "_sync_account_type";
-    field public static final java.lang.String _SYNC_DIRTY = "_sync_dirty";
-    field public static final java.lang.String _SYNC_ID = "_sync_id";
-    field public static final java.lang.String _SYNC_LOCAL_ID = "_sync_local_id";
-    field public static final java.lang.String _SYNC_MARK = "_sync_mark";
-    field public static final java.lang.String _SYNC_TIME = "_sync_time";
-    field public static final java.lang.String _SYNC_VERSION = "_sync_version";
   }
 
   public static abstract deprecated interface Contacts.PhotosColumns {
@@ -23378,7 +23356,6 @@
   protected static abstract interface ContactsContract.ContactOptionsColumns {
     field public static final java.lang.String CUSTOM_RINGTONE = "custom_ringtone";
     field public static final java.lang.String LAST_TIME_CONTACTED = "last_time_contacted";
-    field public static final java.lang.String PINNED = "pinned";
     field public static final java.lang.String SEND_TO_VOICEMAIL = "send_to_voicemail";
     field public static final java.lang.String STARRED = "starred";
     field public static final java.lang.String TIMES_CONTACTED = "times_contacted";
@@ -23656,15 +23633,6 @@
     field public static final int UNDEFINED = 0; // 0x0
   }
 
-  public static final class ContactsContract.PinnedPositions {
-    ctor public ContactsContract.PinnedPositions();
-    field public static final int DEMOTED = -1; // 0xffffffff
-    field public static final java.lang.String STAR_WHEN_PINNING = "star_when_pinning";
-    field public static final java.lang.String UNDEMOTE = "undemote";
-    field public static final int UNPINNED = 2147483647; // 0x7fffffff
-    field public static final android.net.Uri UPDATE_URI;
-  }
-
   public static final class ContactsContract.Preferences {
     ctor public ContactsContract.Preferences();
     field public static final java.lang.String DISPLAY_ORDER = "android.contacts.DISPLAY_ORDER";
@@ -27434,50 +27402,6 @@
     field public static final android.os.Parcelable.Creator CREATOR;
   }
 
-  public class DisconnectCause {
-    method public static java.lang.String toString(int);
-    field public static final int BUSY = 4; // 0x4
-    field public static final int CALL_BARRED = 20; // 0x14
-    field public static final int CDMA_ACCESS_BLOCKED = 35; // 0x23
-    field public static final int CDMA_ACCESS_FAILURE = 32; // 0x20
-    field public static final int CDMA_DROP = 27; // 0x1b
-    field public static final int CDMA_INTERCEPT = 28; // 0x1c
-    field public static final int CDMA_LOCKED_UNTIL_POWER_CYCLE = 26; // 0x1a
-    field public static final int CDMA_NOT_EMERGENCY = 34; // 0x22
-    field public static final int CDMA_PREEMPTED = 33; // 0x21
-    field public static final int CDMA_REORDER = 29; // 0x1d
-    field public static final int CDMA_RETRY_ORDER = 31; // 0x1f
-    field public static final int CDMA_SO_REJECT = 30; // 0x1e
-    field public static final int CONGESTION = 5; // 0x5
-    field public static final int CS_RESTRICTED = 22; // 0x16
-    field public static final int CS_RESTRICTED_EMERGENCY = 24; // 0x18
-    field public static final int CS_RESTRICTED_NORMAL = 23; // 0x17
-    field public static final int ERROR_UNSPECIFIED = 36; // 0x24
-    field public static final int FDN_BLOCKED = 21; // 0x15
-    field public static final int ICC_ERROR = 19; // 0x13
-    field public static final int INCOMING_MISSED = 1; // 0x1
-    field public static final int INCOMING_REJECTED = 16; // 0x10
-    field public static final int INVALID_CREDENTIALS = 10; // 0xa
-    field public static final int INVALID_NUMBER = 7; // 0x7
-    field public static final int LIMIT_EXCEEDED = 15; // 0xf
-    field public static final int LOCAL = 3; // 0x3
-    field public static final int LOST_SIGNAL = 14; // 0xe
-    field public static final int MAXIMUM_VALID_VALUE = 36; // 0x24
-    field public static final int MINIMUM_VALID_VALUE = 0; // 0x0
-    field public static final int MMI = 6; // 0x6
-    field public static final int NORMAL = 2; // 0x2
-    field public static final int NOT_DISCONNECTED = 0; // 0x0
-    field public static final int NOT_VALID = -1; // 0xffffffff
-    field public static final int NUMBER_UNREACHABLE = 8; // 0x8
-    field public static final int OUT_OF_NETWORK = 11; // 0xb
-    field public static final int OUT_OF_SERVICE = 18; // 0x12
-    field public static final int POWER_OFF = 17; // 0x11
-    field public static final int SERVER_ERROR = 12; // 0xc
-    field public static final int SERVER_UNREACHABLE = 9; // 0x9
-    field public static final int TIMED_OUT = 13; // 0xd
-    field public static final int UNOBTAINABLE_NUMBER = 25; // 0x19
-  }
-
   public class NeighboringCellInfo implements android.os.Parcelable {
     ctor public deprecated NeighboringCellInfo();
     ctor public deprecated NeighboringCellInfo(int, int);
@@ -27529,7 +27453,7 @@
     method public static boolean isEmergencyNumber(java.lang.String);
     method public static boolean isGlobalPhoneNumber(java.lang.String);
     method public static boolean isISODigit(char);
-    method public static boolean isLocalEmergencyNumber(java.lang.String, android.content.Context);
+    method public static boolean isLocalEmergencyNumber(android.content.Context, java.lang.String);
     method public static final boolean isNonSeparator(char);
     method public static final boolean isReallyDialable(char);
     method public static final boolean isStartsPostDial(char);
@@ -33835,11 +33759,17 @@
 
   public static final class CaptioningManager.CaptionStyle {
     method public android.graphics.Typeface getTypeface();
+    method public boolean hasBackgroundColor();
+    method public boolean hasEdgeColor();
+    method public boolean hasEdgeType();
+    method public boolean hasForegroundColor();
+    method public boolean hasWindowColor();
     field public static final int EDGE_TYPE_DEPRESSED = 4; // 0x4
     field public static final int EDGE_TYPE_DROP_SHADOW = 2; // 0x2
     field public static final int EDGE_TYPE_NONE = 0; // 0x0
     field public static final int EDGE_TYPE_OUTLINE = 1; // 0x1
     field public static final int EDGE_TYPE_RAISED = 3; // 0x3
+    field public static final int EDGE_TYPE_UNSPECIFIED = -1; // 0xffffffff
     field public final int backgroundColor;
     field public final int edgeColor;
     field public final int edgeType;
diff --git a/core/java/android/app/task/Task.java b/core/java/android/app/task/Task.java
index ca4aeb2..87d57fb4 100644
--- a/core/java/android/app/task/Task.java
+++ b/core/java/android/app/task/Task.java
@@ -20,6 +20,7 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.PersistableBundle;
 
 /**
  * Container of data passed to the {@link android.app.task.TaskManager} fully encapsulating the
@@ -37,6 +38,18 @@
     }
 
     /**
+     * Amount of backoff a task has initially by default, in milliseconds.
+     * @hide.
+     */
+    public static final long DEFAULT_INITIAL_BACKOFF_MILLIS = 5000L;
+
+    /**
+     * Default type of backoff.
+     * @hide
+     */
+    public static final int DEFAULT_BACKOFF_POLICY = BackoffPolicy.EXPONENTIAL;
+
+    /**
      * Linear: retry_time(failure_time, t) = failure_time + initial_retry_delay * t, t >= 1
      * Expon: retry_time(failure_time, t) = failure_time + initial_retry_delay ^ t, t >= 1
      */
@@ -47,7 +60,7 @@
 
     private final int taskId;
     // TODO: Change this to use PersistableBundle when that lands in master.
-    private final Bundle extras;
+    private final PersistableBundle extras;
     private final ComponentName service;
     private final boolean requireCharging;
     private final boolean requireDeviceIdle;
@@ -71,7 +84,7 @@
     /**
      * Bundle of extras which are returned to your application at execution time.
      */
-    public Bundle getExtras() {
+    public PersistableBundle getExtras() {
         return extras;
     }
 
@@ -171,7 +184,7 @@
 
     private Task(Parcel in) {
         taskId = in.readInt();
-        extras = in.readBundle();
+        extras = in.readPersistableBundle();
         service = ComponentName.readFromParcel(in);
         requireCharging = in.readInt() == 1;
         requireDeviceIdle = in.readInt() == 1;
@@ -188,7 +201,7 @@
 
     private Task(Task.Builder b) {
         taskId = b.mTaskId;
-        extras = new Bundle(b.mExtras);
+        extras = new PersistableBundle(b.mExtras);
         service = b.mTaskService;
         requireCharging = b.mRequiresCharging;
         requireDeviceIdle = b.mRequiresDeviceIdle;
@@ -211,7 +224,7 @@
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(taskId);
-        out.writeBundle(extras);
+        out.writePersistableBundle(extras);
         ComponentName.writeToParcel(service, out);
         out.writeInt(requireCharging ? 1 : 0);
         out.writeInt(requireDeviceIdle ? 1 : 0);
@@ -238,12 +251,10 @@
         }
     };
 
-    /**
-     * Builder class for constructing {@link Task} objects.
-     */
+    /** Builder class for constructing {@link Task} objects. */
     public static final class Builder {
         private int mTaskId;
-        private Bundle mExtras;
+        private PersistableBundle mExtras = PersistableBundle.EMPTY;
         private ComponentName mTaskService;
         // Requirements.
         private boolean mRequiresCharging;
@@ -258,8 +269,8 @@
         private boolean mHasLateConstraint;
         private long mIntervalMillis;
         // Back-off parameters.
-        private long mInitialBackoffMillis = 5000L;
-        private int mBackoffPolicy = BackoffPolicy.EXPONENTIAL;
+        private long mInitialBackoffMillis = DEFAULT_INITIAL_BACKOFF_MILLIS;
+        private int mBackoffPolicy = DEFAULT_BACKOFF_POLICY;
         /** Easy way to track whether the client has tried to set a back-off policy. */
         private boolean mBackoffPolicySet = false;
 
@@ -279,7 +290,7 @@
          * Set optional extras. This is persisted, so we only allow primitive types.
          * @param extras Bundle containing extras you want the scheduler to hold on to for you.
          */
-        public Builder setExtras(Bundle extras) {
+        public Builder setExtras(PersistableBundle extras) {
             mExtras = extras;
             return this;
         }
@@ -394,18 +405,13 @@
          * @return The task object to hand to the TaskManager. This object is immutable.
          */
         public Task build() {
-            if (mExtras == null) {
-                mExtras = Bundle.EMPTY;
-            }
-            if (mTaskId < 0) {
-                throw new IllegalArgumentException("Task id must be greater than 0.");
-            }
+            mExtras = new PersistableBundle(mExtras);  // Make our own copy.
             // Check that a deadline was not set on a periodic task.
-            if (mIsPeriodic && mHasLateConstraint) {
+            if (mIsPeriodic && (mMaxExecutionDelayMillis != 0L)) {
                 throw new IllegalArgumentException("Can't call setOverrideDeadline() on a " +
                         "periodic task.");
             }
-            if (mIsPeriodic && mHasEarlyConstraint) {
+            if (mIsPeriodic && (mMinLatencyMillis != 0L)) {
                 throw new IllegalArgumentException("Can't call setMinimumLatency() on a " +
                         "periodic task");
             }
diff --git a/core/java/android/app/task/TaskParams.java b/core/java/android/app/task/TaskParams.java
index dacb3480..f4908c6 100644
--- a/core/java/android/app/task/TaskParams.java
+++ b/core/java/android/app/task/TaskParams.java
@@ -16,10 +16,10 @@
 
 package android.app.task;
 
-import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.PersistableBundle;
 
 /**
  * Contains the parameters used to configure/identify your task. You do not create this object
@@ -28,11 +28,11 @@
 public class TaskParams implements Parcelable {
 
     private final int taskId;
-    private final Bundle extras;
+    private final PersistableBundle extras;
     private final IBinder callback;
 
     /** @hide */
-    public TaskParams(int taskId, Bundle extras, IBinder callback) {
+    public TaskParams(int taskId, PersistableBundle extras, IBinder callback) {
         this.taskId = taskId;
         this.extras = extras;
         this.callback = callback;
@@ -47,10 +47,10 @@
 
     /**
      * @return The extras you passed in when constructing this task with
-     * {@link android.app.task.Task.Builder#setExtras(android.os.Bundle)}. This will
+     * {@link android.app.task.Task.Builder#setExtras(android.os.PersistableBundle)}. This will
      * never be null. If you did not set any extras this will be an empty bundle.
      */
-    public Bundle getExtras() {
+    public PersistableBundle getExtras() {
         return extras;
     }
 
@@ -61,7 +61,7 @@
 
     private TaskParams(Parcel in) {
         taskId = in.readInt();
-        extras = in.readBundle();
+        extras = in.readPersistableBundle();
         callback = in.readStrongBinder();
     }
 
@@ -73,7 +73,7 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(taskId);
-        dest.writeBundle(extras);
+        dest.writePersistableBundle(extras);
         dest.writeStrongBinder(callback);
     }
 
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index e21fb1f..9046b13 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -226,7 +226,7 @@
 
             synchronized (mLock) {
 
-                ICameraDeviceUser cameraUser;
+                ICameraDeviceUser cameraUser = null;
 
                 android.hardware.camera2.impl.CameraDeviceImpl deviceImpl =
                         new android.hardware.camera2.impl.CameraDeviceImpl(
@@ -248,8 +248,23 @@
                         // Use legacy camera implementation for HAL1 devices
                         Log.i(TAG, "Using legacy camera HAL.");
                         cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id);
+                    } else if (e.getReason() == CameraAccessException.CAMERA_IN_USE ||
+                            e.getReason() == CameraAccessException.MAX_CAMERAS_IN_USE ||
+                            e.getReason() == CameraAccessException.CAMERA_DISABLED ||
+                            e.getReason() == CameraAccessException.CAMERA_DISCONNECTED ||
+                            e.getReason() == CameraAccessException.CAMERA_ERROR) {
+                        // Received one of the known connection errors
+                        // The remote camera device cannot be connected to, so
+                        // set the local camera to the startup error state
+                        deviceImpl.setRemoteFailure(e);
+
+                        if (e.getReason() == CameraAccessException.CAMERA_DISABLED ||
+                                e.getReason() == CameraAccessException.CAMERA_DISCONNECTED) {
+                            // Per API docs, these failures call onError and throw
+                            throw e;
+                        }
                     } else {
-                        // Rethrow otherwise
+                        // Unexpected failure - rethrow
                         throw e;
                     }
                 }
@@ -299,7 +314,7 @@
      *
      * <p>If opening the camera device fails, then the device listener's
      * {@link CameraDevice.StateListener#onError onError} method will be called, and subsequent
-     * calls on the camera device will throw an {@link IllegalStateException}.</p>
+     * calls on the camera device will throw a {@link CameraAccessException}.</p>
      *
      * @param cameraId
      *             The unique identifier of the camera device to open
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 81bd2fd..9795082 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -64,6 +64,7 @@
     private volatile StateListener mSessionStateListener;
     private final Handler mDeviceHandler;
 
+    private boolean mInError = false;
     private boolean mIdle = true;
 
     /** map request IDs to listener/request data */
@@ -211,6 +212,9 @@
     public void setRemoteDevice(ICameraDeviceUser remoteDevice) {
         // TODO: Move from decorator to direct binder-mediated exceptions
         synchronized(mLock) {
+            // If setRemoteFailure already called, do nothing
+            if (mInError) return;
+
             mRemoteDevice = CameraBinderDecorator.newInstance(remoteDevice);
 
             mDeviceHandler.post(mCallOnOpened);
@@ -218,6 +222,52 @@
         }
     }
 
+    /**
+     * Call to indicate failed connection to a remote camera device.
+     *
+     * <p>This places the camera device in the error state and informs the listener.
+     * Use in place of setRemoteDevice() when startup fails.</p>
+     */
+    public void setRemoteFailure(final CameraRuntimeException failure) {
+        int failureCode = StateListener.ERROR_CAMERA_DEVICE;
+        boolean failureIsError = true;
+
+        switch (failure.getReason()) {
+            case CameraAccessException.CAMERA_IN_USE:
+                failureCode = StateListener.ERROR_CAMERA_IN_USE;
+                break;
+            case CameraAccessException.MAX_CAMERAS_IN_USE:
+                failureCode = StateListener.ERROR_MAX_CAMERAS_IN_USE;
+                break;
+            case CameraAccessException.CAMERA_DISABLED:
+                failureCode = StateListener.ERROR_CAMERA_DISABLED;
+                break;
+            case CameraAccessException.CAMERA_DISCONNECTED:
+                failureIsError = false;
+                break;
+            case CameraAccessException.CAMERA_ERROR:
+                failureCode = StateListener.ERROR_CAMERA_DEVICE;
+                break;
+            default:
+                Log.wtf(TAG, "Unknown failure in opening camera device: " + failure.getReason());
+                break;
+        }
+        final int code = failureCode;
+        final boolean isError = failureIsError;
+        synchronized (mLock) {
+            mInError = true;
+            mDeviceHandler.post(new Runnable() {
+                public void run() {
+                    if (isError) {
+                        mDeviceListener.onError(CameraDeviceImpl.this, code);
+                    } else {
+                        mDeviceListener.onDisconnected(CameraDeviceImpl.this);
+                    }
+                }
+            });
+        }
+    }
+
     @Override
     public String getId() {
         return mCameraId;
@@ -230,7 +280,7 @@
             outputs = new ArrayList<Surface>();
         }
         synchronized (mLock) {
-            checkIfCameraClosed();
+            checkIfCameraClosedOrInError();
 
             HashSet<Surface> addSet = new HashSet<Surface>(outputs);    // Streams to create
             List<Integer> deleteList = new ArrayList<Integer>();        // Streams to delete
@@ -298,7 +348,7 @@
                 Log.d(TAG, "createCaptureSession");
             }
 
-            checkIfCameraClosed();
+            checkIfCameraClosedOrInError();
 
             // TODO: we must be in UNCONFIGURED mode to begin with, or using another session
 
@@ -336,7 +386,7 @@
     public CaptureRequest.Builder createCaptureRequest(int templateType)
             throws CameraAccessException {
         synchronized (mLock) {
-            checkIfCameraClosed();
+            checkIfCameraClosedOrInError();
 
             CameraMetadataNative templatedRequest = new CameraMetadataNative();
 
@@ -456,7 +506,7 @@
         }
 
         synchronized (mLock) {
-            checkIfCameraClosed();
+            checkIfCameraClosedOrInError();
             int requestId;
 
             if (repeating) {
@@ -528,7 +578,7 @@
     public void stopRepeating() throws CameraAccessException {
 
         synchronized (mLock) {
-            checkIfCameraClosed();
+            checkIfCameraClosedOrInError();
             if (mRepeatingRequestId != REQUEST_ID_NONE) {
 
                 int requestId = mRepeatingRequestId;
@@ -559,7 +609,7 @@
     private void waitUntilIdle() throws CameraAccessException {
 
         synchronized (mLock) {
-            checkIfCameraClosed();
+            checkIfCameraClosedOrInError();
             if (mRepeatingRequestId != REQUEST_ID_NONE) {
                 throw new IllegalStateException("Active repeating request ongoing");
             }
@@ -580,7 +630,7 @@
     @Override
     public void flush() throws CameraAccessException {
         synchronized (mLock) {
-            checkIfCameraClosed();
+            checkIfCameraClosedOrInError();
 
             mDeviceHandler.post(mCallOnBusy);
             try {
@@ -614,11 +664,15 @@
                 // impossible
             }
 
-            if (mRemoteDevice != null) {
+            // Only want to fire the onClosed callback once;
+            // either a normal close where the remote device is valid
+            // or a close after a startup error (no remote device but in error state)
+            if (mRemoteDevice != null || mInError) {
                 mDeviceHandler.post(mCallOnClosed);
             }
 
             mRemoteDevice = null;
+            mInError = false;
         }
     }
 
@@ -835,6 +889,7 @@
             if (isClosed()) return;
 
             synchronized(mLock) {
+                mInError = true;
                 switch (errorCode) {
                     case ERROR_CAMERA_DISCONNECTED:
                         r = mCallOnDisconnected;
@@ -1032,7 +1087,11 @@
         return handler;
     }
 
-    private void checkIfCameraClosed() {
+    private void checkIfCameraClosedOrInError() throws CameraAccessException {
+        if (mInError) {
+            throw new CameraAccessException(CameraAccessException.CAMERA_ERROR,
+                    "The camera device has encountered a serious error");
+        }
         if (mRemoteDevice == null) {
             throw new IllegalStateException("CameraDevice was already closed");
         }
diff --git a/core/java/android/provider/Contacts.java b/core/java/android/provider/Contacts.java
index 9e2aacd..d4c5cfb 100644
--- a/core/java/android/provider/Contacts.java
+++ b/core/java/android/provider/Contacts.java
@@ -58,7 +58,7 @@
     @Deprecated
     public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
 
-    /** 
+    /**
      * Signifies an email address row that is stored in the ContactMethods table
      * @deprecated see {@link android.provider.ContactsContract}
      */
@@ -337,7 +337,7 @@
      * @deprecated see {@link android.provider.ContactsContract}
      */
     @Deprecated
-    public static final class People implements BaseColumns, SyncConstValue, PeopleColumns,
+    public static final class People implements BaseColumns, PeopleColumns,
             PhonesColumns, PresenceColumns {
         /**
          * no public constructor since this is a utility class
@@ -790,7 +790,7 @@
      */
     @Deprecated
     public static final class Groups
-            implements BaseColumns, SyncConstValue, GroupsColumns {
+            implements BaseColumns, GroupsColumns {
         /**
          * no public constructor since this is a utility class
          */
@@ -1864,7 +1864,7 @@
      * @deprecated see {@link android.provider.ContactsContract}
      */
     @Deprecated
-    public static final class Photos implements BaseColumns, PhotosColumns, SyncConstValue {
+    public static final class Photos implements BaseColumns, PhotosColumns {
         /**
          * no public constructor since this is a utility class
          */
@@ -2199,7 +2199,7 @@
             }
 
             /** The action code to use when adding a contact
-             * @deprecated see {@link android.provider.ContactsContract} 
+             * @deprecated see {@link android.provider.ContactsContract}
              */
             @Deprecated
             public static final String ACTION = ContactsContract.Intents.Insert.ACTION;
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index b53ea81..8c7e879 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -184,9 +184,9 @@
     public static final String DEFERRED_SNIPPETING_QUERY = "deferred_snippeting_query";
 
     /**
-     * A boolean parameter for {@link CommonDataKinds.Phone#CONTENT_URI},
-     * {@link CommonDataKinds.Email#CONTENT_URI}, and
-     * {@link CommonDataKinds.StructuredPostal#CONTENT_URI}.
+     * A boolean parameter for {@link CommonDataKinds.Phone#CONTENT_URI Phone.CONTENT_URI},
+     * {@link CommonDataKinds.Email#CONTENT_URI Email.CONTENT_URI}, and
+     * {@link CommonDataKinds.StructuredPostal#CONTENT_URI StructuredPostal.CONTENT_URI}.
      * This enables a content provider to remove duplicate entries in results.
      */
     public static final String REMOVE_DUPLICATE_ENTRIES = "remove_duplicate_entries";
@@ -244,6 +244,9 @@
         public static final String KEY_AUTHORIZED_URI = "authorized_uri";
     }
 
+    /*
+     * @hide
+     */
     public static final class Preferences {
 
         /**
@@ -808,6 +811,7 @@
          * The position at which the contact is pinned. If {@link PinnedPositions#UNPINNED},
          * the contact is not pinned. Also see {@link PinnedPositions}.
          * <P>Type: INTEGER </P>
+         * @hide
          */
         public static final String PINNED = "pinned";
 
@@ -7764,6 +7768,8 @@
      * {@link PinnedPositions#STAR_WHEN_PINNING} to true to force all pinned and unpinned
      * contacts to be automatically starred and unstarred.
      * </p>
+     *
+     * @hide
      */
     public static final class PinnedPositions {
 
diff --git a/core/java/android/view/accessibility/CaptioningManager.java b/core/java/android/view/accessibility/CaptioningManager.java
index a0134d6..334ff43 100644
--- a/core/java/android/view/accessibility/CaptioningManager.java
+++ b/core/java/android/view/accessibility/CaptioningManager.java
@@ -16,6 +16,8 @@
 
 package android.view.accessibility;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
@@ -78,6 +80,7 @@
      *         language
      * @hide
      */
+    @Nullable
     public final String getRawLocale() {
         return Secure.getString(mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_LOCALE);
     }
@@ -86,6 +89,7 @@
      * @return the locale for the user's preferred captioning language, or null
      *         if not specified
      */
+    @Nullable
     public final Locale getLocale() {
         final String rawLocale = getRawLocale();
         if (!TextUtils.isEmpty(rawLocale)) {
@@ -125,6 +129,7 @@
      * @return the user's preferred visual properties for captions as a
      *         {@link CaptionStyle}, or the default style if not specified
      */
+    @NonNull
     public CaptionStyle getUserStyle() {
         final int preset = getRawUserStyle();
         if (preset == CaptionStyle.PRESET_CUSTOM) {
@@ -140,17 +145,19 @@
      *
      * @param listener the listener to add
      */
-    public void addCaptioningChangeListener(CaptioningChangeListener listener) {
+    public void addCaptioningChangeListener(@NonNull CaptioningChangeListener listener) {
         synchronized (mListeners) {
             if (mListeners.isEmpty()) {
                 registerObserver(Secure.ACCESSIBILITY_CAPTIONING_ENABLED);
                 registerObserver(Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR);
                 registerObserver(Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR);
+                registerObserver(Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR);
                 registerObserver(Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE);
                 registerObserver(Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR);
                 registerObserver(Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE);
                 registerObserver(Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE);
                 registerObserver(Secure.ACCESSIBILITY_CAPTIONING_LOCALE);
+                registerObserver(Secure.ACCESSIBILITY_CAPTIONING_PRESET);
             }
 
             mListeners.add(listener);
@@ -167,7 +174,7 @@
      *
      * @param listener the listener to remove
      */
-    public void removeCaptioningChangeListener(CaptioningChangeListener listener) {
+    public void removeCaptioningChangeListener(@NonNull CaptioningChangeListener listener) {
         synchronized (mListeners) {
             mListeners.remove(listener);
 
@@ -253,11 +260,18 @@
         /** Packed value for a color of 'none' and a cached opacity of 100%. */
         private static final int COLOR_NONE_OPAQUE = 0x000000FF;
 
+        /** Packed value for an unspecified color and opacity. */
+        private static final int COLOR_UNSPECIFIED = 0x000001FF;
+
         private static final CaptionStyle WHITE_ON_BLACK;
         private static final CaptionStyle BLACK_ON_WHITE;
         private static final CaptionStyle YELLOW_ON_BLACK;
         private static final CaptionStyle YELLOW_ON_BLUE;
         private static final CaptionStyle DEFAULT_CUSTOM;
+        private static final CaptionStyle UNSPECIFIED;
+
+        /** The default caption style used to fill in unspecified values. @hide */
+        public static final CaptionStyle DEFAULT;
 
         /** @hide */
         public static final CaptionStyle[] PRESETS;
@@ -265,6 +279,9 @@
         /** @hide */
         public static final int PRESET_CUSTOM = -1;
 
+        /** Unspecified edge type value. */
+        public static final int EDGE_TYPE_UNSPECIFIED = -1;
+
         /** Edge type value specifying no character edges. */
         public static final int EDGE_TYPE_NONE = 0;
 
@@ -289,6 +306,7 @@
         /**
          * The preferred edge type for video captions, one of:
          * <ul>
+         * <li>{@link #EDGE_TYPE_UNSPECIFIED}
          * <li>{@link #EDGE_TYPE_NONE}
          * <li>{@link #EDGE_TYPE_OUTLINE}
          * <li>{@link #EDGE_TYPE_DROP_SHADOW}
@@ -326,9 +344,81 @@
         }
 
         /**
+         * Applies a caption style, overriding any properties that are specified
+         * in the overlay caption.
+         *
+         * @param overlay The style to apply
+         * @return A caption style with the overlay style applied
+         * @hide
+         */
+        @NonNull
+        public CaptionStyle applyStyle(@NonNull CaptionStyle overlay) {
+            final int newForegroundColor = overlay.hasForegroundColor() ?
+                    overlay.foregroundColor : foregroundColor;
+            final int newBackgroundColor = overlay.hasBackgroundColor() ?
+                    overlay.backgroundColor : backgroundColor;
+            final int newEdgeType = overlay.hasEdgeType() ?
+                    overlay.edgeType : edgeType;
+            final int newEdgeColor = overlay.hasEdgeColor() ?
+                    overlay.edgeColor : edgeColor;
+            final int newWindowColor = overlay.hasWindowColor() ?
+                    overlay.windowColor : windowColor;
+            final String newRawTypeface = overlay.mRawTypeface != null ?
+                    overlay.mRawTypeface : mRawTypeface;
+            return new CaptionStyle(newForegroundColor, newBackgroundColor, newEdgeType,
+                    newEdgeColor, newWindowColor, newRawTypeface);
+        }
+
+        /**
+         * @return {@code true} if the user has specified a background color
+         *         that should override the application default, {@code false}
+         *         otherwise
+         */
+        public boolean hasBackgroundColor() {
+            return backgroundColor != COLOR_UNSPECIFIED;
+        }
+
+        /**
+         * @return {@code true} if the user has specified a foreground color
+         *         that should override the application default, {@code false}
+         *         otherwise
+         */
+        public boolean hasForegroundColor() {
+            return foregroundColor != COLOR_UNSPECIFIED;
+        }
+
+        /**
+         * @return {@code true} if the user has specified an edge type that
+         *         should override the application default, {@code false}
+         *         otherwise
+         */
+        public boolean hasEdgeType() {
+            return edgeType != EDGE_TYPE_UNSPECIFIED;
+        }
+
+        /**
+         * @return {@code true} if the user has specified an edge color that
+         *         should override the application default, {@code false}
+         *         otherwise
+         */
+        public boolean hasEdgeColor() {
+            return edgeColor != COLOR_UNSPECIFIED;
+        }
+
+        /**
+         * @return {@code true} if the user has specified a window color that
+         *         should override the application default, {@code false}
+         *         otherwise
+         */
+        public boolean hasWindowColor() {
+            return windowColor != COLOR_UNSPECIFIED;
+        }
+
+        /**
          * @return the preferred {@link Typeface} for video captions, or null if
          *         not specified
          */
+        @Nullable
         public Typeface getTypeface() {
             if (mParsedTypeface == null && !TextUtils.isEmpty(mRawTypeface)) {
                 mParsedTypeface = Typeface.create(mRawTypeface, Typeface.NORMAL);
@@ -339,6 +429,7 @@
         /**
          * @hide
          */
+        @NonNull
         public static CaptionStyle getCustomStyle(ContentResolver cr) {
             final CaptionStyle defStyle = CaptionStyle.DEFAULT_CUSTOM;
             final int foregroundColor = Secure.getInt(
@@ -370,12 +461,17 @@
                     Color.BLACK, COLOR_NONE_OPAQUE, null);
             YELLOW_ON_BLUE = new CaptionStyle(Color.YELLOW, Color.BLUE, EDGE_TYPE_NONE,
                     Color.BLACK, COLOR_NONE_OPAQUE, null);
+            UNSPECIFIED = new CaptionStyle(COLOR_UNSPECIFIED, COLOR_UNSPECIFIED,
+                    EDGE_TYPE_UNSPECIFIED, COLOR_UNSPECIFIED, COLOR_UNSPECIFIED, null);
 
+            // The ordering of these cannot change since we store the index
+            // directly in preferences.
             PRESETS = new CaptionStyle[] {
-                    WHITE_ON_BLACK, BLACK_ON_WHITE, YELLOW_ON_BLACK, YELLOW_ON_BLUE
+                    WHITE_ON_BLACK, BLACK_ON_WHITE, YELLOW_ON_BLACK, YELLOW_ON_BLUE, UNSPECIFIED
             };
 
             DEFAULT_CUSTOM = WHITE_ON_BLACK;
+            DEFAULT = WHITE_ON_BLACK;
         }
     }
 
@@ -389,8 +485,7 @@
          *
          * @param enabled the user's new preferred captioning enabled state
          */
-        public void onEnabledChanged(boolean enabled) {
-        }
+        public void onEnabledChanged(boolean enabled) {}
 
         /**
          * Called when the captioning user style changes.
@@ -398,17 +493,15 @@
          * @param userStyle the user's new preferred style
          * @see CaptioningManager#getUserStyle()
          */
-        public void onUserStyleChanged(CaptionStyle userStyle) {
-        }
+        public void onUserStyleChanged(@NonNull CaptionStyle userStyle) {}
 
         /**
          * Called when the captioning locale changes.
          *
-         * @param locale the preferred captioning locale
+         * @param locale the preferred captioning locale, or {@code null} if not specified
          * @see CaptioningManager#getLocale()
          */
-        public void onLocaleChanged(Locale locale) {
-        }
+        public void onLocaleChanged(@Nullable Locale locale) {}
 
         /**
          * Called when the captioning font scaling factor changes.
@@ -416,7 +509,6 @@
          * @param fontScale the preferred font scaling factor
          * @see CaptioningManager#getFontScale()
          */
-        public void onFontScaleChanged(float fontScale) {
-        }
+        public void onFontScaleChanged(float fontScale) {}
     }
 }
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index d841d53..7fe03f5 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -22,17 +22,18 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
-import android.graphics.Color;
+import android.graphics.ColorFilter;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
 import android.graphics.Rect;
 import android.os.Debug;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
 import android.util.AttributeSet;
-import android.util.TypedValue;
 import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
 import android.view.View;
@@ -110,14 +111,11 @@
     private float mSquareWidth;
     private float mSquareHeight;
 
-    private Bitmap mBitmapBtnDefault;
-    private Bitmap mBitmapBtnTouched;
-    private Bitmap mBitmapCircleDefault;
-    private Bitmap mBitmapCircleGreen;
-    private Bitmap mBitmapCircleRed;
-
-    private Bitmap mBitmapArrowGreenUp;
-    private Bitmap mBitmapArrowRedUp;
+    private final Bitmap mBitmapBtnDefault;
+    private final Bitmap mBitmapBtnTouched;
+    private final Bitmap mBitmapCircleDefault;
+    private final Bitmap mBitmapCircleAlpha;
+    private final Bitmap mBitmapArrowAlphaUp;
 
     private final Path mCurrentPath = new Path();
     private final Rect mInvalidate = new Rect();
@@ -129,6 +127,10 @@
     private int mAspect;
     private final Matrix mArrowMatrix = new Matrix();
     private final Matrix mCircleMatrix = new Matrix();
+    private final PorterDuffColorFilter mRegularColorFilter;
+    private final PorterDuffColorFilter mErrorColorFilter;
+    private final PorterDuffColorFilter mSuccessColorFilter;
+
 
     /**
      * Represents a cell in the 3 X 3 matrix of the unlock pattern view.
@@ -266,17 +268,22 @@
 
         setClickable(true);
 
+
         mPathPaint.setAntiAlias(true);
         mPathPaint.setDither(true);
 
-        int defaultColor = Color.WHITE;
-        TypedValue outValue = new TypedValue();
-        if (context.getTheme().resolveAttribute(android.R.attr.textColorPrimary, outValue, true)) {
-            defaultColor = context.getResources().getColor(outValue.resourceId);
-        }
+        int regularColor = getResources().getColor(R.color.lock_pattern_view_regular_color);
+        int errorColor = getResources().getColor(R.color.lock_pattern_view_error_color);
+        int successColor = getResources().getColor(R.color.lock_pattern_view_success_color);
+        regularColor = a.getColor(R.styleable.LockPatternView_regularColor, regularColor);
+        errorColor = a.getColor(R.styleable.LockPatternView_errorColor, errorColor);
+        successColor = a.getColor(R.styleable.LockPatternView_successColor, successColor);
+        mRegularColorFilter = new PorterDuffColorFilter(regularColor, PorterDuff.Mode.SRC_ATOP);
+        mErrorColorFilter = new PorterDuffColorFilter(errorColor, PorterDuff.Mode.SRC_ATOP);
+        mSuccessColorFilter = new PorterDuffColorFilter(successColor, PorterDuff.Mode.SRC_ATOP);
 
-        final int color = a.getColor(R.styleable.LockPatternView_pathColor, defaultColor);
-        mPathPaint.setColor(color);
+        int pathColor = a.getColor(R.styleable.LockPatternView_pathColor, regularColor);
+        mPathPaint.setColor(pathColor);
 
         mPathPaint.setAlpha(mStrokeAlpha);
         mPathPaint.setStyle(Paint.Style.STROKE);
@@ -284,25 +291,26 @@
         mPathPaint.setStrokeCap(Paint.Cap.ROUND);
 
         // lot's of bitmaps!
-        // TODO: those bitmaps are hardcoded to the Holo Theme which should not be the case!
-        mBitmapBtnDefault = getBitmapFor(R.drawable.btn_code_lock_default_holo);
-        mBitmapBtnTouched = getBitmapFor(R.drawable.btn_code_lock_touched_holo);
-        mBitmapCircleDefault = getBitmapFor(R.drawable.indicator_code_lock_point_area_default_holo);
-        mBitmapCircleGreen = getBitmapFor(R.drawable.indicator_code_lock_point_area_green_holo);
-        mBitmapCircleRed = getBitmapFor(R.drawable.indicator_code_lock_point_area_red_holo);
-
-        mBitmapArrowGreenUp = getBitmapFor(R.drawable.indicator_code_lock_drag_direction_green_up);
-        mBitmapArrowRedUp = getBitmapFor(R.drawable.indicator_code_lock_drag_direction_red_up);
+        // TODO: those bitmaps are hardcoded to the Quantum Theme which should not be the case!
+        mBitmapBtnDefault = getBitmapFor(R.drawable.btn_code_lock_default_qntm_alpha);
+        mBitmapBtnTouched = getBitmapFor(R.drawable.btn_code_lock_touched_qntm_alpha);
+        mBitmapCircleDefault = getBitmapFor(
+                R.drawable.indicator_code_lock_point_area_default_qntm_alpha);
+        mBitmapCircleAlpha = getBitmapFor(R.drawable.indicator_code_lock_point_area_qntm_alpha);
+        mBitmapArrowAlphaUp = getBitmapFor(
+                R.drawable.indicator_code_lock_drag_direction_up_qntm_alpha);
 
         // bitmaps have the size of the largest bitmap in this group
         final Bitmap bitmaps[] = { mBitmapBtnDefault, mBitmapBtnTouched, mBitmapCircleDefault,
-                mBitmapCircleGreen, mBitmapCircleRed };
+                mBitmapCircleAlpha};
 
         for (Bitmap bitmap : bitmaps) {
             mBitmapWidth = Math.max(mBitmapWidth, bitmap.getWidth());
             mBitmapHeight = Math.max(mBitmapHeight, bitmap.getHeight());
         }
 
+        mPaint.setAntiAlias(true);
+        mPaint.setDither(true);
         mPaint.setFilterBitmap(true);
 
         mCellStates = new CellState[3][3];
@@ -963,7 +971,12 @@
     }
 
     private void drawArrow(Canvas canvas, float leftX, float topY, Cell start, Cell end) {
-        boolean green = mPatternDisplayMode != DisplayMode.Wrong;
+        if (mPatternInProgress) {
+            mPaint.setColorFilter(mRegularColorFilter);
+        } else {
+            boolean success = mPatternDisplayMode != DisplayMode.Wrong;
+            mPaint.setColorFilter(success ? mSuccessColorFilter : mErrorColorFilter);
+        }
 
         final int endRow = end.row;
         final int startRow = start.row;
@@ -977,7 +990,6 @@
         // compute transform to place arrow bitmaps at correct angle inside circle.
         // This assumes that the arrow image is drawn at 12:00 with it's top edge
         // coincident with the circle bitmap's top edge.
-        Bitmap arrow = green ? mBitmapArrowGreenUp : mBitmapArrowRedUp;
         final int cellWidth = mBitmapWidth;
         final int cellHeight = mBitmapHeight;
 
@@ -994,8 +1006,8 @@
         mArrowMatrix.preScale(sx, sy);
         mArrowMatrix.preTranslate(-mBitmapWidth/2, -mBitmapHeight/2);
         mArrowMatrix.preRotate(angle, cellWidth / 2.0f, cellHeight / 2.0f);  // rotate about cell center
-        mArrowMatrix.preTranslate((cellWidth - arrow.getWidth()) / 2.0f, 0.0f); // translate to 12:00 pos
-        canvas.drawBitmap(arrow, mArrowMatrix, mPaint);
+        mArrowMatrix.preTranslate((cellWidth - mBitmapArrowAlphaUp.getWidth()) / 2.0f, 0.0f); // translate to 12:00 pos
+        canvas.drawBitmap(mBitmapArrowAlphaUp, mArrowMatrix, mPaint);
     }
 
     /**
@@ -1008,24 +1020,28 @@
             boolean partOfPattern) {
         Bitmap outerCircle;
         Bitmap innerCircle;
-
+        ColorFilter outerFilter;
         if (!partOfPattern || mInStealthMode) {
             // unselected circle
             outerCircle = mBitmapCircleDefault;
             innerCircle = mBitmapBtnDefault;
+            outerFilter = mRegularColorFilter;
         } else if (mPatternInProgress) {
             // user is in middle of drawing a pattern
-            outerCircle = mBitmapCircleGreen;
+            outerCircle = mBitmapCircleAlpha;
             innerCircle = mBitmapBtnTouched;
+            outerFilter = mRegularColorFilter;
         } else if (mPatternDisplayMode == DisplayMode.Wrong) {
             // the pattern is wrong
-            outerCircle = mBitmapCircleRed;
+            outerCircle = mBitmapCircleAlpha;
             innerCircle = mBitmapBtnDefault;
+            outerFilter = mErrorColorFilter;
         } else if (mPatternDisplayMode == DisplayMode.Correct ||
                 mPatternDisplayMode == DisplayMode.Animate) {
             // the pattern is correct
-            outerCircle = mBitmapCircleGreen;
+            outerCircle = mBitmapCircleAlpha;
             innerCircle = mBitmapBtnDefault;
+            outerFilter = mSuccessColorFilter;
         } else {
             throw new IllegalStateException("unknown display mode " + mPatternDisplayMode);
         }
@@ -1048,7 +1064,9 @@
         mCircleMatrix.preScale(sx * scale, sy * scale);
         mCircleMatrix.preTranslate(-mBitmapWidth/2, -mBitmapHeight/2);
 
+        mPaint.setColorFilter(outerFilter);
         canvas.drawBitmap(outerCircle, mCircleMatrix, mPaint);
+        mPaint.setColorFilter(mRegularColorFilter);
         canvas.drawBitmap(innerCircle, mCircleMatrix, mPaint);
     }
 
diff --git a/core/java/com/android/internal/widget/SubtitleView.java b/core/java/com/android/internal/widget/SubtitleView.java
index 117463a..2f987e9 100644
--- a/core/java/com/android/internal/widget/SubtitleView.java
+++ b/core/java/com/android/internal/widget/SubtitleView.java
@@ -271,10 +271,13 @@
             style = CaptionStyle.PRESETS[styleId];
         }
 
-        mForegroundColor = style.foregroundColor;
-        mBackgroundColor = style.backgroundColor;
-        mEdgeType = style.edgeType;
-        mEdgeColor = style.edgeColor;
+        final CaptionStyle defStyle = CaptionStyle.DEFAULT;
+        mForegroundColor = style.hasForegroundColor() ?
+                style.foregroundColor : defStyle.foregroundColor;
+        mBackgroundColor = style.hasBackgroundColor() ?
+                style.backgroundColor : defStyle.backgroundColor;
+        mEdgeType = style.hasEdgeType() ? style.edgeType : defStyle.edgeType;
+        mEdgeColor = style.hasEdgeColor() ? style.edgeColor : defStyle.edgeColor;
         mHasMeasurements = false;
 
         final Typeface typeface = style.getTypeface();
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index ec935cc..8e56eec 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -879,8 +879,8 @@
 
 #ifdef USE_MINIKIN
         Layout layout;
-        MinikinUtils::SetLayoutProperties(&layout, paint, flags, typeface);
-        layout.doLayout(textArray + start, count);
+        std::string css = MinikinUtils::setLayoutProperties(&layout, paint, flags, typeface);
+        layout.doLayout(textArray, start, count, contextCount, css);
         drawGlyphsToSkia(canvas, paint, layout, x, y);
 #else
         sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
diff --git a/core/jni/android/graphics/MinikinSkia.cpp b/core/jni/android/graphics/MinikinSkia.cpp
index 243fa10..2b96f1b 100644
--- a/core/jni/android/graphics/MinikinSkia.cpp
+++ b/core/jni/android/graphics/MinikinSkia.cpp
@@ -46,8 +46,10 @@
 static void MinikinFontSkia_SetSkiaPaint(SkTypeface* typeface, SkPaint* skPaint, const MinikinPaint& paint) {
     skPaint->setTypeface(typeface);
     skPaint->setTextEncoding(SkPaint::kGlyphID_TextEncoding);
-    // TODO: set more paint parameters from Minikin
     skPaint->setTextSize(paint.size);
+    skPaint->setTextScaleX(paint.scaleX);
+    skPaint->setTextSkewX(paint.skewX);
+    MinikinFontSkia::unpackPaintFlags(skPaint, paint.paintFlags);
 }
 
 float MinikinFontSkia::GetHorizontalAdvance(uint32_t glyph_id,
@@ -96,4 +98,21 @@
     return mTypeface->uniqueID();
 }
 
+uint32_t MinikinFontSkia::packPaintFlags(const SkPaint* paint) {
+    uint32_t flags = paint->getFlags();
+    SkPaint::Hinting hinting = paint->getHinting();
+    // select only flags that might affect text layout
+    flags &= (SkPaint::kAntiAlias_Flag | SkPaint::kFakeBoldText_Flag | SkPaint::kLinearText_Flag |
+            SkPaint::kSubpixelText_Flag | SkPaint::kDevKernText_Flag |
+            SkPaint::kEmbeddedBitmapText_Flag | SkPaint::kAutoHinting_Flag |
+            SkPaint::kVerticalText_Flag);
+    flags |= (hinting << 16);
+    return flags;
+}
+
+void MinikinFontSkia::unpackPaintFlags(SkPaint* paint, uint32_t paintFlags) {
+    paint->setFlags(paintFlags & SkPaint::kAllFlags);
+    paint->setHinting(static_cast<SkPaint::Hinting>(paintFlags >> 16));
+}
+
 }
diff --git a/core/jni/android/graphics/MinikinSkia.h b/core/jni/android/graphics/MinikinSkia.h
index 1cc2c51..0452c57 100644
--- a/core/jni/android/graphics/MinikinSkia.h
+++ b/core/jni/android/graphics/MinikinSkia.h
@@ -38,6 +38,8 @@
 
     SkTypeface *GetSkTypeface();
 
+    static uint32_t packPaintFlags(const SkPaint* paint);
+    static void unpackPaintFlags(SkPaint* paint, uint32_t paintFlags);
 private:
     SkTypeface *mTypeface;
 };
diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp
index a88b747..a9360ea 100644
--- a/core/jni/android/graphics/MinikinUtils.cpp
+++ b/core/jni/android/graphics/MinikinUtils.cpp
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
+#define LOG_TAG "Minikin"
+#include <cutils/log.h>
+#include <string>
+
 #include "SkPaint.h"
 #include "minikin/Layout.h"
 #include "TypefaceImpl.h"
@@ -23,24 +27,39 @@
 
 namespace android {
 
-void MinikinUtils::SetLayoutProperties(Layout* layout, const SkPaint* paint, int flags,
-    TypefaceImpl* typeface) {
+// Do an sprintf starting at offset n, abort on overflow
+static int snprintfcat(char* buf, int off, int size, const char* format, ...) {
+    va_list args;
+    va_start(args, format);
+    int n = vsnprintf(buf + off, size - off, format, args);
+    LOG_ALWAYS_FATAL_IF(n >= size - off, "String overflow in setting layout properties");
+    va_end(args);
+    return off + n;
+}
+
+std::string MinikinUtils::setLayoutProperties(Layout* layout, const SkPaint* paint, int bidiFlags,
+        TypefaceImpl* typeface) {
     TypefaceImpl* resolvedFace = TypefaceImpl_resolveDefault(typeface);
     layout->setFontCollection(resolvedFace->fFontCollection);
     FontStyle style = resolvedFace->fStyle;
     char css[256];
-    int off = snprintf(css, sizeof(css),
-        "font-size: %d; font-weight: %d; font-style: %s; -minikin-bidi: %d;",
+    int off = snprintfcat(css, 0, sizeof(css),
+        "font-size: %d; font-scale-x: %f; font-skew-x: %f; -paint-flags: %d;"
+        " font-weight: %d; font-style: %s; -minikin-bidi: %d;",
         (int)paint->getTextSize(),
+        paint->getTextScaleX(),
+        paint->getTextSkewX(),
+        MinikinFontSkia::packPaintFlags(paint),
         style.getWeight() * 100,
         style.getItalic() ? "italic" : "normal",
-        flags);
+        bidiFlags);
     SkString langString = paint->getPaintOptionsAndroid().getLanguage().getTag();
-    off += snprintf(css + off, sizeof(css) - off, " lang: %s;", langString.c_str());
+    off = snprintfcat(css, off, sizeof(css), " lang: %s;", langString.c_str());
     SkPaintOptionsAndroid::FontVariant var = paint->getPaintOptionsAndroid().getFontVariant();
     const char* varstr = var == SkPaintOptionsAndroid::kElegant_Variant ? "elegant" : "compact";
-    off += snprintf(css + off, sizeof(css) - off, " -minikin-variant: %s;", varstr);
+    off = snprintfcat(css, off, sizeof(css), " -minikin-variant: %s;", varstr);
     layout->setProperties(css);
+    return std::string(css);
 }
 
 float MinikinUtils::xOffsetForTextAlign(SkPaint* paint, const Layout& layout) {
diff --git a/core/jni/android/graphics/MinikinUtils.h b/core/jni/android/graphics/MinikinUtils.h
index 997d6e3..ea7eb5d 100644
--- a/core/jni/android/graphics/MinikinUtils.h
+++ b/core/jni/android/graphics/MinikinUtils.h
@@ -26,10 +26,14 @@
 
 namespace android {
 
+class Layout;
+class TypefaceImpl;
+
 class MinikinUtils {
 public:
-    static void SetLayoutProperties(Layout* layout, const SkPaint* paint, int flags,
-        TypefaceImpl* face);
+    static std::string setLayoutProperties(Layout* layout, const SkPaint* paint, int bidiFlags,
+            TypefaceImpl* typeface);
+
     static float xOffsetForTextAlign(SkPaint* paint, const Layout& layout);
 
     // f is a functor of type void f(SkTypeface *, size_t start, size_t end);
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 4000b07..3dc874e 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -520,8 +520,8 @@
 #ifdef USE_MINIKIN
         Layout layout;
         TypefaceImpl* typeface = GraphicsJNI::getNativeTypeface(env, jpaint);
-        MinikinUtils::SetLayoutProperties(&layout, paint, bidiFlags, typeface);
-        layout.doLayout(textArray + index, count);
+        std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
+        layout.doLayout(textArray, index, count, textLength, css);
         result = layout.getAdvance();
 #else
         TextLayout::getTextRunAdvances(paint, textArray, index, count, textLength,
@@ -554,8 +554,8 @@
 #ifdef USE_MINIKIN
         Layout layout;
         TypefaceImpl* typeface = GraphicsJNI::getNativeTypeface(env, jpaint);
-        MinikinUtils::SetLayoutProperties(&layout, paint, bidiFlags, typeface);
-        layout.doLayout(textArray + start, count);
+        std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
+        layout.doLayout(textArray, start, count, textLength, css);
         width = layout.getAdvance();
 #else
         TextLayout::getTextRunAdvances(paint, textArray, start, count, textLength,
@@ -582,8 +582,8 @@
 #ifdef USE_MINIKIN
         Layout layout;
         TypefaceImpl* typeface = GraphicsJNI::getNativeTypeface(env, jpaint);
-        MinikinUtils::SetLayoutProperties(&layout, paint, bidiFlags, typeface);
-        layout.doLayout(textArray, textLength);
+        std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
+        layout.doLayout(textArray, 0, textLength, textLength, css);
         width = layout.getAdvance();
 #else
         TextLayout::getTextRunAdvances(paint, textArray, 0, textLength, textLength,
@@ -617,8 +617,8 @@
 
 #ifdef USE_MINIKIN
         Layout layout;
-        MinikinUtils::SetLayoutProperties(&layout, paint, bidiFlags, typeface);
-        layout.doLayout(text, count);
+        std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
+        layout.doLayout(text, 0, count, count, css);
         layout.getAdvances(widthsArray);
 #else
         TextLayout::getTextRunAdvances(paint, text, 0, count, count,
@@ -715,8 +715,8 @@
 
 #ifdef USE_MINIKIN
         Layout layout;
-        MinikinUtils::SetLayoutProperties(&layout, paint, flags, typeface);
-        layout.doLayout(text + start, count);
+        std::string css = MinikinUtils::setLayoutProperties(&layout, paint, flags, typeface);
+        layout.doLayout(text, start, count, contextCount, css);
         layout.getAdvances(advancesArray);
         totalAdvance = layout.getAdvance();
 #else
@@ -860,8 +860,8 @@
             jint count, jint bidiFlags, jfloat x, jfloat y, SkPath* path) {
 #ifdef USE_MINIKIN
         Layout layout;
-        MinikinUtils::SetLayoutProperties(&layout, paint, bidiFlags, typeface);
-        layout.doLayout(text, count);
+        std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
+        layout.doLayout(text, 0, count, count, css);
         size_t nGlyphs = layout.nGlyphs();
         uint16_t* glyphs = new uint16_t[nGlyphs];
         SkPoint* pos = new SkPoint[nGlyphs];
@@ -992,8 +992,8 @@
 
 #ifdef USE_MINIKIN
         Layout layout;
-        MinikinUtils::SetLayoutProperties(&layout, &paint, bidiFlags, typeface);
-        layout.doLayout(text, count);
+        std::string css = MinikinUtils::setLayoutProperties(&layout, &paint, bidiFlags, typeface);
+        layout.doLayout(text, 0, count, count, css);
         MinikinRect rect;
         layout.getBounds(&rect);
         r.fLeft = rect.mLeft;
diff --git a/core/jni/android/graphics/TypefaceImpl.cpp b/core/jni/android/graphics/TypefaceImpl.cpp
index 786d19c..27df7cf 100644
--- a/core/jni/android/graphics/TypefaceImpl.cpp
+++ b/core/jni/android/graphics/TypefaceImpl.cpp
@@ -32,6 +32,7 @@
 #include <minikin/FontCollection.h>
 #include <minikin/FontFamily.h>
 #include <minikin/Layout.h>
+#include "SkPaint.h"
 #include "MinikinSkia.h"
 #endif
 
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index 820da17..a46ccd6 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -702,8 +702,8 @@
         jfloat x, jfloat y, int flags, SkPaint* paint, TypefaceImpl* typeface) {
 #ifdef USE_MINIKIN
     Layout layout;
-    MinikinUtils::SetLayoutProperties(&layout, paint, flags, typeface);
-    layout.doLayout(text, count);
+    std::string css = MinikinUtils::setLayoutProperties(&layout, paint, flags, typeface);
+    layout.doLayout(text, 0, count, count, css);
     x += xOffsetForTextAlign(paint, layout.getAdvance());
     renderTextLayout(renderer, &layout, x, y, paint);
 #else
@@ -746,8 +746,8 @@
         int flags, SkPaint* paint, TypefaceImpl* typeface) {
 #ifdef USE_MINIKIN
     Layout layout;
-    MinikinUtils::SetLayoutProperties(&layout, paint, flags, typeface);
-    layout.doLayout(text + start, count);
+    std::string css = MinikinUtils::setLayoutProperties(&layout, paint, flags, typeface);
+    layout.doLayout(text, start, count, contextCount, css);
     x += xOffsetForTextAlign(paint, layout.getAdvance());
     renderTextLayout(renderer, &layout, x, y, paint);
 #else
diff --git a/core/res/res/drawable-hdpi/btn_code_lock_default.png b/core/res/res/drawable-hdpi/btn_code_lock_default.png
deleted file mode 100644
index 4469ce0..0000000
--- a/core/res/res/drawable-hdpi/btn_code_lock_default.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_code_lock_default_holo.png b/core/res/res/drawable-hdpi/btn_code_lock_default_holo.png
deleted file mode 100644
index 449d427..0000000
--- a/core/res/res/drawable-hdpi/btn_code_lock_default_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_code_lock_default_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_code_lock_default_qntm_alpha.png
new file mode 100644
index 0000000..7cc3c11
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_code_lock_default_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_code_lock_touched.png b/core/res/res/drawable-hdpi/btn_code_lock_touched.png
deleted file mode 100644
index 0410dd3..0000000
--- a/core/res/res/drawable-hdpi/btn_code_lock_touched.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_code_lock_touched_holo.png b/core/res/res/drawable-hdpi/btn_code_lock_touched_holo.png
deleted file mode 100644
index 66cb1ec..0000000
--- a/core/res/res/drawable-hdpi/btn_code_lock_touched_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_code_lock_touched_qntm_alpha.png b/core/res/res/drawable-hdpi/btn_code_lock_touched_qntm_alpha.png
new file mode 100644
index 0000000..70397d21
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_code_lock_touched_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_drag_direction_red_up.png b/core/res/res/drawable-hdpi/indicator_code_lock_drag_direction_red_up.png
deleted file mode 100644
index 698c3ec..0000000
--- a/core/res/res/drawable-hdpi/indicator_code_lock_drag_direction_red_up.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png b/core/res/res/drawable-hdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png
new file mode 100644
index 0000000..b9b400f
--- /dev/null
+++ b/core/res/res/drawable-hdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_default.png b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_default.png
deleted file mode 100644
index c45b956..0000000
--- a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_default.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_default_holo.png b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_default_qntm_alpha.png
similarity index 82%
rename from core/res/res/drawable-hdpi/indicator_code_lock_point_area_default_holo.png
rename to core/res/res/drawable-hdpi/indicator_code_lock_point_area_default_qntm_alpha.png
index 7fe402a..b1601f4 100644
--- a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_default_holo.png
+++ b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_default_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_green.png b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_green.png
deleted file mode 100644
index b9fd0a4..0000000
--- a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_green.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_green_holo.png b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_qntm_alpha.png
similarity index 95%
rename from core/res/res/drawable-hdpi/indicator_code_lock_point_area_green_holo.png
rename to core/res/res/drawable-hdpi/indicator_code_lock_point_area_qntm_alpha.png
index 4052eed..a038a13 100644
--- a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_green_holo.png
+++ b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_red.png b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_red.png
deleted file mode 100644
index 94e947d..0000000
--- a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_red.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_red_holo.png b/core/res/res/drawable-hdpi/indicator_code_lock_point_area_red_holo.png
deleted file mode 100644
index 738d0fe..0000000
--- a/core/res/res/drawable-hdpi/indicator_code_lock_point_area_red_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-ldpi/btn_code_lock_default.png b/core/res/res/drawable-ldpi/btn_code_lock_default.png
deleted file mode 100644
index 149da9b..0000000
--- a/core/res/res/drawable-ldpi/btn_code_lock_default.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-ldpi/btn_code_lock_touched.png b/core/res/res/drawable-ldpi/btn_code_lock_touched.png
deleted file mode 100644
index ad9a313..0000000
--- a/core/res/res/drawable-ldpi/btn_code_lock_touched.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-ldpi/indicator_code_lock_drag_direction_red_up.png b/core/res/res/drawable-ldpi/indicator_code_lock_drag_direction_red_up.png
deleted file mode 100644
index ac8e42a..0000000
--- a/core/res/res/drawable-ldpi/indicator_code_lock_drag_direction_red_up.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-ldpi/indicator_code_lock_point_area_default.png b/core/res/res/drawable-ldpi/indicator_code_lock_point_area_default.png
deleted file mode 100644
index 5b77b9f..0000000
--- a/core/res/res/drawable-ldpi/indicator_code_lock_point_area_default.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-ldpi/indicator_code_lock_point_area_green.png b/core/res/res/drawable-ldpi/indicator_code_lock_point_area_green.png
deleted file mode 100644
index c7c0b9a..0000000
--- a/core/res/res/drawable-ldpi/indicator_code_lock_point_area_green.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-ldpi/indicator_code_lock_point_area_red.png b/core/res/res/drawable-ldpi/indicator_code_lock_point_area_red.png
deleted file mode 100644
index ac02dc4..0000000
--- a/core/res/res/drawable-ldpi/indicator_code_lock_point_area_red.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_code_lock_default.png b/core/res/res/drawable-mdpi/btn_code_lock_default.png
deleted file mode 100644
index 206f9b3..0000000
--- a/core/res/res/drawable-mdpi/btn_code_lock_default.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_code_lock_default_holo.png b/core/res/res/drawable-mdpi/btn_code_lock_default_holo.png
deleted file mode 100644
index 4c4adf2..0000000
--- a/core/res/res/drawable-mdpi/btn_code_lock_default_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_code_lock_default_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_code_lock_default_qntm_alpha.png
new file mode 100644
index 0000000..14d0b32
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_code_lock_default_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_code_lock_touched.png b/core/res/res/drawable-mdpi/btn_code_lock_touched.png
deleted file mode 100644
index fe5c1af..0000000
--- a/core/res/res/drawable-mdpi/btn_code_lock_touched.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_code_lock_touched_holo.png b/core/res/res/drawable-mdpi/btn_code_lock_touched_holo.png
deleted file mode 100644
index ef701ed..0000000
--- a/core/res/res/drawable-mdpi/btn_code_lock_touched_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_code_lock_touched_qntm_alpha.png b/core/res/res/drawable-mdpi/btn_code_lock_touched_qntm_alpha.png
new file mode 100644
index 0000000..9cfbdf9
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_code_lock_touched_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/indicator_code_lock_drag_direction_red_up.png b/core/res/res/drawable-mdpi/indicator_code_lock_drag_direction_red_up.png
deleted file mode 100644
index 7201e58..0000000
--- a/core/res/res/drawable-mdpi/indicator_code_lock_drag_direction_red_up.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png b/core/res/res/drawable-mdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png
new file mode 100644
index 0000000..2fb1325
--- /dev/null
+++ b/core/res/res/drawable-mdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/indicator_code_lock_point_area_default.png b/core/res/res/drawable-mdpi/indicator_code_lock_point_area_default.png
deleted file mode 100644
index 05c194b..0000000
--- a/core/res/res/drawable-mdpi/indicator_code_lock_point_area_default.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/indicator_code_lock_point_area_default_holo.png b/core/res/res/drawable-mdpi/indicator_code_lock_point_area_default_qntm_alpha.png
similarity index 78%
rename from core/res/res/drawable-mdpi/indicator_code_lock_point_area_default_holo.png
rename to core/res/res/drawable-mdpi/indicator_code_lock_point_area_default_qntm_alpha.png
index 5762e5f..07d4afd 100644
--- a/core/res/res/drawable-mdpi/indicator_code_lock_point_area_default_holo.png
+++ b/core/res/res/drawable-mdpi/indicator_code_lock_point_area_default_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/indicator_code_lock_point_area_green.png b/core/res/res/drawable-mdpi/indicator_code_lock_point_area_green.png
deleted file mode 100644
index 8f24832..0000000
--- a/core/res/res/drawable-mdpi/indicator_code_lock_point_area_green.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/indicator_code_lock_point_area_green_holo.png b/core/res/res/drawable-mdpi/indicator_code_lock_point_area_qntm_alpha.png
similarity index 94%
rename from core/res/res/drawable-mdpi/indicator_code_lock_point_area_green_holo.png
rename to core/res/res/drawable-mdpi/indicator_code_lock_point_area_qntm_alpha.png
index bfb0967..ea8c2b4 100644
--- a/core/res/res/drawable-mdpi/indicator_code_lock_point_area_green_holo.png
+++ b/core/res/res/drawable-mdpi/indicator_code_lock_point_area_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/indicator_code_lock_point_area_red_holo.png b/core/res/res/drawable-mdpi/indicator_code_lock_point_area_red_holo.png
deleted file mode 100644
index 8c0386f..0000000
--- a/core/res/res/drawable-mdpi/indicator_code_lock_point_area_red_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_green_up.png b/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_green_up.png
deleted file mode 100644
index cc46f19..0000000
--- a/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_green_up.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_red_up.png b/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_red_up.png
deleted file mode 100644
index cc46f19..0000000
--- a/core/res/res/drawable-nodpi/indicator_code_lock_drag_direction_red_up.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml
index 668cff7..d1e2df3 100644
--- a/core/res/res/drawable-nodpi/platlogo.xml
+++ b/core/res/res/drawable-nodpi/platlogo.xml
@@ -19,18 +19,21 @@
     <viewport android:viewportHeight="25" android:viewportWidth="25" />
 
     <path
-        android:name="shadow"
-        android:pathData="m12,2.5 a11,11 0 1,0 1,0
-        M6.5,7.5
-        l5,0 l0,7 l7,0 l0,5 l-12,0 z"
-        android:fill="#40000000"
+        android:name="torso"
+        android:pathData="m2,2 l21,0 l0,21 l-21,0 z"
+        android:fill="#FFFFFFFF"
         />
+
     <path
-        android:name="circle-L-ranch"
-        android:pathData="m12,1.5 a11,11 0 1,0 1,0
-        M6.5,6.5
-        l5,0 l0,7 l7,0 l0,5 l-12,0 z"
-        android:fill="#FFFFFF40"
+        android:name="|"
+        android:pathData="m4,4 l8,0 l0,17 l-8,0 z"
+        android:fill="#FF0000FF"
+        />
+
+    <path
+        android:name="_"
+        android:pathData="m5,14 l16,0 l0,6 l-16,0 z"
+        android:fill="#FFFF0000"
         />
 
 </vector>
diff --git a/core/res/res/drawable-nodpi/stat_sys_adb.xml b/core/res/res/drawable-nodpi/stat_sys_adb.xml
index b8ddb77..6b3be4a 100644
--- a/core/res/res/drawable-nodpi/stat_sys_adb.xml
+++ b/core/res/res/drawable-nodpi/stat_sys_adb.xml
@@ -18,13 +18,26 @@
 
     <viewport android:viewportHeight="25" android:viewportWidth="25" />
 
-
     <path
-        android:name="adb"
-        android:pathData="m3,3l8,0l0,11l11,0l0,8l-19,0z"
+        android:name="L-card"
+
+        android:pathData="
+        m4,2
+        a2,2,0,0,0,-2,2 l0,17
+        a2,2,0,0,0,2,2  l17,0
+        a2,2,0,0,0,2,-2 l0,-17
+        a2,2,0,0,0,-2,-2
+        z
+
+        M7,2 l3,0 l0,13 l13,0 l0,3 l-16,0
+
+        M15,2 l3,0 l0,5 l5,0 l0,3 l-8,0
+
+        z"
         android:fill="#FFFFFFFF"
         />
 
+
 </vector>
 
 
diff --git a/core/res/res/drawable-sw600dp-mdpi/indicator_code_lock_drag_direction_red_up.png b/core/res/res/drawable-sw600dp-mdpi/indicator_code_lock_drag_direction_red_up.png
deleted file mode 100644
index 2ab4547..0000000
--- a/core/res/res/drawable-sw600dp-mdpi/indicator_code_lock_drag_direction_red_up.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_code_lock_default.png b/core/res/res/drawable-xhdpi/btn_code_lock_default.png
deleted file mode 100644
index c1358a2..0000000
--- a/core/res/res/drawable-xhdpi/btn_code_lock_default.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_code_lock_default_holo.png b/core/res/res/drawable-xhdpi/btn_code_lock_default_holo.png
deleted file mode 100644
index db1cbe6..0000000
--- a/core/res/res/drawable-xhdpi/btn_code_lock_default_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_code_lock_default_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_code_lock_default_qntm_alpha.png
new file mode 100644
index 0000000..0c457b4
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/btn_code_lock_default_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_code_lock_touched.png b/core/res/res/drawable-xhdpi/btn_code_lock_touched.png
deleted file mode 100644
index 0fafc3e..0000000
--- a/core/res/res/drawable-xhdpi/btn_code_lock_touched.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_code_lock_touched_holo.png b/core/res/res/drawable-xhdpi/btn_code_lock_touched_holo.png
deleted file mode 100644
index 073c3ac..0000000
--- a/core/res/res/drawable-xhdpi/btn_code_lock_touched_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_code_lock_touched_qntm_alpha.png b/core/res/res/drawable-xhdpi/btn_code_lock_touched_qntm_alpha.png
new file mode 100644
index 0000000..020d699
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/btn_code_lock_touched_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/indicator_code_lock_drag_direction_red_up.png b/core/res/res/drawable-xhdpi/indicator_code_lock_drag_direction_red_up.png
deleted file mode 100644
index 2d34cf6..0000000
--- a/core/res/res/drawable-xhdpi/indicator_code_lock_drag_direction_red_up.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png b/core/res/res/drawable-xhdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png
new file mode 100644
index 0000000..fda5e37
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_default.png b/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_default.png
deleted file mode 100644
index 0812cb5..0000000
--- a/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_default.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_default_holo.png b/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_default_qntm_alpha.png
similarity index 86%
rename from core/res/res/drawable-xhdpi/indicator_code_lock_point_area_default_holo.png
rename to core/res/res/drawable-xhdpi/indicator_code_lock_point_area_default_qntm_alpha.png
index 6a97445..75d0221 100644
--- a/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_default_holo.png
+++ b/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_default_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_green.png b/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_green.png
deleted file mode 100644
index 3ab2e99..0000000
--- a/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_green.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_green_holo.png b/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_qntm_alpha.png
similarity index 96%
rename from core/res/res/drawable-xhdpi/indicator_code_lock_point_area_green_holo.png
rename to core/res/res/drawable-xhdpi/indicator_code_lock_point_area_qntm_alpha.png
index f0e9ab9..225799b 100644
--- a/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_green_holo.png
+++ b/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_red_holo.png b/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_red_holo.png
deleted file mode 100644
index 170b833..0000000
--- a/core/res/res/drawable-xhdpi/indicator_code_lock_point_area_red_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/btn_code_lock_default_holo.png b/core/res/res/drawable-xxhdpi/btn_code_lock_default_qntm_alpha.png
similarity index 100%
rename from core/res/res/drawable-xxhdpi/btn_code_lock_default_holo.png
rename to core/res/res/drawable-xxhdpi/btn_code_lock_default_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/btn_code_lock_touched_holo.png b/core/res/res/drawable-xxhdpi/btn_code_lock_touched_qntm_alpha.png
similarity index 100%
rename from core/res/res/drawable-xxhdpi/btn_code_lock_touched_holo.png
rename to core/res/res/drawable-xxhdpi/btn_code_lock_touched_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png b/core/res/res/drawable-xxhdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png
new file mode 100644
index 0000000..d3e80be
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/indicator_code_lock_point_area_default_holo.png b/core/res/res/drawable-xxhdpi/indicator_code_lock_point_area_default_qntm_alpha.png
similarity index 100%
rename from core/res/res/drawable-xxhdpi/indicator_code_lock_point_area_default_holo.png
rename to core/res/res/drawable-xxhdpi/indicator_code_lock_point_area_default_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/indicator_code_lock_point_area_green_holo.png b/core/res/res/drawable-xxhdpi/indicator_code_lock_point_area_qntm_alpha.png
similarity index 100%
rename from core/res/res/drawable-xxhdpi/indicator_code_lock_point_area_green_holo.png
rename to core/res/res/drawable-xxhdpi/indicator_code_lock_point_area_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/indicator_code_lock_point_area_red_holo.png b/core/res/res/drawable-xxhdpi/indicator_code_lock_point_area_red_holo.png
deleted file mode 100644
index f6c3e27..0000000
--- a/core/res/res/drawable-xxhdpi/indicator_code_lock_point_area_red_holo.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xxxhdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png b/core/res/res/drawable-xxxhdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png
new file mode 100644
index 0000000..23214fa
--- /dev/null
+++ b/core/res/res/drawable-xxxhdpi/indicator_code_lock_drag_direction_up_qntm_alpha.png
Binary files differ
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 0811c02..08c1680 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -6344,6 +6344,12 @@
         <attr name="aspect" format="string" />
         <!-- Color to use when drawing LockPatternView paths. -->
         <attr name="pathColor" format="color|reference" />
+        <!-- The regular pattern color -->
+        <attr name="regularColor" format="color|reference" />
+        <!-- The error color -->
+        <attr name="errorColor" format="color|reference" />
+        <!-- The success color -->
+        <attr name="successColor" format="color|reference"/>
     </declare-styleable>
 
     <!-- Use <code>recognition-service</code> as the root tag of the XML resource that
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 5a2609e..9bf2ce8 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -115,6 +115,11 @@
     <color name="kg_multi_user_text_inactive">#ff808080</color>
     <color name="kg_widget_pager_gradient">#ffffffff</color>
 
+    <!-- LockPatternView -->
+    <color name="lock_pattern_view_regular_color">#ffffffff</color>
+    <color name="lock_pattern_view_success_color">#ffffffff</color>
+    <color name="lock_pattern_view_error_color">#fff4511e</color>
+
     <!-- FaceLock -->
     <color name="facelock_spotlight_mask">#CC000000</color>
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8af45db..865d92a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -364,6 +364,9 @@
     <!-- If this is true, the screen will come on when you unplug usb/power/whatever. -->
     <bool name="config_unplugTurnsOnScreen">false</bool>
 
+    <!-- Set this true only if the device has separate attention and notification lights. -->
+    <bool name="config_useAttentionLight">false</bool>
+
     <!-- If this is true, the screen will fade off. -->
     <bool name="config_animateScreenLights">true</bool>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 41238a3..61b6a0d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1017,8 +1017,14 @@
   <java-symbol type="drawable" name="text_edit_side_paste_window" />
   <java-symbol type="drawable" name="text_edit_paste_window" />
   <java-symbol type="drawable" name="btn_check_off" />
-  <java-symbol type="drawable" name="btn_code_lock_default_holo" />
-  <java-symbol type="drawable" name="btn_code_lock_touched_holo" />
+  <java-symbol type="drawable" name="btn_code_lock_default_qntm_alpha" />
+  <java-symbol type="drawable" name="btn_code_lock_touched_qntm_alpha" />
+  <java-symbol type="drawable" name="indicator_code_lock_point_area_default_qntm_alpha" />
+  <java-symbol type="drawable" name="indicator_code_lock_point_area_qntm_alpha" />
+  <java-symbol type="drawable" name="indicator_code_lock_drag_direction_up_qntm_alpha" />
+  <java-symbol type="color" name="lock_pattern_view_regular_color" />
+  <java-symbol type="color" name="lock_pattern_view_success_color" />
+  <java-symbol type="color" name="lock_pattern_view_error_color" />
   <java-symbol type="drawable" name="clock_dial" />
   <java-symbol type="drawable" name="clock_hand_hour" />
   <java-symbol type="drawable" name="clock_hand_minute" />
@@ -1062,11 +1068,6 @@
   <java-symbol type="drawable" name="ic_print" />
   <java-symbol type="drawable" name="ic_print_error" />
   <java-symbol type="drawable" name="ic_grayedout_printer" />
-  <java-symbol type="drawable" name="indicator_code_lock_drag_direction_green_up" />
-  <java-symbol type="drawable" name="indicator_code_lock_drag_direction_red_up" />
-  <java-symbol type="drawable" name="indicator_code_lock_point_area_default_holo" />
-  <java-symbol type="drawable" name="indicator_code_lock_point_area_green_holo" />
-  <java-symbol type="drawable" name="indicator_code_lock_point_area_red_holo" />
   <java-symbol type="drawable" name="jog_dial_arrow_long_left_green" />
   <java-symbol type="drawable" name="jog_dial_arrow_long_right_red" />
   <java-symbol type="drawable" name="jog_dial_arrow_short_left_and_right" />
@@ -1455,6 +1456,7 @@
   <java-symbol type="array" name="config_defaultNotificationVibePattern" />
   <java-symbol type="array" name="config_notificationFallbackVibePattern" />
   <java-symbol type="array" name="config_onlySingleDcAllowed" />
+  <java-symbol type="bool" name="config_useAttentionLight" />
   <java-symbol type="bool" name="config_animateScreenLights" />
   <java-symbol type="bool" name="config_automatic_brightness_available" />
   <java-symbol type="bool" name="config_enableFusedLocationOverlay" />
diff --git a/data/fonts/Roboto-MediumItalic.ttf b/data/fonts/Roboto-MediumItalic.ttf
index a30aa0c..b828205 100644
--- a/data/fonts/Roboto-MediumItalic.ttf
+++ b/data/fonts/Roboto-MediumItalic.ttf
Binary files differ
diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java
index 0ee253a..16548d0 100644
--- a/graphics/java/android/graphics/drawable/AnimationDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java
@@ -94,7 +94,7 @@
         boolean changed = super.setVisible(visible, restart);
         if (visible) {
             if (changed || restart) {
-                setFrame(0, true, mCurFrame >= 0);
+                setFrame(0, true, restart || mCurFrame >= 0);
             }
         } else {
             unscheduleSelf(this);
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 1512da5..241b89e 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -1203,8 +1203,11 @@
 
         st.mAttrStroke = a.extractThemeAttrs();
 
+        // We have an explicit stroke defined, so the default stroke width
+        // must be at least 0 or the current stroke width.
+        final int defaultStrokeWidth = Math.max(0, st.mStrokeWidth);
         final int width = a.getDimensionPixelSize(
-                R.styleable.GradientDrawableStroke_width, st.mStrokeWidth);
+                R.styleable.GradientDrawableStroke_width, defaultStrokeWidth);
         final float dashWidth = a.getDimension(
                 R.styleable.GradientDrawableStroke_dashWidth, st.mStrokeDashWidth);
 
@@ -1406,10 +1409,13 @@
                 outline.setOval(bounds);
                 return true;
             case LINE:
-                float halfStrokeWidth = mStrokePaint.getStrokeWidth() * 0.5f;
-                float centerY = bounds.centerY();
-                int top = (int) Math.floor(centerY - halfStrokeWidth);
-                int bottom = (int) Math.ceil(centerY + halfStrokeWidth);
+                // Hairlines (0-width stroke) must have a non-empty outline for
+                // shadows to draw correctly, so we'll use a very small width.
+                final float halfStrokeWidth = mStrokePaint == null ?
+                        0.0001f : mStrokePaint.getStrokeWidth() * 0.5f;
+                final float centerY = bounds.centerY();
+                final int top = (int) Math.floor(centerY - halfStrokeWidth);
+                final int bottom = (int) Math.ceil(centerY + halfStrokeWidth);
 
                 outline.setRect(bounds.left, top, bounds.right, bottom);
                 return true;
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index f0ae00f..dff4f6c 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -153,6 +153,11 @@
 
     // TODO: rename for consistency
     virtual status_t callDrawGLFunction(Functor* functor, Rect& dirty);
+protected:
+    // NOTE: must override these to avoid calling into super class, which calls GL. These may be
+    // removed once DisplayListRenderer no longer inherits from OpenGLRenderer
+    virtual void onViewportInitialized() {};
+    virtual void onSnapshotRestored() {};
 
 private:
     void insertRestoreToCount();
diff --git a/media/java/android/media/AudioPortEventHandler.java b/media/java/android/media/AudioPortEventHandler.java
index 782ecd8..d5fea07 100644
--- a/media/java/android/media/AudioPortEventHandler.java
+++ b/media/java/android/media/AudioPortEventHandler.java
@@ -56,7 +56,6 @@
             mHandler = new Handler(looper) {
                 @Override
                 public void handleMessage(Message msg) {
-                    Log.i(TAG, "handleMessage: "+msg.what);
                     ArrayList<AudioManager.OnAudioPortUpdateListener> listeners;
                     synchronized (this) {
                         if (msg.what == AUDIOPORT_EVENT_NEW_LISTENER) {
diff --git a/media/java/android/media/WebVttRenderer.java b/media/java/android/media/WebVttRenderer.java
index 1c9730f..7977988 100644
--- a/media/java/android/media/WebVttRenderer.java
+++ b/media/java/android/media/WebVttRenderer.java
@@ -1103,6 +1103,9 @@
  */
 class WebVttRenderingWidget extends ViewGroup implements SubtitleTrack.RenderingWidget {
     private static final boolean DEBUG = false;
+
+    private static final CaptionStyle DEFAULT_CAPTION_STYLE = CaptionStyle.DEFAULT;
+
     private static final int DEBUG_REGION_BACKGROUND = 0x800000FF;
     private static final int DEBUG_CUE_BACKGROUND = 0x80FF0000;
 
@@ -1144,7 +1147,8 @@
         this(context, attrs, defStyleAttr, 0);
     }
 
-    public WebVttRenderingWidget(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+    public WebVttRenderingWidget(
+            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
 
         // Cannot render text over video when layer type is hardware.
@@ -1259,6 +1263,7 @@
     }
 
     private void setCaptionStyle(CaptionStyle captionStyle, float fontSize) {
+        captionStyle = DEFAULT_CAPTION_STYLE.applyStyle(captionStyle);
         mCaptionStyle = captionStyle;
         mFontSize = fontSize;
 
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 6e0586e..5e650c2 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -86,6 +86,27 @@
     }
 
     /**
+     * Builds a URI that points to a channel logo. See {@link Channels.Logo}.
+     *
+     * @param channelId The ID of the channel whose logo is pointed to.
+     */
+    public static final Uri buildChannelLogoUri(long channelId) {
+        return buildChannelLogoUri(buildChannelUri(channelId));
+    }
+
+    /**
+     * Builds a URI that points to a channel logo. See {@link Channels.Logo}.
+     *
+     * @param channelUri The URI of the channel whose logo is pointed to.
+     */
+    public static final Uri buildChannelLogoUri(Uri channelUri) {
+        if (!PATH_CHANNEL.equals(channelUri.getPathSegments().get(0))) {
+            throw new IllegalArgumentException("Not a channel: " + channelUri);
+        }
+        return Uri.withAppendedPath(channelUri, Channels.Logo.CONTENT_DIRECTORY);
+    }
+
+    /**
      * Builds a URI that points to all browsable channels from a given TV input.
      *
      * @param name {@link ComponentName} of the {@link android.media.tv.TvInputService} that
@@ -523,6 +544,48 @@
         public static final String COLUMN_VERSION_NUMBER = "version_number";
 
         private Channels() {}
+
+        /**
+         * A sub-directory of a single TV channel that represents its primary logo.
+         * <p>
+         * To access this directory, append {@link Channels.Logo#CONTENT_DIRECTORY} to the raw
+         * channel URI.  The resulting URI represents an image file, and should be interacted
+         * using ContentResolver.openAssetFileDescriptor.
+         * </p>
+         * <p>
+         * Note that this sub-directory also supports opening the logo as an asset file in write
+         * mode.  Callers can create or replace the primary logo associated with this channel by
+         * opening the asset file and writing the full-size photo contents into it.  When the file
+         * is closed, the image will be parsed, sized down if necessary, and stored.
+         * </p>
+         * <p>
+         * Usage example:
+         * <pre>
+         * public void writeChannelLogo(long channelId, byte[] logo) {
+         *     Uri channelLogoUri = TvContract.buildChannelLogoUri(channelId);
+         *     try {
+         *         AssetFileDescriptor fd =
+         *             getContentResolver().openAssetFileDescriptor(channelLogoUri, "rw");
+         *         OutputStream os = fd.createOutputStream();
+         *         os.write(logo);
+         *         os.close();
+         *         fd.close();
+         *     } catch (IOException e) {
+         *         // Handle error cases.
+         *     }
+         * }
+         * </pre>
+         * </p>
+         */
+        public static final class Logo {
+
+            /**
+             * The directory twig for this sub-table.
+             */
+            public static final String CONTENT_DIRECTORY = "logo";
+
+            private Logo() {}
+        }
     }
 
     /** Column definitions for the TV programs table. */
@@ -631,6 +694,26 @@
         public static final String COLUMN_AUDIO_LANGUAGE = "audio_language";
 
         /**
+         * The URI for the poster art of this TV program.
+         * <p>
+         * Can be empty.
+         * </p><p>
+         * Type: TEXT
+         * </p>
+         */
+        public static final String COLUMN_POSTER_ART_URI = "poster_art_uri";
+
+        /**
+         * The URI for the thumbnail of this TV program.
+         * <p>
+         * Can be empty.
+         * </p><p>
+         * Type: TEXT
+         * </p>
+         */
+        public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
+
+        /**
          * Internal data used by individual TV input services.
          * <p>
          * This is internal to the provider that inserted it, and should not be decoded by other
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Recents.java b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
index 0cc09c8..116d755d 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
@@ -61,6 +61,11 @@
 
     @Override
     protected void onBootCompleted() {
+        if (mUseAlternateRecents) {
+            if (mAlternateRecents != null) {
+                mAlternateRecents.onBootCompleted();
+            }
+        }
         mBootCompleted = true;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index bb19415..2f6d58f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -153,6 +153,7 @@
     Messenger mService = null;
     Messenger mMessenger;
     RecentsMessageHandler mHandler;
+    boolean mBootCompleted = false;
     boolean mServiceIsBound = false;
     boolean mToggleRecentsUponServiceBound;
     RecentsServiceConnection mConnection = new RecentsServiceConnection();
@@ -182,6 +183,10 @@
         bindToRecentsService(false);
     }
 
+    public void onBootCompleted() {
+        mBootCompleted = true;
+    }
+
     /** Shows the recents */
     public void onShowRecents(boolean triggeredFromAltTab, View statusBarView) {
         if (Console.Enabled) {
@@ -208,7 +213,7 @@
         if (Console.Enabled) {
             Console.log(Constants.Log.App.RecentsComponent, "[RecentsComponent|hideRecents]");
         }
-        if (mServiceIsBound) {
+        if (mServiceIsBound && mBootCompleted) {
             // Notify recents to close it
             try {
                 Bundle data = new Bundle();
@@ -278,7 +283,7 @@
 
     /** Updates each of the task animation rects. */
     void updateAnimationRects() {
-        if (mServiceIsBound) {
+        if (mServiceIsBound && mBootCompleted) {
             Resources res = mContext.getResources();
             int statusBarHeight = res.getDimensionPixelSize(
                     com.android.internal.R.dimen.status_bar_height);
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 7926d03..34179cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -48,7 +48,6 @@
         View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
         KeyguardPageSwipeHelper.Callback {
 
-    private static final float EXPANSION_RUBBER_BAND_EXTRA_FACTOR = 0.6f;
     private static final float LOCK_ICON_ACTIVE_SCALE = 1.2f;
 
     private KeyguardPageSwipeHelper mPageSwiper;
@@ -719,6 +718,16 @@
         updateUnlockIcon();
     }
 
+    @Override
+    protected float getOverExpansionAmount() {
+        return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */);
+    }
+
+    @Override
+    protected float getOverExpansionPixels() {
+        return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */);
+    }
+
     private void updateUnlockIcon() {
         if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
                 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
@@ -805,14 +814,17 @@
     }
 
     @Override
-    protected void onOverExpansionChanged(float overExpansion) {
+    protected void setOverExpansion(float overExpansion, boolean isPixels) {
         if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
-            float currentOverScroll = mNotificationStackScroller.getCurrentOverScrolledPixels(true);
-            float expansionChange = overExpansion - mOverExpansion;
-            expansionChange *= EXPANSION_RUBBER_BAND_EXTRA_FACTOR;
-            mNotificationStackScroller.setOverScrolledPixels(currentOverScroll + expansionChange,
-                    true /* onTop */,
-                    false /* animate */);
+            mNotificationStackScroller.setOnHeightChangedListener(null);
+            if (isPixels) {
+                mNotificationStackScroller.setOverScrolledPixels(
+                        overExpansion, true /* onTop */, false /* animate */);
+            } else {
+                mNotificationStackScroller.setOverScrollAmount(
+                        overExpansion, true /* onTop */, false /* animate */);
+            }
+            mNotificationStackScroller.setOnHeightChangedListener(this);
         }
     }
 
@@ -828,7 +840,10 @@
     @Override
     protected void onTrackingStopped(boolean expand) {
         super.onTrackingStopped(expand);
-        mNotificationStackScroller.setOverScrolledPixels(0.0f, true /* onTop */, true /* animate */);
+        if (expand) {
+            mNotificationStackScroller.setOverScrolledPixels(
+                    0.0f, true /* onTop */, true /* animate */);
+        }
         if (expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD
                 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) {
             mPageSwiper.showAllIcons(true);
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 89a1907..772d0e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -41,7 +41,6 @@
 public abstract class PanelView extends FrameLayout {
     public static final boolean DEBUG = PanelBar.DEBUG;
     public static final String TAG = PanelView.class.getSimpleName();
-    protected float mOverExpansion;
 
     private final void logf(String fmt, Object... args) {
         Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
@@ -61,6 +60,7 @@
     private int mTrackingPointer;
     protected int mTouchSlop;
     protected boolean mHintAnimationRunning;
+    private boolean mOverExpandedBeforeFling;
 
     private ValueAnimator mHeightAnimator;
     private ObjectAnimator mPeekAnimator;
@@ -370,13 +370,12 @@
     protected void fling(float vel, boolean expand) {
         cancelPeek();
         float target = expand ? getMaxPanelHeight() : 0.0f;
-        if (target == mExpandedHeight || mOverExpansion > 0) {
+        if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) {
             onExpandingFinished();
-            mExpandedHeight = target;
-            mOverExpansion = 0.0f;
             mBar.panelExpansionChanged(this, mExpandedFraction);
             return;
         }
+        mOverExpandedBeforeFling = getOverExpansionAmount() > 0f;
         ValueAnimator animator = createHeightAnimator(target);
         if (expand) {
             mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, getHeight());
@@ -396,8 +395,8 @@
                 onExpandingFinished();
             }
         });
-        animator.start();
         mHeightAnimator = animator;
+        animator.start();
     }
 
     @Override
@@ -433,7 +432,7 @@
 
     public void setExpandedHeight(float height) {
         if (DEBUG) logf("setExpandedHeight(%.1f)", height);
-        setExpandedHeightInternal(height);
+        setExpandedHeightInternal(height + getOverExpansionPixels());
         mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
     }
 
@@ -451,32 +450,39 @@
         // If the user isn't actively poking us, let's update the height
         if (!mTracking && mHeightAnimator == null
                 && mExpandedHeight > 0 && currentMaxPanelHeight != mExpandedHeight) {
-            setExpandedHeightInternal(currentMaxPanelHeight);
+            setExpandedHeight(currentMaxPanelHeight);
         }
     }
 
     public void setExpandedHeightInternal(float h) {
-        float fh = getMaxPanelHeight();
-        mExpandedHeight = Math.max(0, Math.min(fh, h));
-        float overExpansion = h - fh;
-        overExpansion = Math.max(0, overExpansion);
-        if (overExpansion != mOverExpansion) {
-            onOverExpansionChanged(overExpansion);
-            mOverExpansion = overExpansion;
-        }
-
-        if (DEBUG) {
-            logf("setExpansion: height=%.1f fh=%.1f tracking=%s", h, fh, mTracking ? "T" : "f");
+        float fhWithoutOverExpansion = getMaxPanelHeight() - getOverExpansionAmount();
+        if (mHeightAnimator == null) {
+            float overExpansionPixels = Math.max(0, h - fhWithoutOverExpansion);
+            if (getOverExpansionPixels() != overExpansionPixels && mTracking) {
+                setOverExpansion(overExpansionPixels, true /* isPixels */);
+            }
+            mExpandedHeight = Math.min(h, fhWithoutOverExpansion) + getOverExpansionAmount();
+        } else {
+            mExpandedHeight = h;
+            if (mOverExpandedBeforeFling) {
+                setOverExpansion(Math.max(0, h - fhWithoutOverExpansion), false /* isPixels */);
+            }
         }
 
         onHeightUpdated(mExpandedHeight);
-        mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : mExpandedHeight / fh);
+        mExpandedFraction = Math.min(1f, fhWithoutOverExpansion == 0
+                ? 0
+                : mExpandedHeight / fhWithoutOverExpansion);
     }
 
-    protected abstract void onOverExpansionChanged(float overExpansion);
+    protected abstract void setOverExpansion(float overExpansion, boolean isPixels);
 
     protected abstract void onHeightUpdated(float expandedHeight);
 
+    protected abstract float getOverExpansionAmount();
+
+    protected abstract float getOverExpansionPixels();
+
     /**
      * This returns the maximum height of the panel. Children should override this if their
      * desired height is not the full height.
@@ -624,7 +630,8 @@
         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {
-                setExpandedHeight((Float) animation.getAnimatedValue());
+                setExpandedHeightInternal((Float) animation.getAnimatedValue());
+                mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
             }
         });
         return animator;
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 166401f..227304c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -2853,7 +2853,9 @@
     }
 
     private void updatePublicMode() {
-        setLockscreenPublicMode(mState == StatusBarState.KEYGUARD
+        setLockscreenPublicMode(
+                (mStatusBarKeyguardViewManager.isShowing() || 
+                    mStatusBarKeyguardViewManager.isOccluded())
                 && mStatusBarKeyguardViewManager.isSecure());
     }
 
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 e3145a6..09e4d94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -181,6 +181,10 @@
         reset();
     }
 
+    public boolean isOccluded() {
+        return mOccluded;
+    }
+
     /**
      * Hides the keyguard view
      */
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 0383706..4d86213 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -52,6 +52,7 @@
     private static final boolean DEBUG = false;
     private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
     private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
+    private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
 
     /**
      * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
@@ -1267,11 +1268,14 @@
     }
 
     private float getRubberBandFactor() {
-        return mExpandedInThisMotion
-                ? RUBBER_BAND_FACTOR_AFTER_EXPAND
-                : (mScrolledToTopOnFirstDown
-                    ? 1.0f
-                    : RUBBER_BAND_FACTOR_NORMAL);
+        if (mExpandedInThisMotion) {
+            return RUBBER_BAND_FACTOR_AFTER_EXPAND;
+        } else if (mIsExpansionChanging) {
+            return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
+        } else if (mScrolledToTopOnFirstDown) {
+            return 1.0f;
+        }
+        return RUBBER_BAND_FACTOR_NORMAL;
     }
 
     private void endDrag() {
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 2edd7d1..225398a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -726,11 +726,21 @@
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {
                 float currentOverScroll = (float) animation.getAnimatedValue();
-                mHostLayout.setOverScrollAmount(currentOverScroll, onTop, false /* animate */,
-                        false /* cancelAnimators */);
+                mHostLayout.setOverScrollAmount(
+                        currentOverScroll, onTop, false /* animate */, false /* cancelAnimators */);
             }
         });
         overScrollAnimator.setInterpolator(mFastOutSlowInInterpolator);
+        overScrollAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (onTop) {
+                    mTopOverScrollAnimator = null;
+                } else {
+                    mBottomOverScrollAnimator = null;
+                }
+            }
+        });
         overScrollAnimator.start();
         if (onTop) {
             mTopOverScrollAnimator = overScrollAnimator;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 495db20..386402b 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -163,6 +163,7 @@
     private long[] mDefaultVibrationPattern;
 
     private long[] mFallbackVibrationPattern;
+    private boolean mUseAttentionLight;
     boolean mSystemReady;
 
     private boolean mDisableNotificationAlerts;
@@ -182,7 +183,7 @@
             new ArrayMap<String, NotificationRecord>();
     final ArrayList<ToastRecord> mToastQueue = new ArrayList<ToastRecord>();
 
-    ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>();
+    ArrayList<String> mLights = new ArrayList<String>();
     NotificationRecord mLedNotification;
 
     private AppOpsManager mAppOps;
@@ -797,6 +798,8 @@
                 VIBRATE_PATTERN_MAXLEN,
                 DEFAULT_VIBRATE_PATTERN);
 
+        mUseAttentionLight = resources.getBoolean(R.bool.config_useAttentionLight);
+
         // Don't start allowing notifications until the setup wizard has run once.
         // After that, including subsequent boots, init with notifications turned on.
         // This works on the first boot because the setup wizard will toggle this
@@ -1478,14 +1481,14 @@
                     }
                 }
 
-                // 1. initial score: buckets of 10, around the app
-                int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER; //[-20..20]
+                // 1. initial score: buckets of 10, around the app [-20..20]
+                final int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER;
 
                 // 2. extract ranking signals from the notification data
                 final StatusBarNotification n = new StatusBarNotification(
                         pkg, opPkg, id, tag, callingUid, callingPid, score, notification,
                         user);
-                NotificationRecord r = new NotificationRecord(n);
+                NotificationRecord r = new NotificationRecord(n, score);
                 NotificationRecord old = mNotificationsByKey.get(n.getKey());
                 if (old != null) {
                     // Retain ranking information from previous record
@@ -1507,13 +1510,13 @@
                 // blocked apps
                 if (ENABLE_BLOCKED_NOTIFICATIONS && !noteNotificationOp(pkg, callingUid)) {
                     if (!isSystemNotification) {
-                        score = JUNK_SCORE;
+                        r.score = JUNK_SCORE;
                         Slog.e(TAG, "Suppressing notification from package " + pkg
                                 + " by user request.");
                     }
                 }
 
-                if (score < SCORE_DISPLAY_THRESHOLD) {
+                if (r.score < SCORE_DISPLAY_THRESHOLD) {
                     // Notification will be blocked because the score is too low.
                     return;
                 }
@@ -1529,28 +1532,16 @@
                         mUsageStats.registerUpdatedByApp(r, old);
                         // Make sure we don't lose the foreground service state.
                         notification.flags |=
-                            old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE;
+                                old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE;
                         mNotificationsByKey.remove(old.sbn.getKey());
+                        r.isUpdate = true;
                     }
                     mNotificationsByKey.put(n.getKey(), r);
 
                     applyZenModeLocked(r);
-                    // Should this notification make noise, vibe, or use the LED?
-                    final boolean canInterrupt = (score >= SCORE_INTERRUPTION_THRESHOLD) &&
-                            !r.isIntercepted();
-                    if (DBG || r.isIntercepted()) Slog.v(TAG,
-                            "pkg=" + pkg + " canInterrupt=" + canInterrupt +
-                                    " intercept=" + r.isIntercepted());
 
                     Collections.sort(mNotificationList, mRankingComparator);
 
-                    // Ensure if this is a foreground service that the proper additional
-                    // flags are set.
-                    if ((notification.flags&Notification.FLAG_FOREGROUND_SERVICE) != 0) {
-                        notification.flags |= Notification.FLAG_ONGOING_EVENT
-                                | Notification.FLAG_NO_CLEAR;
-                    }
-
                     final int currentUser;
                     final long token = Binder.clearCallingIdentity();
                     try {
@@ -1571,20 +1562,11 @@
                             final long identity = Binder.clearCallingIdentity();
                             try {
                                 mStatusBar.addNotification(n);
-                                if ((n.getNotification().flags & Notification.FLAG_SHOW_LIGHTS) != 0
-                                        && canInterrupt) {
-                                    mAttentionLight.pulse();
-                                }
                             } finally {
                                 Binder.restoreCallingIdentity(identity);
                             }
                         }
-                        // Send accessibility events only for the current user.
-                        if (currentUser == userId) {
-                            sendAccessibilityEvent(notification, pkg);
-                        }
-
-                        mListeners.notifyPostedLocked(r.sbn);
+                        mListeners.notifyPostedLocked(n);
                     } else {
                         Slog.e(TAG, "Not posting notification with icon==0: " + notification);
                         if (old != null && !old.isCanceled) {
@@ -1595,7 +1577,7 @@
                                 Binder.restoreCallingIdentity(identity);
                             }
 
-                            mListeners.notifyRemovedLocked(r.sbn);
+                            mListeners.notifyRemovedLocked(n);
                         }
                         // ATTENTION: in a future release we will bail out here
                         // so that we do not play sounds, show lights, etc. for invalid
@@ -1604,136 +1586,14 @@
                                 + n.getPackageName());
                     }
 
-                    // If we're not supposed to beep, vibrate, etc. then don't.
-                    if (!mDisableNotificationAlerts
-                            && (!(old != null
-                                && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
-                            && (r.getUserId() == UserHandle.USER_ALL ||
-                                (r.getUserId() == userId && r.getUserId() == currentUser) ||
-                                mUserProfiles.isCurrentProfile(r.getUserId()))
-                            && canInterrupt
-                            && mSystemReady
-                            && mAudioManager != null) {
-                        if (DBG) Slog.v(TAG, "Interrupting!");
-                        // sound
-
-                        // should we use the default notification sound? (indicated either by
-                        // DEFAULT_SOUND or because notification.sound is pointing at
-                        // Settings.System.NOTIFICATION_SOUND)
-                        final boolean useDefaultSound =
-                               (notification.defaults & Notification.DEFAULT_SOUND) != 0 ||
-                                       Settings.System.DEFAULT_NOTIFICATION_URI
-                                               .equals(notification.sound);
-
-                        Uri soundUri = null;
-                        boolean hasValidSound = false;
-
-                        if (useDefaultSound) {
-                            soundUri = Settings.System.DEFAULT_NOTIFICATION_URI;
-
-                            // check to see if the default notification sound is silent
-                            ContentResolver resolver = getContext().getContentResolver();
-                            hasValidSound = Settings.System.getString(resolver,
-                                   Settings.System.NOTIFICATION_SOUND) != null;
-                        } else if (notification.sound != null) {
-                            soundUri = notification.sound;
-                            hasValidSound = (soundUri != null);
-                        }
-
-                        if (hasValidSound) {
-                            boolean looping =
-                                    (notification.flags & Notification.FLAG_INSISTENT) != 0;
-                            int audioStreamType;
-                            if (notification.audioStreamType >= 0) {
-                                audioStreamType = notification.audioStreamType;
-                            } else {
-                                audioStreamType = DEFAULT_STREAM_TYPE;
-                            }
-                            mSoundNotification = r;
-                            // do not play notifications if stream volume is 0 (typically because
-                            // ringer mode is silent) or if there is a user of exclusive audio focus
-                            if ((mAudioManager.getStreamVolume(audioStreamType) != 0)
-                                    && !mAudioManager.isAudioFocusExclusive()) {
-                                final long identity = Binder.clearCallingIdentity();
-                                try {
-                                    final IRingtonePlayer player =
-                                            mAudioManager.getRingtonePlayer();
-                                    if (player != null) {
-                                        if (DBG) Slog.v(TAG, "Playing sound " + soundUri
-                                                + " on stream " + audioStreamType);
-                                        player.playAsync(soundUri, user, looping, audioStreamType);
-                                    }
-                                } catch (RemoteException e) {
-                                } finally {
-                                    Binder.restoreCallingIdentity(identity);
-                                }
-                            }
-                        }
-
-                        // vibrate
-                        // Does the notification want to specify its own vibration?
-                        final boolean hasCustomVibrate = notification.vibrate != null;
-
-                        // new in 4.2: if there was supposed to be a sound and we're in vibrate
-                        // mode, and no other vibration is specified, we fall back to vibration
-                        final boolean convertSoundToVibration =
-                                   !hasCustomVibrate
-                                && hasValidSound
-                                && (mAudioManager.getRingerMode()
-                                           == AudioManager.RINGER_MODE_VIBRATE);
-
-                        // The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback.
-                        final boolean useDefaultVibrate =
-                                (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
-
-                        if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate)
-                                && !(mAudioManager.getRingerMode()
-                                        == AudioManager.RINGER_MODE_SILENT)) {
-                            mVibrateNotification = r;
-
-                            if (useDefaultVibrate || convertSoundToVibration) {
-                                // Escalate privileges so we can use the vibrator even if the
-                                // notifying app does not have the VIBRATE permission.
-                                long identity = Binder.clearCallingIdentity();
-                                try {
-                                    mVibrator.vibrate(r.sbn.getUid(), r.sbn.getOpPkg(),
-                                        useDefaultVibrate ? mDefaultVibrationPattern
-                                            : mFallbackVibrationPattern,
-                                        ((notification.flags & Notification.FLAG_INSISTENT) != 0)
-                                                ? 0: -1, notification.audioStreamType);
-                                } finally {
-                                    Binder.restoreCallingIdentity(identity);
-                                }
-                            } else if (notification.vibrate.length > 1) {
-                                // If you want your own vibration pattern, you need the VIBRATE
-                                // permission
-                                mVibrator.vibrate(r.sbn.getUid(), r.sbn.getOpPkg(),
-                                        notification.vibrate,
-                                    ((notification.flags & Notification.FLAG_INSISTENT) != 0)
-                                            ? 0: -1, notification.audioStreamType);
-                            }
-                        }
+                    // Ensure if this is a foreground service that the proper additional
+                    // flags are set.
+                    if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+                        notification.flags |= Notification.FLAG_ONGOING_EVENT
+                                | Notification.FLAG_NO_CLEAR;
                     }
 
-                    // light
-                    // the most recent thing gets the light
-                    mLights.remove(old);
-                    if (mLedNotification == old) {
-                        mLedNotification = null;
-                    }
-                    //Slog.i(TAG, "notification.lights="
-                    //        + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS)
-                    //                  != 0));
-                    if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0
-                            && canInterrupt) {
-                        mLights.add(r);
-                        updateLightsLocked();
-                    } else {
-                        if (old != null
-                                && ((old.getFlags() & Notification.FLAG_SHOW_LIGHTS) != 0)) {
-                            updateLightsLocked();
-                        }
-                    }
+                    buzzBeepBlinkLocked(r);
                 }
             }
         });
@@ -1741,6 +1601,158 @@
         idOut[0] = id;
     }
 
+    private void buzzBeepBlinkLocked(NotificationRecord record) {
+        final Notification notification = record.sbn.getNotification();
+
+        // Should this notification make noise, vibe, or use the LED?
+        final boolean canInterrupt = (record.score >= SCORE_INTERRUPTION_THRESHOLD) &&
+                !record.isIntercepted();
+        if (DBG || record.isIntercepted())
+            Slog.v(TAG,
+                    "pkg=" + record.sbn.getPackageName() + " canInterrupt=" + canInterrupt +
+                            " intercept=" + record.isIntercepted()
+            );
+
+        final int currentUser;
+        final long token = Binder.clearCallingIdentity();
+        try {
+            currentUser = ActivityManager.getCurrentUser();
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+
+        // If we're not supposed to beep, vibrate, etc. then don't.
+        if (!mDisableNotificationAlerts
+                && (!(record.isUpdate
+                    && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
+                && (record.getUserId() == UserHandle.USER_ALL ||
+                    record.getUserId() == currentUser ||
+                    mUserProfiles.isCurrentProfile(record.getUserId()))
+                && canInterrupt
+                && mSystemReady
+                && mAudioManager != null) {
+            if (DBG) Slog.v(TAG, "Interrupting!");
+
+            sendAccessibilityEvent(notification, record.sbn.getPackageName());
+
+            // sound
+
+            // should we use the default notification sound? (indicated either by
+            // DEFAULT_SOUND or because notification.sound is pointing at
+            // Settings.System.NOTIFICATION_SOUND)
+            final boolean useDefaultSound =
+                   (notification.defaults & Notification.DEFAULT_SOUND) != 0 ||
+                           Settings.System.DEFAULT_NOTIFICATION_URI
+                                   .equals(notification.sound);
+
+            Uri soundUri = null;
+            boolean hasValidSound = false;
+
+            if (useDefaultSound) {
+                soundUri = Settings.System.DEFAULT_NOTIFICATION_URI;
+
+                // check to see if the default notification sound is silent
+                ContentResolver resolver = getContext().getContentResolver();
+                hasValidSound = Settings.System.getString(resolver,
+                       Settings.System.NOTIFICATION_SOUND) != null;
+            } else if (notification.sound != null) {
+                soundUri = notification.sound;
+                hasValidSound = (soundUri != null);
+            }
+
+            if (hasValidSound) {
+                boolean looping =
+                        (notification.flags & Notification.FLAG_INSISTENT) != 0;
+                int audioStreamType;
+                if (notification.audioStreamType >= 0) {
+                    audioStreamType = notification.audioStreamType;
+                } else {
+                    audioStreamType = DEFAULT_STREAM_TYPE;
+                }
+                mSoundNotification = record;
+                // do not play notifications if stream volume is 0 (typically because
+                // ringer mode is silent) or if there is a user of exclusive audio focus
+                if ((mAudioManager.getStreamVolume(audioStreamType) != 0)
+                        && !mAudioManager.isAudioFocusExclusive()) {
+                    final long identity = Binder.clearCallingIdentity();
+                    try {
+                        final IRingtonePlayer player =
+                                mAudioManager.getRingtonePlayer();
+                        if (player != null) {
+                            if (DBG) Slog.v(TAG, "Playing sound " + soundUri
+                                    + " on stream " + audioStreamType);
+                            player.playAsync(soundUri, record.sbn.getUser(), looping,
+                                    audioStreamType);
+                        }
+                    } catch (RemoteException e) {
+                    } finally {
+                        Binder.restoreCallingIdentity(identity);
+                    }
+                }
+            }
+
+            // vibrate
+            // Does the notification want to specify its own vibration?
+            final boolean hasCustomVibrate = notification.vibrate != null;
+
+            // new in 4.2: if there was supposed to be a sound and we're in vibrate
+            // mode, and no other vibration is specified, we fall back to vibration
+            final boolean convertSoundToVibration =
+                       !hasCustomVibrate
+                    && hasValidSound
+                    && (mAudioManager.getRingerMode()
+                               == AudioManager.RINGER_MODE_VIBRATE);
+
+            // The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback.
+            final boolean useDefaultVibrate =
+                    (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
+
+            if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate)
+                    && !(mAudioManager.getRingerMode()
+                            == AudioManager.RINGER_MODE_SILENT)) {
+                mVibrateNotification = record;
+
+                if (useDefaultVibrate || convertSoundToVibration) {
+                    // Escalate privileges so we can use the vibrator even if the
+                    // notifying app does not have the VIBRATE permission.
+                    long identity = Binder.clearCallingIdentity();
+                    try {
+                        mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
+                            useDefaultVibrate ? mDefaultVibrationPattern
+                                : mFallbackVibrationPattern,
+                            ((notification.flags & Notification.FLAG_INSISTENT) != 0)
+                                    ? 0: -1, notification.audioStreamType);
+                    } finally {
+                        Binder.restoreCallingIdentity(identity);
+                    }
+                } else if (notification.vibrate.length > 1) {
+                    // If you want your own vibration pattern, you need the VIBRATE
+                    // permission
+                    mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
+                            notification.vibrate,
+                        ((notification.flags & Notification.FLAG_INSISTENT) != 0)
+                                ? 0: -1, notification.audioStreamType);
+                }
+            }
+        }
+
+        // light
+        // release the light
+        boolean wasShowLights = mLights.remove(record.getKey());
+        if (mLedNotification != null && record.getKey().equals(mLedNotification.getKey())) {
+            mLedNotification = null;
+        }
+        if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && canInterrupt) {
+            mLights.add(record.getKey());
+            updateLightsLocked();
+            if (mUseAttentionLight) {
+                mAttentionLight.pulse();
+            }
+        } else if (wasShowLights) {
+            updateLightsLocked();
+        }
+    }
+
     void showNextToastLocked() {
         ToastRecord record = mToastQueue.get(0);
         while (record != null) {
@@ -1866,6 +1878,9 @@
             int indexAfter = findNotificationRecordIndexLocked(record);
             boolean interceptAfter = record.isIntercepted();
             changed = indexBefore != indexAfter || interceptBefore != interceptAfter;
+            if (interceptBefore && !interceptAfter) {
+                buzzBeepBlinkLocked(record);
+            }
         }
         if (changed) {
             scheduleSendRankingUpdate();
@@ -2011,7 +2026,7 @@
         }
 
         // light
-        mLights.remove(r);
+        mLights.remove(r.getKey());
         if (mLedNotification == r) {
             mLedNotification = null;
         }
@@ -2196,7 +2211,7 @@
             // get next notification, if any
             int n = mLights.size();
             if (n > 0) {
-                mLedNotification = mLights.get(n-1);
+                mLedNotification = mNotificationsByKey.get(mLights.get(n-1));
             }
         }
 
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 08f8eb4..30d4fec 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -42,6 +42,7 @@
     final StatusBarNotification sbn;
     NotificationUsageStats.SingleNotificationStats stats;
     boolean isCanceled;
+    int score;
 
     // These members are used by NotificationSignalExtractors
     // to communicate with the ranking module.
@@ -53,9 +54,13 @@
     // InterceptedNotifications needs to know if this has been previously evaluated.
     private boolean mTouchedByZen;
 
-    NotificationRecord(StatusBarNotification sbn)
+    // Is this record an update of an old record?
+    public boolean isUpdate;
+
+    NotificationRecord(StatusBarNotification sbn, int score)
     {
         this.sbn = sbn;
+        this.score = score;
     }
 
     // copy any notes that the ranking system may have made before the update
diff --git a/services/core/java/com/android/server/task/TaskManagerService.java b/services/core/java/com/android/server/task/TaskManagerService.java
index d5b70e6..a5f865f 100644
--- a/services/core/java/com/android/server/task/TaskManagerService.java
+++ b/services/core/java/com/android/server/task/TaskManagerService.java
@@ -36,6 +36,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.server.task.controllers.BatteryController;
 import com.android.server.task.controllers.ConnectivityController;
 import com.android.server.task.controllers.IdleController;
 import com.android.server.task.controllers.StateController;
@@ -48,12 +49,19 @@
  * 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
  * endpoint.
+ * Implements logic for scheduling, and rescheduling tasks. The TaskManagerService knows nothing
+ * about constraints, or the state of active tasks. It receives callbacks from the various
+ * controllers and completed tasks and operates accordingly.
+ *
+ * Note on locking: Any operations that manipulate {@link #mTasks} need to lock on that object, and
+ * similarly for {@link #mActiveServices}. If both locks need to be held take mTasksSet first and then
+ * mActiveService afterwards.
  * @hide
  */
 public class TaskManagerService extends com.android.server.SystemService
-        implements StateChangedListener, TaskCompletedListener {
+        implements StateChangedListener, TaskCompletedListener, TaskMapReadFinishedListener {
     // TODO: Switch this off for final version.
-    private static final boolean DEBUG = true;
+    static final boolean DEBUG = true;
     /** The number of concurrent tasks we run at one time. */
     private static final int MAX_TASK_CONTEXTS_COUNT = 3;
     static final String TAG = "TaskManager";
@@ -113,8 +121,8 @@
      */
     public int schedule(Task task, int uId, boolean canPersistTask) {
         TaskStatus taskStatus = new TaskStatus(task, uId, canPersistTask);
-        return startTrackingTask(taskStatus) ?
-                TaskManager.RESULT_SUCCESS : TaskManager.RESULT_FAILURE;
+        startTrackingTask(taskStatus);
+        return TaskManager.RESULT_SUCCESS;
     }
 
     public List<Task> getPendingTasks(int uid) {
@@ -210,7 +218,7 @@
      */
     public TaskManagerService(Context context) {
         super(context);
-        mTasks = new TaskStore(context);
+        mTasks = TaskStore.initAndGet(this);
         mHandler = new TaskHandler(context.getMainLooper());
         mTaskManagerStub = new TaskManagerStub();
         // Create the "runners".
@@ -218,12 +226,12 @@
             mActiveServices.add(
                     new TaskServiceContext(this, context.getMainLooper()));
         }
-
+        // Create the controllers.
         mControllers = new LinkedList<StateController>();
         mControllers.add(ConnectivityController.get(this));
         mControllers.add(TimeController.get(this));
         mControllers.add(IdleController.get(this));
-        // TODO: Add BatteryStateController when implemented.
+        mControllers.add(BatteryController.get(this));
     }
 
     @Override
@@ -236,17 +244,14 @@
      * {@link com.android.server.task.TaskStore}, and make sure all the relevant controllers know
      * about.
      */
-    private boolean startTrackingTask(TaskStatus taskStatus) {
-        boolean added = false;
+    private void startTrackingTask(TaskStatus taskStatus) {
         synchronized (mTasks) {
-            added = mTasks.add(taskStatus);
+            mTasks.add(taskStatus);
         }
-        if (added) {
-            for (StateController controller : mControllers) {
-                controller.maybeStartTrackingTask(taskStatus);
-            }
+        for (StateController controller : mControllers) {
+            controller.maybeStartTrackingTask(taskStatus);
+
         }
-        return added;
     }
 
     /**
@@ -404,6 +409,27 @@
         mHandler.obtainMessage(MSG_TASK_EXPIRED, taskStatus);
     }
 
+    /**
+     * Disk I/O is finished, take the list of tasks we read from disk and add them to our
+     * {@link TaskStore}.
+     * This is run on the {@link com.android.server.IoThread} instance, which is a separate thread,
+     * and is called once at boot.
+     */
+    @Override
+    public void onTaskMapReadFinished(List<TaskStatus> tasks) {
+        synchronized (mTasks) {
+            for (TaskStatus ts : tasks) {
+                if (mTasks.contains(ts)) {
+                    // An app with BOOT_COMPLETED *might* have decided to reschedule their task, in
+                    // the same amount of time it took us to read it from disk. If this is the case
+                    // we leave it be.
+                    continue;
+                }
+                startTrackingTask(ts);
+            }
+        }
+    }
+
     private class TaskHandler extends Handler {
 
         public TaskHandler(Looper looper) {
diff --git a/services/core/java/com/android/server/task/TaskMapReadFinishedListener.java b/services/core/java/com/android/server/task/TaskMapReadFinishedListener.java
new file mode 100644
index 0000000..c68d8db
--- /dev/null
+++ b/services/core/java/com/android/server/task/TaskMapReadFinishedListener.java
@@ -0,0 +1,34 @@
+/*
+ * 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 java.util.List;
+
+import com.android.server.task.controllers.TaskStatus;
+
+/**
+ * Callback definition for I/O thread to let the TaskManagerService know when
+ * I/O read has completed. Done this way so we don't stall the main thread on
+ * boot.
+ */
+public interface TaskMapReadFinishedListener {
+
+    /**
+     * Called by the {@link TaskStore} at boot, when the disk read is finished.
+     */
+    public void onTaskMapReadFinished(List<TaskStatus> tasks);
+}
diff --git a/services/core/java/com/android/server/task/TaskStore.java b/services/core/java/com/android/server/task/TaskStore.java
index f72ab22..6bb00b1 100644
--- a/services/core/java/com/android/server/task/TaskStore.java
+++ b/services/core/java/com/android/server/task/TaskStore.java
@@ -16,17 +16,37 @@
 
 package com.android.server.task;
 
+import android.content.ComponentName;
 import android.app.task.Task;
 import android.content.Context;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.PersistableBundle;
+import android.os.SystemClock;
+import android.util.AtomicFile;
 import android.util.ArraySet;
+import android.util.Pair;
 import android.util.Slog;
-import android.util.SparseArray;
+import android.util.Xml;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.server.IoThread;
 import com.android.server.task.controllers.TaskStatus;
 
-import java.util.HashSet;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Iterator;
-import java.util.Set;
+import java.util.List;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
 
 /**
  * Maintain a list of classes, and accessor methods/logic for these tasks.
@@ -38,57 +58,108 @@
  *     - 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.
+ *       - This class <strong>is not</strong> thread-safe.
+ *
+ * Note on locking:
+ *      All callers to this class must <strong>lock on the class object they are calling</strong>.
+ *      This is important b/c {@link com.android.server.task.TaskStore.WriteTasksMapToDiskRunnable}
+ *      and {@link com.android.server.task.TaskStore.ReadTaskMapFromDiskRunnable} lock on that
+ *      object.
  */
 public class TaskStore {
     private static final String TAG = "TaskManagerStore";
+    private static final boolean DEBUG = TaskManagerService.DEBUG;
+
     /** Threshold to adjust how often we want to write to the db. */
     private static final int MAX_OPS_BEFORE_WRITE = 1;
-    final ArraySet<TaskStatus> mTasks;
+    final ArraySet<TaskStatus> mTasksSet;
     final Context mContext;
 
     private int mDirtyOperations;
 
-    TaskStore(Context context) {
-        mTasks = intialiseTasksFromDisk();
+    private static final Object sSingletonLock = new Object();
+    private final AtomicFile mTasksFile;
+    /** Handler backed by IoThread for writing to disk. */
+    private final Handler mIoHandler = IoThread.getHandler();
+    private static TaskStore sSingleton;
+
+    /** Used by the {@Link TaskManagerService} to instantiate the TaskStore. */
+    static TaskStore initAndGet(TaskManagerService taskManagerService) {
+        synchronized (sSingletonLock) {
+            if (sSingleton == null) {
+                sSingleton = new TaskStore(taskManagerService.getContext(),
+                        Environment.getDataDirectory(), taskManagerService);
+            }
+            return sSingleton;
+        }
+    }
+
+    @VisibleForTesting
+    public static TaskStore initAndGetForTesting(Context context, File dataDir,
+                                          TaskMapReadFinishedListener callback) {
+        return new TaskStore(context, dataDir, callback);
+    }
+
+    private TaskStore(Context context, File dataDir, TaskMapReadFinishedListener callback) {
         mContext = context;
         mDirtyOperations = 0;
+
+        File systemDir = new File(dataDir, "system");
+        File taskDir = new File(systemDir, "task");
+        taskDir.mkdirs();
+        mTasksFile = new AtomicFile(new File(taskDir, "tasks.xml"));
+
+        mTasksSet = new ArraySet<TaskStatus>();
+
+        readTaskMapFromDiskAsync(callback);
     }
 
     /**
      * Add a task to the master list, persisting it if necessary. If the TaskStatus already exists,
      * it will be replaced.
      * @param taskStatus Task to add.
-     * @return true if the operation succeeded.
+     * @return Whether or not an equivalent TaskStatus was replaced by this operation.
      */
     public boolean add(TaskStatus taskStatus) {
+        boolean replaced = mTasksSet.remove(taskStatus);
+        mTasksSet.add(taskStatus);
         if (taskStatus.isPersisted()) {
-            if (!maybeWriteStatusToDisk()) {
-                return false;
-            }
+            maybeWriteStatusToDiskAsync();
         }
-        mTasks.remove(taskStatus);
-        mTasks.add(taskStatus);
-        return true;
+        return replaced;
+    }
+
+    /**
+     * Whether this taskStatus object already exists in the TaskStore.
+     */
+    public boolean contains(TaskStatus taskStatus) {
+        return mTasksSet.contains(taskStatus);
     }
 
     public int size() {
-        return mTasks.size();
+        return mTasksSet.size();
     }
 
     /**
      * Remove the provided task. Will also delete the task if it was persisted.
-     * @return The TaskStatus that was removed, or null if an invalid token was provided.
+     * @return Whether or not the task existed to be removed.
      */
     public boolean remove(TaskStatus taskStatus) {
-        boolean removed = mTasks.remove(taskStatus);
+        boolean removed = mTasksSet.remove(taskStatus);
         if (!removed) {
-            Slog.e(TAG, "Error removing task: " + taskStatus);
+            if (DEBUG) {
+                Slog.d(TAG, "Couldn't remove task: didn't exist: " + taskStatus);
+            }
             return false;
-        } else {
-            maybeWriteStatusToDisk();
         }
-        return true;
+        maybeWriteStatusToDiskAsync();
+        return removed;
+    }
+
+    @VisibleForTesting
+    public void clear() {
+        mTasksSet.clear();
+        maybeWriteStatusToDiskAsync();
     }
 
     /**
@@ -100,19 +171,16 @@
      * was found.
      */
     public boolean removeAllByUid(int uid) {
-        Iterator<TaskStatus> it = mTasks.iterator();
-        boolean removed = false;
+        Iterator<TaskStatus> it = mTasksSet.iterator();
         while (it.hasNext()) {
             TaskStatus ts = it.next();
             if (ts.getUid() == uid) {
                 it.remove();
-                removed = true;
+                maybeWriteStatusToDiskAsync();
+                return true;
             }
         }
-        if (removed) {
-            maybeWriteStatusToDisk();
-        }
-        return removed;
+        return false;
     }
 
     /**
@@ -124,48 +192,464 @@
      * @return true if a removal occurred, false if the provided parameters didn't match anything.
      */
     public boolean remove(int uid, int taskId) {
-        Iterator<TaskStatus> it = mTasks.iterator();
+        boolean changed = false;
+        Iterator<TaskStatus> it = mTasksSet.iterator();
         while (it.hasNext()) {
             TaskStatus ts = it.next();
             if (ts.getUid() == uid && ts.getTaskId() == taskId) {
                 it.remove();
-                maybeWriteStatusToDisk();
-                return true;
+                changed = true;
             }
         }
-        return false;
+        if (changed) {
+            maybeWriteStatusToDiskAsync();
+        }
+        return changed;
     }
 
     /**
      * @return The live array of TaskStatus objects.
      */
-    public Set<TaskStatus> getTasks() {
-        return mTasks;
+    public ArraySet<TaskStatus> getTasks() {
+        return mTasksSet;
     }
 
+    /** Version of the db schema. */
+    private static final int TASKS_FILE_VERSION = 0;
+    /** Tag corresponds to constraints this task needs. */
+    private static final String XML_TAG_PARAMS_CONSTRAINTS = "constraints";
+    /** Tag corresponds to execution parameters. */
+    private static final String XML_TAG_PERIODIC = "periodic";
+    private static final String XML_TAG_ONEOFF = "one-off";
+    private static final String XML_TAG_EXTRAS = "extras";
+
     /**
      * Every time the state changes we write all the tasks in one swathe, instead of trying to
      * track incremental changes.
      * @return Whether the operation was successful. This will only fail for e.g. if the system is
      * low on storage. If this happens, we continue as normal
      */
-    private boolean maybeWriteStatusToDisk() {
+    private void maybeWriteStatusToDiskAsync() {
         mDirtyOperations++;
-        if (mDirtyOperations > MAX_OPS_BEFORE_WRITE) {
-            for (TaskStatus ts : mTasks) {
-                //
+        if (mDirtyOperations >= MAX_OPS_BEFORE_WRITE) {
+            if (DEBUG) {
+                Slog.v(TAG, "Writing tasks to disk.");
             }
-            mDirtyOperations = 0;
+            mIoHandler.post(new WriteTasksMapToDiskRunnable());
         }
-        return true;
+    }
+
+    private void readTaskMapFromDiskAsync(TaskMapReadFinishedListener callback) {
+        mIoHandler.post(new ReadTaskMapFromDiskRunnable(callback));
+    }
+
+    public void readTaskMapFromDisk(TaskMapReadFinishedListener callback) {
+        new ReadTaskMapFromDiskRunnable(callback).run();
     }
 
     /**
-     *
-     * @return
+     * Runnable that writes {@link #mTasksSet} out to xml.
+     * NOTE: This Runnable locks on TaskStore.this
      */
-    // TODO: Implement this.
-    private ArraySet<TaskStatus> intialiseTasksFromDisk() {
-        return new ArraySet<TaskStatus>();
+    private class WriteTasksMapToDiskRunnable implements Runnable {
+        @Override
+        public void run() {
+            final long startElapsed = SystemClock.elapsedRealtime();
+            synchronized (TaskStore.this) {
+                writeTasksMapImpl();
+            }
+            if (TaskManagerService.DEBUG) {
+                Slog.v(TAG, "Finished writing, took " + (SystemClock.elapsedRealtime()
+                        - startElapsed) + "ms");
+            }
+        }
+
+        private void writeTasksMapImpl() {
+            try {
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                XmlSerializer out = new FastXmlSerializer();
+                out.setOutput(baos, "utf-8");
+                out.startDocument(null, true);
+                out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+                out.startTag(null, "task-info");
+                out.attribute(null, "version", Integer.toString(TASKS_FILE_VERSION));
+                for (int i = 0; i < mTasksSet.size(); i++) {
+                    final TaskStatus taskStatus = mTasksSet.valueAt(i);
+                    if (DEBUG) {
+                        Slog.d(TAG, "Saving task " + taskStatus.getTaskId());
+                    }
+                    out.startTag(null, "task");
+                    addIdentifierAttributesToTaskTag(out, taskStatus);
+                    writeConstraintsToXml(out, taskStatus);
+                    writeExecutionCriteriaToXml(out, taskStatus);
+                    writeBundleToXml(taskStatus.getExtras(), out);
+                    out.endTag(null, "task");
+                }
+                out.endTag(null, "task-info");
+                out.endDocument();
+
+                // Write out to disk in one fell sweep.
+                FileOutputStream fos = mTasksFile.startWrite();
+                fos.write(baos.toByteArray());
+                mTasksFile.finishWrite(fos);
+                mDirtyOperations = 0;
+            } catch (IOException e) {
+                if (DEBUG) {
+                    Slog.v(TAG, "Error writing out task data.", e);
+                }
+            } catch (XmlPullParserException e) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Error persisting bundle.", e);
+                }
+            }
+        }
+
+        /** Write out a tag with data comprising the required fields of this task and its client. */
+        private void addIdentifierAttributesToTaskTag(XmlSerializer out, TaskStatus taskStatus)
+                throws IOException {
+            out.attribute(null, "taskid", Integer.toString(taskStatus.getTaskId()));
+            out.attribute(null, "package", taskStatus.getServiceComponent().getPackageName());
+            out.attribute(null, "class", taskStatus.getServiceComponent().getClassName());
+            out.attribute(null, "uid", Integer.toString(taskStatus.getUid()));
+        }
+
+        private void writeBundleToXml(PersistableBundle extras, XmlSerializer out)
+                throws IOException, XmlPullParserException {
+            out.startTag(null, XML_TAG_EXTRAS);
+            extras.saveToXml(out);
+            out.endTag(null, XML_TAG_EXTRAS);
+        }
+        /**
+         * Write out a tag with data identifying this tasks constraints. If the constraint isn't here
+         * it doesn't apply.
+         */
+        private void writeConstraintsToXml(XmlSerializer out, TaskStatus taskStatus) throws IOException {
+            out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS);
+            if (taskStatus.hasMeteredConstraint()) {
+                out.attribute(null, "unmetered", Boolean.toString(true));
+            }
+            if (taskStatus.hasConnectivityConstraint()) {
+                out.attribute(null, "connectivity", Boolean.toString(true));
+            }
+            if (taskStatus.hasIdleConstraint()) {
+                out.attribute(null, "idle", Boolean.toString(true));
+            }
+            if (taskStatus.hasChargingConstraint()) {
+                out.attribute(null, "charging", Boolean.toString(true));
+            }
+            out.endTag(null, XML_TAG_PARAMS_CONSTRAINTS);
+        }
+
+        private void writeExecutionCriteriaToXml(XmlSerializer out, TaskStatus taskStatus)
+                throws IOException {
+            final Task task = taskStatus.getTask();
+            if (taskStatus.getTask().isPeriodic()) {
+                out.startTag(null, XML_TAG_PERIODIC);
+                out.attribute(null, "period", Long.toString(task.getIntervalMillis()));
+            } else {
+                out.startTag(null, XML_TAG_ONEOFF);
+            }
+
+            if (taskStatus.hasDeadlineConstraint()) {
+                // Wall clock deadline.
+                final long deadlineWallclock =  System.currentTimeMillis() +
+                        (taskStatus.getLatestRunTimeElapsed() - SystemClock.elapsedRealtime());
+                out.attribute(null, "deadline", Long.toString(deadlineWallclock));
+            }
+            if (taskStatus.hasTimingDelayConstraint()) {
+                final long delayWallclock = System.currentTimeMillis() +
+                        (taskStatus.getEarliestRunTime() - SystemClock.elapsedRealtime());
+                out.attribute(null, "delay", Long.toString(delayWallclock));
+            }
+
+            // Only write out back-off policy if it differs from the default.
+            // This also helps the case where the task is idle -> these aren't allowed to specify
+            // back-off.
+            if (taskStatus.getTask().getInitialBackoffMillis() != Task.DEFAULT_INITIAL_BACKOFF_MILLIS
+                    || taskStatus.getTask().getBackoffPolicy() != Task.DEFAULT_BACKOFF_POLICY) {
+                out.attribute(null, "backoff-policy", Integer.toString(task.getBackoffPolicy()));
+                out.attribute(null, "initial-backoff", Long.toString(task.getInitialBackoffMillis()));
+            }
+            if (task.isPeriodic()) {
+                out.endTag(null, XML_TAG_PERIODIC);
+            } else {
+                out.endTag(null, XML_TAG_ONEOFF);
+            }
+        }
+    }
+
+    /**
+     * Runnable that reads list of persisted task from xml.
+     * NOTE: This Runnable locks on TaskStore.this
+     */
+    private class ReadTaskMapFromDiskRunnable implements Runnable {
+        private TaskMapReadFinishedListener mCallback;
+        public ReadTaskMapFromDiskRunnable(TaskMapReadFinishedListener callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void run() {
+            try {
+                List<TaskStatus> tasks;
+                synchronized (TaskStore.this) {
+                    tasks = readTaskMapImpl();
+                }
+                if (tasks != null) {
+                    mCallback.onTaskMapReadFinished(tasks);
+                }
+            } catch (FileNotFoundException e) {
+                if (TaskManagerService.DEBUG) {
+                    Slog.d(TAG, "Could not find tasks file, probably there was nothing to load.");
+                }
+            } catch (XmlPullParserException e) {
+                if (TaskManagerService.DEBUG) {
+                    Slog.d(TAG, "Error parsing xml.", e);
+                }
+            } catch (IOException e) {
+                if (TaskManagerService.DEBUG) {
+                    Slog.d(TAG, "Error parsing xml.", e);
+                }
+            }
+        }
+
+        private List<TaskStatus> readTaskMapImpl() throws XmlPullParserException, IOException {
+            FileInputStream fis = mTasksFile.openRead();
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(fis, null);
+
+            int eventType = parser.getEventType();
+            while (eventType != XmlPullParser.START_TAG &&
+                    eventType != XmlPullParser.END_DOCUMENT) {
+                eventType = parser.next();
+                Slog.d(TAG, parser.getName());
+            }
+            if (eventType == XmlPullParser.END_DOCUMENT) {
+                if (DEBUG) {
+                    Slog.d(TAG, "No persisted tasks.");
+                }
+                return null;
+            }
+
+            String tagName = parser.getName();
+            if ("task-info".equals(tagName)) {
+                final List<TaskStatus> tasks = new ArrayList<TaskStatus>();
+                // Read in version info.
+                try {
+                    int version = Integer.valueOf(parser.getAttributeValue(null, "version"));
+                    if (version != TASKS_FILE_VERSION) {
+                        Slog.d(TAG, "Invalid version number, aborting tasks file read.");
+                        return null;
+                    }
+                } catch (NumberFormatException e) {
+                    Slog.e(TAG, "Invalid version number, aborting tasks file read.");
+                    return null;
+                }
+                eventType = parser.next();
+                do {
+                    // Read each <task/>
+                    if (eventType == XmlPullParser.START_TAG) {
+                        tagName = parser.getName();
+                        // Start reading task.
+                        if ("task".equals(tagName)) {
+                            TaskStatus persistedTask = restoreTaskFromXml(parser);
+                            if (persistedTask != null) {
+                                if (DEBUG) {
+                                    Slog.d(TAG, "Read out " + persistedTask);
+                                }
+                                tasks.add(persistedTask);
+                            } else {
+                                Slog.d(TAG, "Error reading task from file.");
+                            }
+                        }
+                    }
+                    eventType = parser.next();
+                } while (eventType != XmlPullParser.END_DOCUMENT);
+                return tasks;
+            }
+            return null;
+        }
+
+        /**
+         * @param parser Xml parser at the beginning of a "<task/>" tag. The next "parser.next()" call
+         *               will take the parser into the body of the task tag.
+         * @return Newly instantiated task holding all the information we just read out of the xml tag.
+         */
+        private TaskStatus restoreTaskFromXml(XmlPullParser parser) throws XmlPullParserException,
+                IOException {
+            Task.Builder taskBuilder;
+            int uid;
+
+            // Read out task identifier attributes.
+            try {
+                taskBuilder = buildBuilderFromXml(parser);
+                uid = Integer.valueOf(parser.getAttributeValue(null, "uid"));
+            } catch (NumberFormatException e) {
+                Slog.e(TAG, "Error parsing task's required fields, skipping");
+                return null;
+            }
+
+            int eventType;
+            // Read out constraints tag.
+            do {
+                eventType = parser.next();
+            } while (eventType == XmlPullParser.TEXT);  // Push through to next START_TAG.
+
+            if (!(eventType == XmlPullParser.START_TAG &&
+                    XML_TAG_PARAMS_CONSTRAINTS.equals(parser.getName()))) {
+                // Expecting a <constraints> start tag.
+                return null;
+            }
+            try {
+                buildConstraintsFromXml(taskBuilder, parser);
+            } catch (NumberFormatException e) {
+                Slog.d(TAG, "Error reading constraints, skipping.");
+                return null;
+            }
+            parser.next(); // Consume </constraints>
+
+            // Read out execution parameters tag.
+            do {
+                eventType = parser.next();
+            } while (eventType == XmlPullParser.TEXT);
+            if (eventType != XmlPullParser.START_TAG) {
+                return null;
+            }
+
+            Pair<Long, Long> runtimes;
+            try {
+                runtimes = buildExecutionTimesFromXml(parser);
+            } catch (NumberFormatException e) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Error parsing execution time parameters, skipping.");
+                }
+                return null;
+            }
+
+            if (XML_TAG_PERIODIC.equals(parser.getName())) {
+                try {
+                    String val = parser.getAttributeValue(null, "period");
+                    taskBuilder.setPeriodic(Long.valueOf(val));
+                } catch (NumberFormatException e) {
+                    Slog.d(TAG, "Error reading periodic execution criteria, skipping.");
+                    return null;
+                }
+            } else if (XML_TAG_ONEOFF.equals(parser.getName())) {
+                try {
+                    if (runtimes.first != TaskStatus.DEFAULT_EARLIEST_RUNTIME) {
+                        taskBuilder.setMinimumLatency(runtimes.first - SystemClock.elapsedRealtime());
+                    }
+                    if (runtimes.second != TaskStatus.DEFAULT_LATEST_RUNTIME) {
+                        taskBuilder.setOverrideDeadline(
+                                runtimes.second - SystemClock.elapsedRealtime());
+                    }
+                } catch (NumberFormatException e) {
+                    Slog.d(TAG, "Error reading task execution criteria, skipping.");
+                    return null;
+                }
+            } else {
+                if (DEBUG) {
+                    Slog.d(TAG, "Invalid parameter tag, skipping - " + parser.getName());
+                }
+                // Expecting a parameters start tag.
+                return null;
+            }
+            maybeBuildBackoffPolicyFromXml(taskBuilder, parser);
+
+            parser.nextTag(); // Consume parameters end tag.
+
+            // Read out extras Bundle.
+            do {
+                eventType = parser.next();
+            } while (eventType == XmlPullParser.TEXT);
+            if (!(eventType == XmlPullParser.START_TAG && XML_TAG_EXTRAS.equals(parser.getName()))) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Error reading extras, skipping.");
+                }
+                return null;
+            }
+
+            PersistableBundle extras = PersistableBundle.restoreFromXml(parser);
+            taskBuilder.setExtras(extras);
+            parser.nextTag(); // Consume </extras>
+
+            return new TaskStatus(taskBuilder.build(), uid, runtimes.first, runtimes.second);
+        }
+
+        private Task.Builder buildBuilderFromXml(XmlPullParser parser) throws NumberFormatException {
+            // Pull out required fields from <task> attributes.
+            int taskId = Integer.valueOf(parser.getAttributeValue(null, "taskid"));
+            String packageName = parser.getAttributeValue(null, "package");
+            String className = parser.getAttributeValue(null, "class");
+            ComponentName cname = new ComponentName(packageName, className);
+
+            return new Task.Builder(taskId, cname);
+        }
+
+        private void buildConstraintsFromXml(Task.Builder taskBuilder, XmlPullParser parser) {
+            String val = parser.getAttributeValue(null, "unmetered");
+            if (val != null) {
+                taskBuilder.setRequiredNetworkCapabilities(Task.NetworkType.UNMETERED);
+            }
+            val = parser.getAttributeValue(null, "connectivity");
+            if (val != null) {
+                taskBuilder.setRequiredNetworkCapabilities(Task.NetworkType.ANY);
+            }
+            val = parser.getAttributeValue(null, "idle");
+            if (val != null) {
+                taskBuilder.setRequiresDeviceIdle(true);
+            }
+            val = parser.getAttributeValue(null, "charging");
+            if (val != null) {
+                taskBuilder.setRequiresCharging(true);
+            }
+        }
+
+        /**
+         * Builds the back-off policy out of the params tag. These attributes may not exist, depending
+         * on whether the back-off was set when the task was first scheduled.
+         */
+        private void maybeBuildBackoffPolicyFromXml(Task.Builder taskBuilder, XmlPullParser parser) {
+            String val = parser.getAttributeValue(null, "initial-backoff");
+            if (val != null) {
+                long initialBackoff = Long.valueOf(val);
+                val = parser.getAttributeValue(null, "backoff-policy");
+                int backoffPolicy = Integer.valueOf(val);  // Will throw NFE which we catch higher up.
+                taskBuilder.setBackoffCriteria(initialBackoff, backoffPolicy);
+            }
+        }
+
+        /**
+         * Convenience function to read out and convert deadline and delay from xml into elapsed real
+         * time.
+         * @return A {@link android.util.Pair}, where the first value is the earliest elapsed runtime
+         * and the second is the latest elapsed runtime.
+         */
+        private Pair<Long, Long> buildExecutionTimesFromXml(XmlPullParser parser)
+                throws NumberFormatException {
+            // Pull out execution time data.
+            final long nowWallclock = System.currentTimeMillis();
+            final long nowElapsed = SystemClock.elapsedRealtime();
+
+            long earliestRunTimeElapsed = TaskStatus.DEFAULT_EARLIEST_RUNTIME;
+            long latestRunTimeElapsed = TaskStatus.DEFAULT_LATEST_RUNTIME;
+            String val = parser.getAttributeValue(null, "deadline");
+            if (val != null) {
+                long latestRuntimeWallclock = Long.valueOf(val);
+                long maxDelayElapsed =
+                        Math.max(latestRuntimeWallclock - nowWallclock, 0);
+                latestRunTimeElapsed = nowElapsed + maxDelayElapsed;
+            }
+            val = parser.getAttributeValue(null, "delay");
+            if (val != null) {
+                long earliestRuntimeWallclock = Long.valueOf(val);
+                long minDelayElapsed =
+                        Math.max(earliestRuntimeWallclock - nowWallclock, 0);
+                earliestRunTimeElapsed = nowElapsed + minDelayElapsed;
+
+            }
+            return Pair.create(earliestRunTimeElapsed, latestRunTimeElapsed);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/task/controllers/BatteryController.java b/services/core/java/com/android/server/task/controllers/BatteryController.java
new file mode 100644
index 0000000..585b41f
--- /dev/null
+++ b/services/core/java/com/android/server/task/controllers/BatteryController.java
@@ -0,0 +1,213 @@
+/*
+ * 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 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.BatteryManager;
+import android.os.BatteryProperty;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.BatteryService;
+import com.android.server.task.StateChangedListener;
+import com.android.server.task.TaskManagerService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Simple controller that tracks whether the phone is charging or not. The phone is considered to
+ * be charging when it's been plugged in for more than two minutes, and the system has broadcast
+ * ACTION_BATTERY_OK.
+ */
+public class BatteryController extends StateController {
+    private static final String TAG = "BatteryController";
+
+    private static final Object sCreationLock = new Object();
+    private static volatile BatteryController sController;
+    private static final String ACTION_CHARGING_STABLE =
+            "com.android.server.task.controllers.BatteryController.ACTION_CHARGING_STABLE";
+    /** Wait this long after phone is plugged in before doing any work. */
+    private static final long STABLE_CHARGING_THRESHOLD_MILLIS = 2 * 60 * 1000; // 2 minutes.
+
+    private List<TaskStatus> mTrackedTasks = new ArrayList<TaskStatus>();
+    private ChargingTracker mChargeTracker;
+
+    public static BatteryController get(TaskManagerService taskManagerService) {
+        synchronized (sCreationLock) {
+            if (sController == null) {
+                sController = new BatteryController(taskManagerService,
+                        taskManagerService.getContext());
+            }
+        }
+        return sController;
+    }
+
+    @VisibleForTesting
+    public ChargingTracker getTracker() {
+        return mChargeTracker;
+    }
+
+    @VisibleForTesting
+    public static BatteryController getForTesting(StateChangedListener stateChangedListener,
+                                           Context context) {
+        return new BatteryController(stateChangedListener, context);
+    }
+
+    private BatteryController(StateChangedListener stateChangedListener, Context context) {
+        super(stateChangedListener, context);
+        mChargeTracker = new ChargingTracker();
+        mChargeTracker.startTracking();
+    }
+
+    @Override
+    public void maybeStartTrackingTask(TaskStatus taskStatus) {
+        if (taskStatus.hasChargingConstraint()) {
+            synchronized (mTrackedTasks) {
+                mTrackedTasks.add(taskStatus);
+                taskStatus.chargingConstraintSatisfied.set(mChargeTracker.isOnStablePower());
+            }
+        }
+
+    }
+
+    @Override
+    public void maybeStopTrackingTask(TaskStatus taskStatus) {
+        if (taskStatus.hasChargingConstraint()) {
+            synchronized (mTrackedTasks) {
+                mTrackedTasks.remove(taskStatus);
+            }
+        }
+    }
+
+    private void maybeReportNewChargingState() {
+        final boolean stablePower = mChargeTracker.isOnStablePower();
+        boolean reportChange = false;
+        synchronized (mTrackedTasks) {
+            for (TaskStatus ts : mTrackedTasks) {
+                boolean previous = ts.chargingConstraintSatisfied.getAndSet(stablePower);
+                if (previous != stablePower) {
+                    reportChange = true;
+                }
+            }
+        }
+        if (reportChange) {
+            mStateChangedListener.onControllerStateChanged();
+        }
+    }
+
+    public class ChargingTracker extends BroadcastReceiver {
+        private final AlarmManager mAlarm;
+        private final PendingIntent mStableChargingTriggerIntent;
+        /**
+         * Track whether we're "charging", where charging means that we're ready to commit to
+         * doing work.
+         */
+        private boolean mCharging;
+        /** Keep track of whether the battery is charged enough that we want to do work. */
+        private boolean mBatteryHealthy;
+
+        public ChargingTracker() {
+            mAlarm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+            Intent intent = new Intent(ACTION_CHARGING_STABLE)
+                    .setComponent(new ComponentName(mContext, this.getClass()));
+            mStableChargingTriggerIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+        }
+
+        public void startTracking() {
+            IntentFilter filter = new IntentFilter();
+
+            // Battery health.
+            filter.addAction(Intent.ACTION_BATTERY_LOW);
+            filter.addAction(Intent.ACTION_BATTERY_OKAY);
+            // Charging/not charging.
+            filter.addAction(Intent.ACTION_POWER_CONNECTED);
+            filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
+            mContext.registerReceiver(this, filter);
+
+            // Initialise tracker state.
+            BatteryService batteryService = (BatteryService) ServiceManager.getService("battery");
+            if (batteryService != null) {
+                mBatteryHealthy = !batteryService.isBatteryLow();
+                mCharging = batteryService.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
+            } else {
+                // Unavailable for some reason, we default to false and let ACTION_BATTERY_[OK,LOW]
+                // sort it out.
+            }
+        }
+
+        boolean isOnStablePower() {
+            return mCharging && mBatteryHealthy;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            onReceiveInternal(intent);
+        }
+
+        @VisibleForTesting
+        public void onReceiveInternal(Intent intent) {
+            final String action = intent.getAction();
+            if (Intent.ACTION_BATTERY_LOW.equals(action)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Battery life too low to do work. @ "
+                            + SystemClock.elapsedRealtime());
+                }
+                // If we get this action, the battery is discharging => it isn't plugged in so
+                // there's no work to cancel. We track this variable for the case where it is
+                // charging, but hasn't been for long enough to be healthy.
+                mBatteryHealthy = false;
+            } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Battery life healthy enough to do work. @ "
+                            + SystemClock.elapsedRealtime());
+                }
+                mBatteryHealthy = true;
+                maybeReportNewChargingState();
+            } else if (Intent.ACTION_POWER_CONNECTED.equals(action)) {
+                // Set up an alarm for ACTION_CHARGING_STABLE - we don't want to kick off tasks
+                // here if the user unplugs the phone immediately.
+                mAlarm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                        SystemClock.elapsedRealtime() + STABLE_CHARGING_THRESHOLD_MILLIS,
+                        mStableChargingTriggerIntent);
+                mCharging = true;
+            } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
+                // If an alarm is set, breathe a sigh of relief and cancel it - crisis averted.
+                mAlarm.cancel(mStableChargingTriggerIntent);
+                mCharging = false;
+                maybeReportNewChargingState();
+            }else if (ACTION_CHARGING_STABLE.equals(action)) {
+                // Here's where we actually do the notify for a task being ready.
+                if (DEBUG) {
+                    Slog.d(TAG, "Battery connected fired @ " + SystemClock.elapsedRealtime());
+                }
+                if (mCharging) {  // Should never receive this intent if mCharging is false.
+                    maybeReportNewChargingState();
+                }
+            }
+        }
+    }
+}
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 474af8f..4819460 100644
--- a/services/core/java/com/android/server/task/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/task/controllers/ConnectivityController.java
@@ -27,6 +27,7 @@
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.server.task.StateChangedListener;
 import com.android.server.task.TaskManagerService;
 
 import java.util.LinkedList;
@@ -39,7 +40,6 @@
  */
 public class ConnectivityController extends StateController {
     private static final String TAG = "TaskManager.Connectivity";
-    private static final boolean DEBUG = true;
 
     private final List<TaskStatus> mTrackedTasks = new LinkedList<TaskStatus>();
     private final BroadcastReceiver mConnectivityChangedReceiver =
@@ -54,13 +54,13 @@
 
     public static synchronized ConnectivityController get(TaskManagerService taskManager) {
         if (mSingleton == null) {
-            mSingleton = new ConnectivityController(taskManager);
+            mSingleton = new ConnectivityController(taskManager, taskManager.getContext());
         }
         return mSingleton;
     }
 
-    private ConnectivityController(TaskManagerService service) {
-        super(service);
+    private ConnectivityController(StateChangedListener stateChangedListener, Context context) {
+        super(stateChangedListener, context);
         // Register connectivity changed BR.
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(ConnectivityManager.CONNECTIVITY_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
index 9489644..c47faca 100644
--- a/services/core/java/com/android/server/task/controllers/IdleController.java
+++ b/services/core/java/com/android/server/task/controllers/IdleController.java
@@ -28,11 +28,11 @@
 import android.os.SystemClock;
 import android.util.Slog;
 
+import com.android.server.task.StateChangedListener;
 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
@@ -52,14 +52,14 @@
     public static IdleController get(TaskManagerService service) {
         synchronized (sCreationLock) {
             if (sController == null) {
-                sController = new IdleController(service);
+                sController = new IdleController(service, service.getContext());
             }
             return sController;
         }
     }
 
-    private IdleController(TaskManagerService service) {
-        super(service);
+    private IdleController(StateChangedListener stateChangedListener, Context context) {
+        super(stateChangedListener, context);
         initIdleStateTracking();
     }
 
diff --git a/services/core/java/com/android/server/task/controllers/StateController.java b/services/core/java/com/android/server/task/controllers/StateController.java
index ed31eac..cbe6ff8 100644
--- a/services/core/java/com/android/server/task/controllers/StateController.java
+++ b/services/core/java/com/android/server/task/controllers/StateController.java
@@ -27,13 +27,13 @@
  * are ready to run, or whether they must be stopped.
  */
 public abstract class StateController {
-
+    protected static final boolean DEBUG = true;
     protected Context mContext;
     protected StateChangedListener mStateChangedListener;
 
-    public StateController(TaskManagerService service) {
-        mStateChangedListener = service;
-        mContext = service.getContext();
+    public StateController(StateChangedListener stateChangedListener, Context context) {
+        mStateChangedListener = stateChangedListener;
+        mContext = context;
     }
 
     /**
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 b7f84ec..33670a1 100644
--- a/services/core/java/com/android/server/task/controllers/TaskStatus.java
+++ b/services/core/java/com/android/server/task/controllers/TaskStatus.java
@@ -18,7 +18,7 @@
 
 import android.app.task.Task;
 import android.content.ComponentName;
-import android.os.Bundle;
+import android.os.PersistableBundle;
 import android.os.SystemClock;
 import android.os.UserHandle;
 
@@ -37,6 +37,9 @@
  * @hide
  */
 public class TaskStatus {
+    public static final long DEFAULT_LATEST_RUNTIME = Long.MAX_VALUE;
+    public static final long DEFAULT_EARLIEST_RUNTIME = 0L;
+
     final Task task;
     final int uId;
 
@@ -61,7 +64,7 @@
      * indicates there is no deadline constraint. See {@link #hasDeadlineConstraint()}.
      */
     private long latestRunTimeElapsedMillis;
-
+    /** How many times this task has failed, used to compute back-off. */
     private final int numFailures;
 
     /** Provide a handle to the service that this task will be run on. */
@@ -69,36 +72,52 @@
         return uId;
     }
 
-    /** Create a newly scheduled task. */
-    public TaskStatus(Task task, int uId, boolean persisted) {
+    private TaskStatus(Task task, int uId, boolean persisted, int numFailures) {
         this.task = task;
         this.uId = uId;
-        this.numFailures = 0;
+        this.numFailures = numFailures;
         this.persisted = persisted;
+    }
+
+    /** Create a newly scheduled task. */
+    public TaskStatus(Task task, int uId, boolean persisted) {
+        this(task, uId, persisted, 0);
 
         final long elapsedNow = SystemClock.elapsedRealtime();
-        // Timing constraints
+
         if (task.isPeriodic()) {
             earliestRunTimeElapsedMillis = elapsedNow;
             latestRunTimeElapsedMillis = elapsedNow + task.getIntervalMillis();
         } else {
             earliestRunTimeElapsedMillis = task.hasEarlyConstraint() ?
-                    elapsedNow + task.getMinLatencyMillis() : 0L;
+                    elapsedNow + task.getMinLatencyMillis() : DEFAULT_EARLIEST_RUNTIME;
             latestRunTimeElapsedMillis = task.hasLateConstraint() ?
-                    elapsedNow + task.getMaxExecutionDelayMillis() : Long.MAX_VALUE;
+                    elapsedNow + task.getMaxExecutionDelayMillis() : DEFAULT_LATEST_RUNTIME;
         }
     }
 
-    public TaskStatus(TaskStatus rescheduling, long newEarliestRuntimeElapsed,
-                      long newLatestRuntimeElapsed, int backoffAttempt) {
-        this.task = rescheduling.task;
+    /**
+     * Create a new TaskStatus that was loaded from disk. We ignore the provided
+     * {@link android.app.task.Task} time criteria because we can load a persisted periodic task
+     * from the {@link com.android.server.task.TaskStore} and still want to respect its
+     * wallclock runtime rather than resetting it on every boot.
+     * We consider a freshly loaded task to no longer be in back-off.
+     */
+    public TaskStatus(Task task, int uId, long earliestRunTimeElapsedMillis,
+                      long latestRunTimeElapsedMillis) {
+        this(task, uId, true, 0);
 
-        this.uId = rescheduling.getUid();
-        this.persisted = rescheduling.isPersisted();
-        this.numFailures = backoffAttempt;
+        this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis;
+        this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
+    }
 
-        earliestRunTimeElapsedMillis = newEarliestRuntimeElapsed;
-        latestRunTimeElapsedMillis = newLatestRuntimeElapsed;
+    /** Create a new task to be rescheduled with the provided parameters. */
+    public TaskStatus(TaskStatus rescheduling, long newEarliestRuntimeElapsedMillis,
+                      long newLatestRuntimeElapsedMillis, int backoffAttempt) {
+        this(rescheduling.task, rescheduling.getUid(), rescheduling.isPersisted(), backoffAttempt);
+
+        earliestRunTimeElapsedMillis = newEarliestRuntimeElapsedMillis;
+        latestRunTimeElapsedMillis = newLatestRuntimeElapsedMillis;
     }
 
     public Task getTask() {
@@ -125,7 +144,7 @@
         return uId;
     }
 
-    public Bundle getExtras() {
+    public PersistableBundle getExtras() {
         return task.getExtras();
     }
 
@@ -142,11 +161,11 @@
     }
 
     public boolean hasTimingDelayConstraint() {
-        return earliestRunTimeElapsedMillis != 0L;
+        return earliestRunTimeElapsedMillis != DEFAULT_EARLIEST_RUNTIME;
     }
 
     public boolean hasDeadlineConstraint() {
-        return latestRunTimeElapsedMillis != Long.MAX_VALUE;
+        return latestRunTimeElapsedMillis != DEFAULT_LATEST_RUNTIME;
     }
 
     public boolean hasIdleConstraint() {
diff --git a/services/core/java/com/android/server/task/controllers/TimeController.java b/services/core/java/com/android/server/task/controllers/TimeController.java
index 72f312c..8c6dd27 100644
--- a/services/core/java/com/android/server/task/controllers/TimeController.java
+++ b/services/core/java/com/android/server/task/controllers/TimeController.java
@@ -24,6 +24,7 @@
 import android.content.IntentFilter;
 import android.os.SystemClock;
 
+import com.android.server.task.StateChangedListener;
 import com.android.server.task.TaskManagerService;
 
 import java.util.Iterator;
@@ -58,13 +59,13 @@
 
     public static synchronized TimeController get(TaskManagerService taskManager) {
         if (mSingleton == null) {
-            mSingleton = new TimeController(taskManager);
+            mSingleton = new TimeController(taskManager, taskManager.getContext());
         }
         return mSingleton;
     }
 
-    private TimeController(TaskManagerService service) {
-        super(service);
+    private TimeController(StateChangedListener stateChangedListener, Context context) {
+        super(stateChangedListener, context);
         mTaskExpiredAlarmIntent =
                 PendingIntent.getBroadcast(mContext, 0 /* ignored */,
                         new Intent(ACTION_TASK_EXPIRED), 0);
diff --git a/services/tests/servicestests/src/com/android/server/task/TaskStoreTest.java b/services/tests/servicestests/src/com/android/server/task/TaskStoreTest.java
new file mode 100644
index 0000000..e7f9ca0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/task/TaskStoreTest.java
@@ -0,0 +1,199 @@
+package com.android.server.task;
+
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.app.task.Task;
+import android.app.task.Task.Builder;
+import android.os.PersistableBundle;
+import android.test.AndroidTestCase;
+import android.test.RenamingDelegatingContext;
+import android.util.Log;
+
+import com.android.server.task.controllers.TaskStatus;
+
+import java.util.List;
+
+import static com.android.server.task.TaskStore.initAndGet;
+/**
+ * Test reading and writing correctly from file.
+ */
+public class TaskStoreTest extends AndroidTestCase {
+    private static final String TAG = "TaskStoreTest";
+    private static final String TEST_PREFIX = "_test_";
+    // private static final int USER_NON_0 = 3;
+    private static final int SOME_UID = 34234;
+    private ComponentName mComponent;
+    private static final long IO_WAIT = 600L;
+
+    TaskStore mTaskStoreUnderTest;
+    Context mTestContext;
+    TaskMapReadFinishedListener mTaskMapReadFinishedListenerStub =
+            new TaskMapReadFinishedListener() {
+        @Override
+        public void onTaskMapReadFinished(List<TaskStatus> tasks) {
+            // do nothing.
+        }
+    };
+
+    @Override
+    public void setUp() throws Exception {
+        mTestContext = new RenamingDelegatingContext(getContext(), TEST_PREFIX);
+        Log.d(TAG, "Saving tasks to '" + mTestContext.getFilesDir() + "'");
+        mTaskStoreUnderTest = TaskStore.initAndGetForTesting(mTestContext,
+                mTestContext.getFilesDir(), mTaskMapReadFinishedListenerStub);
+        mComponent = new ComponentName(getContext().getPackageName(), StubClass.class.getName());
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mTaskStoreUnderTest.clear();
+    }
+
+    public void testMaybeWriteStatusToDisk() throws Exception {
+        int taskId = 5;
+        long runByMillis = 20000L; // 20s
+        long runFromMillis = 2000L; // 2s
+        long initialBackoff = 10000L; // 10s
+
+        final Task task = new Builder(taskId, mComponent)
+                .setRequiresCharging(true)
+                .setRequiredNetworkCapabilities(Task.NetworkType.ANY)
+                .setBackoffCriteria(initialBackoff, Task.BackoffPolicy.EXPONENTIAL)
+                .setOverrideDeadline(runByMillis)
+                .setMinimumLatency(runFromMillis)
+                .build();
+        final TaskStatus ts = new TaskStatus(task, SOME_UID, true /* persisted */);
+        mTaskStoreUnderTest.add(ts);
+        Thread.sleep(IO_WAIT);
+        // Manually load tasks from xml file.
+        mTaskStoreUnderTest.readTaskMapFromDisk(new TaskMapReadFinishedListener() {
+            @Override
+            public void onTaskMapReadFinished(List<TaskStatus> tasks) {
+                assertEquals("Didn't get expected number of persisted tasks.", 1, tasks.size());
+                TaskStatus loadedTaskStatus = tasks.get(0);
+                assertTasksEqual(task, loadedTaskStatus.getTask());
+                assertEquals("Different uids.", SOME_UID, tasks.get(0).getUid());
+                compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
+                        ts.getEarliestRunTime(), loadedTaskStatus.getEarliestRunTime());
+                compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
+                        ts.getLatestRunTimeElapsed(), loadedTaskStatus.getLatestRunTimeElapsed());
+            }
+        });
+
+    }
+
+    public void testWritingTwoFilesToDisk() throws Exception {
+        final Task task1 = new Builder(8, mComponent)
+                .setRequiresDeviceIdle(true)
+                .setPeriodic(10000L)
+                .setRequiresCharging(true)
+                .build();
+        final Task task2 = new Builder(12, mComponent)
+                .setMinimumLatency(5000L)
+                .setBackoffCriteria(15000L, Task.BackoffPolicy.LINEAR)
+                .setOverrideDeadline(30000L)
+                .setRequiredNetworkCapabilities(Task.NetworkType.UNMETERED)
+                .build();
+        final TaskStatus taskStatus1 = new TaskStatus(task1, SOME_UID, true /* persisted */);
+        final TaskStatus taskStatus2 = new TaskStatus(task2, SOME_UID, true /* persisted */);
+        mTaskStoreUnderTest.add(taskStatus1);
+        mTaskStoreUnderTest.add(taskStatus2);
+        Thread.sleep(IO_WAIT);
+        mTaskStoreUnderTest.readTaskMapFromDisk(new TaskMapReadFinishedListener() {
+            @Override
+            public void onTaskMapReadFinished(List<TaskStatus> tasks) {
+                assertEquals("Incorrect # of persisted tasks.", 2, tasks.size());
+                TaskStatus loaded1 = tasks.get(0);
+                TaskStatus loaded2 = tasks.get(1);
+                assertTasksEqual(task1, loaded1.getTask());
+                assertTasksEqual(task2, loaded2.getTask());
+
+                // Check that the loaded task has the correct runtimes.
+                compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
+                        taskStatus1.getEarliestRunTime(), loaded1.getEarliestRunTime());
+                compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
+                        taskStatus1.getLatestRunTimeElapsed(), loaded1.getLatestRunTimeElapsed());
+                compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
+                        taskStatus2.getEarliestRunTime(), loaded2.getEarliestRunTime());
+                compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
+                        taskStatus2.getLatestRunTimeElapsed(), loaded2.getLatestRunTimeElapsed());
+            }
+        });
+
+    }
+
+    public void testWritingTaskWithExtras() throws Exception {
+        Task.Builder b = new Builder(8, mComponent)
+                .setRequiresDeviceIdle(true)
+                .setPeriodic(10000L)
+                .setRequiresCharging(true);
+
+        PersistableBundle extras = new PersistableBundle();
+        extras.putDouble("hello", 3.2);
+        extras.putString("hi", "there");
+        extras.putInt("into", 3);
+        b.setExtras(extras);
+        final Task task = b.build();
+        TaskStatus taskStatus = new TaskStatus(task, SOME_UID, true /* persisted */);
+
+        mTaskStoreUnderTest.add(taskStatus);
+        Thread.sleep(IO_WAIT);
+        mTaskStoreUnderTest.readTaskMapFromDisk(new TaskMapReadFinishedListener() {
+            @Override
+            public void onTaskMapReadFinished(List<TaskStatus> tasks) {
+                assertEquals("Incorrect # of persisted tasks.", 1, tasks.size());
+                TaskStatus loaded = tasks.get(0);
+                assertTasksEqual(task, loaded.getTask());
+            }
+        });
+
+    }
+
+    /**
+     * Helper function to throw an error if the provided task and TaskStatus objects are not equal.
+     */
+    private void assertTasksEqual(Task first, Task second) {
+        assertEquals("Different task ids.", first.getId(), second.getId());
+        assertEquals("Different components.", first.getService(), second.getService());
+        assertEquals("Different periodic status.", first.isPeriodic(), second.isPeriodic());
+        assertEquals("Different period.", first.getIntervalMillis(), second.getIntervalMillis());
+        assertEquals("Different inital backoff.", first.getInitialBackoffMillis(),
+                second.getInitialBackoffMillis());
+        assertEquals("Different backoff policy.", first.getBackoffPolicy(),
+                second.getBackoffPolicy());
+
+        assertEquals("Invalid charging constraint.", first.isRequireCharging(),
+                second.isRequireCharging());
+        assertEquals("Invalid idle constraint.", first.isRequireDeviceIdle(),
+                second.isRequireDeviceIdle());
+        assertEquals("Invalid unmetered constraint.",
+                first.getNetworkCapabilities() == Task.NetworkType.UNMETERED,
+                second.getNetworkCapabilities() == Task.NetworkType.UNMETERED);
+        assertEquals("Invalid connectivity constraint.",
+                first.getNetworkCapabilities() == Task.NetworkType.ANY,
+                second.getNetworkCapabilities() == Task.NetworkType.ANY);
+        assertEquals("Invalid deadline constraint.",
+                first.hasLateConstraint(),
+                second.hasLateConstraint());
+        assertEquals("Invalid delay constraint.",
+                first.hasEarlyConstraint(),
+                second.hasEarlyConstraint());
+        assertEquals("Extras don't match",
+                first.getExtras().toString(), second.getExtras().toString());
+    }
+
+    /**
+     * When comparing timestamps before and after DB read/writes (to make sure we're saving/loading
+     * the correct values), there is some latency involved that terrorises a naive assertEquals().
+     * We define a <code>DELTA_MILLIS</code> as a function variable here to make this comparision
+     * more reasonable.
+     */
+    private void compareTimestampsSubjectToIoLatency(String error, long ts1, long ts2) {
+        final long DELTA_MILLIS = 700L;  // We allow up to 700ms of latency for IO read/writes.
+        assertTrue(error, Math.abs(ts1 - ts2) < DELTA_MILLIS + IO_WAIT);
+    }
+
+    private static class StubClass {}
+
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/task/controllers/BatteryControllerTest.java b/services/tests/servicestests/src/com/android/server/task/controllers/BatteryControllerTest.java
new file mode 100644
index 0000000..e617caf
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/task/controllers/BatteryControllerTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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 android.content.ComponentName;
+import android.content.Intent;
+import android.test.AndroidTestCase;
+
+import com.android.server.task.StateChangedListener;
+
+import static com.android.server.task.controllers.BatteryController.getForTesting;
+
+import static org.mockito.Mockito.*;
+
+/**
+ *
+ */
+public class BatteryControllerTest extends AndroidTestCase {
+    BatteryController mBatteryControllerUnderTest;
+
+    StateChangedListener mStateChangedListenerStub = new StateChangedListener() {
+        @Override
+        public void onControllerStateChanged() {
+
+        }
+
+        @Override
+        public void onTaskDeadlineExpired(TaskStatus taskStatus) {
+
+        }
+    };
+    BatteryController.ChargingTracker mTrackerUnderTest;
+
+    public void setUp() throws Exception {
+        mBatteryControllerUnderTest = getForTesting(mStateChangedListenerStub, getTestContext());
+        mTrackerUnderTest = mBatteryControllerUnderTest.getTracker();
+    }
+
+    public void testSendBatteryChargingIntent() throws Exception {
+        Intent batteryConnectedIntent = new Intent(Intent.ACTION_POWER_CONNECTED)
+                .setComponent(new ComponentName(getContext(), mTrackerUnderTest.getClass()));
+        Intent batteryHealthyIntent = new Intent(Intent.ACTION_BATTERY_OKAY)
+                .setComponent(new ComponentName(getContext(), mTrackerUnderTest.getClass()));
+
+        mTrackerUnderTest.onReceiveInternal(batteryConnectedIntent);
+        mTrackerUnderTest.onReceiveInternal(batteryHealthyIntent);
+
+        assertTrue(mTrackerUnderTest.isOnStablePower());
+    }
+
+}
diff --git a/telecomm/java/android/telecomm/CallServiceAdapter.java b/telecomm/java/android/telecomm/CallServiceAdapter.java
index 7396808..0c57828 100644
--- a/telecomm/java/android/telecomm/CallServiceAdapter.java
+++ b/telecomm/java/android/telecomm/CallServiceAdapter.java
@@ -84,12 +84,16 @@
     /**
      * Tells Telecomm that an attempt to place the specified outgoing call failed.
      *
-     * @param callId The ID of the outgoing call.
-     * @param errorMessage The error associated with the failed call attempt.
+     * @param request The originating request for a connection.
+     * @param errorCode The error code associated with the failed call attempt.
+     * @param errorMsg The error message associated with the failed call attempt.
      */
-    public void handleFailedOutgoingCall(String callId, String errorMessage) {
+    public void handleFailedOutgoingCall(
+            ConnectionRequest request,
+            int errorCode,
+            String errorMsg) {
         try {
-            mAdapter.handleFailedOutgoingCall(callId, errorMessage);
+            mAdapter.handleFailedOutgoingCall(request, errorCode, errorMsg);
         } catch (RemoteException e) {
         }
     }
diff --git a/telecomm/java/android/telecomm/ConnectionRequest.aidl b/telecomm/java/android/telecomm/ConnectionRequest.aidl
new file mode 100644
index 0000000..72e5c8c
--- /dev/null
+++ b/telecomm/java/android/telecomm/ConnectionRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+package android.telecomm;
+
+parcelable ConnectionRequest;
diff --git a/telecomm/java/android/telecomm/ConnectionRequest.java b/telecomm/java/android/telecomm/ConnectionRequest.java
index c1f1871..bf5727b 100644
--- a/telecomm/java/android/telecomm/ConnectionRequest.java
+++ b/telecomm/java/android/telecomm/ConnectionRequest.java
@@ -18,23 +18,37 @@
 
 import android.os.Bundle;
 import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
 
 /**
  * Simple data container encapsulating a request to some entity to
  * create a new {@link Connection}.
  */
-public final class ConnectionRequest {
+public final class ConnectionRequest implements Parcelable {
 
     // TODO: Token to limit recursive invocations
     // TODO: Consider upgrading "mHandle" to ordered list of handles, indicating a set of phone
     //         numbers that would satisfy the client's needs, in order of preference
+    private final String mCallId;
     private final Uri mHandle;
     private final Bundle mExtras;
 
     public ConnectionRequest(Uri handle, Bundle extras) {
-        mHandle = handle; mExtras = extras;
+        this(null, handle, extras);
     }
 
+    public ConnectionRequest(String callId, Uri handle, Bundle extras) {
+        mCallId = callId;
+        mHandle = handle;
+        mExtras = extras;
+    }
+
+    /**
+     * An identifier for this call.
+     */
+    public String getCallId() { return mCallId; }
+
     /**
      * The handle (e.g., phone number) to which the {@link Connection} is to connect.
      */
@@ -54,4 +68,40 @@
                         : ConnectionService.toLogSafePhoneNumber(mHandle.toString()),
                 mExtras == null ? "" : mExtras);
     }
-}
+
+    /**
+     * Responsible for creating CallInfo objects for deserialized Parcels.
+     */
+    public static final Parcelable.Creator<ConnectionRequest> CREATOR =
+            new Parcelable.Creator<ConnectionRequest> () {
+                @Override
+                public ConnectionRequest createFromParcel(Parcel source) {
+                    String callId = source.readString();
+                    Uri handle = (Uri) source.readParcelable(getClass().getClassLoader());
+                    Bundle extras = (Bundle) source.readParcelable(getClass().getClassLoader());
+                    return new ConnectionRequest(callId, handle, extras);
+                }
+
+                @Override
+                public ConnectionRequest[] newArray(int size) {
+                    return new ConnectionRequest[size];
+                }
+            };
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Writes CallInfo object into a serializeable Parcel.
+     */
+    @Override
+    public void writeToParcel(Parcel destination, int flags) {
+        destination.writeString(mCallId);
+        destination.writeParcelable(mHandle, 0);
+        destination.writeParcelable(mExtras, 0);
+    }}
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index aeb1c33..31de15c 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -18,6 +18,7 @@
 
 import android.net.Uri;
 import android.os.Bundle;
+import android.telephony.DisconnectCause;
 
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -115,9 +116,8 @@
                     }
 
                     @Override
-                    public void onError(Uri handle, String reason) {
-                        Log.w(this, "Error in onFindSubscriptions " + callInfo.getHandle()
-                                + " error: " + reason);
+                    public void onError(Uri handle, int code, String msg) {
+                        Log.w(this, "Error in onFindSubscriptions %s %d %s", handle, code, msg);
                         getAdapter().setIsCompatibleWith(callInfo.getId(), false);
                     }
                 }
@@ -129,6 +129,7 @@
         Log.d(this, "call %s", callInfo);
         onCreateConnections(
                 new ConnectionRequest(
+                        callInfo.getId(),
                         callInfo.getHandle(),
                         callInfo.getExtras()),
                 new Response<ConnectionRequest, Connection>() {
@@ -137,7 +138,8 @@
                         if (result.length != 1) {
                             Log.d(this, "adapter handleFailedOutgoingCall %s", callInfo);
                             getAdapter().handleFailedOutgoingCall(
-                                    callInfo.getId(),
+                                    request,
+                                    DisconnectCause.ERROR_UNSPECIFIED,
                                     "Created " + result.length + " Connections, expected 1");
                             for (Connection c : result) {
                                 c.abort();
@@ -150,8 +152,8 @@
                     }
 
                     @Override
-                    public void onError(ConnectionRequest request, String reason) {
-                        getAdapter().handleFailedOutgoingCall(callInfo.getId(), reason);
+                    public void onError(ConnectionRequest request, int code, String msg) {
+                        getAdapter().handleFailedOutgoingCall(request, code, msg);
                     }
                 }
         );
@@ -168,6 +170,7 @@
         Log.d(this, "setIncomingCallId %s %s", callId, extras);
         onCreateIncomingConnection(
                 new ConnectionRequest(
+                        callId,
                         null,  // TODO: Can we obtain this from "extras"?
                         extras),
                 new Response<ConnectionRequest, Connection>() {
@@ -176,7 +179,8 @@
                         if (result.length != 1) {
                             Log.d(this, "adapter handleFailedOutgoingCall %s", callId);
                             getAdapter().handleFailedOutgoingCall(
-                                    callId,
+                                    request,
+                                    DisconnectCause.ERROR_UNSPECIFIED,
                                     "Created " + result.length + " Connections, expected 1");
                             for (Connection c : result) {
                                 c.abort();
@@ -195,8 +199,9 @@
                     }
 
                     @Override
-                    public void onError(ConnectionRequest request, String reason) {
-                        Log.d(this, "adapter failed setIncomingCallId %s %s", request, reason);
+                    public void onError(ConnectionRequest request, int code, String msg) {
+                        Log.d(this, "adapter failed setIncomingCallId %s %d %s",
+                                request, code, msg);
                     }
                 }
         );
diff --git a/telecomm/java/android/telecomm/InCallCall.java b/telecomm/java/android/telecomm/InCallCall.java
index 346d207..b531ccd 100644
--- a/telecomm/java/android/telecomm/InCallCall.java
+++ b/telecomm/java/android/telecomm/InCallCall.java
@@ -31,7 +31,8 @@
 public final class InCallCall implements Parcelable {
     private final String mId;
     private final CallState mState;
-    private final int mDisconnectCause;
+    private final int mDisconnectCauseCode;
+    private final String mDisconnectCauseMsg;
     private final int mCapabilities;
     private final long mConnectTimeMillis;
     private final Uri mHandle;
@@ -47,15 +48,16 @@
     public InCallCall(
             String id,
             CallState state,
-            int disconnectCause,
+            int disconnectCauseCode,
+            String disconnectCauseMsg,
             int capabilities,
             long connectTimeMillis,
             Uri handle,
             GatewayInfo gatewayInfo,
             CallServiceDescriptor descriptor,
             CallServiceDescriptor handoffDescriptor) {
-        this(id, state, disconnectCause, capabilities, connectTimeMillis, handle, gatewayInfo,
-                descriptor, handoffDescriptor, Collections.EMPTY_LIST, null,
+        this(id, state, disconnectCauseCode, disconnectCauseMsg, capabilities, connectTimeMillis,
+                handle, gatewayInfo, descriptor, handoffDescriptor, Collections.EMPTY_LIST, null,
                 Collections.EMPTY_LIST);
     }
 
@@ -63,7 +65,8 @@
     public InCallCall(
             String id,
             CallState state,
-            int disconnectCause,
+            int disconnectCauseCode,
+            String disconnectCauseMsg,
             int capabilities,
             long connectTimeMillis,
             Uri handle,
@@ -75,7 +78,8 @@
             List<String> childCallIds) {
         mId = id;
         mState = state;
-        mDisconnectCause = disconnectCause;
+        mDisconnectCauseCode = disconnectCauseCode;
+        mDisconnectCauseMsg = disconnectCauseMsg;
         mCapabilities = capabilities;
         mConnectTimeMillis = connectTimeMillis;
         mHandle = handle;
@@ -101,8 +105,16 @@
      * Reason for disconnection, values are defined in {@link DisconnectCause}. Valid when call
      * state is {@link CallState#DISCONNECTED}.
      */
-    public int getDisconnectCause() {
-        return mDisconnectCause;
+    public int getDisconnectCauseCode() {
+        return mDisconnectCauseCode;
+    }
+
+    /**
+     * Further optional textual information about the reason for disconnection. Valid when call
+     * state is {@link CallState#DISCONNECTED}.
+     */
+    public String getDisconnectCauseMsg() {
+        return mDisconnectCauseMsg;
     }
 
     // Bit mask of actions a call supports, values are defined in {@link CallCapabilities}.
@@ -170,7 +182,8 @@
         public InCallCall createFromParcel(Parcel source) {
             String id = source.readString();
             CallState state = CallState.valueOf(source.readString());
-            int disconnectCause = source.readInt();
+            int disconnectCauseCode = source.readInt();
+            String disconnectCauseMsg = source.readString();
             int capabilities = source.readInt();
             long connectTimeMillis = source.readLong();
             ClassLoader classLoader = InCallCall.class.getClassLoader();
@@ -183,9 +196,9 @@
             String parentCallId = source.readString();
             List<String> childCallIds = new ArrayList<>();
             source.readList(childCallIds, classLoader);
-            return new InCallCall(id, state, disconnectCause, capabilities, connectTimeMillis,
-                    handle, gatewayInfo, descriptor, handoffDescriptor, conferenceCapableCallIds,
-                    parentCallId, childCallIds);
+            return new InCallCall(id, state, disconnectCauseCode, disconnectCauseMsg, capabilities,
+                    connectTimeMillis, handle, gatewayInfo, descriptor, handoffDescriptor,
+                    conferenceCapableCallIds, parentCallId, childCallIds);
         }
 
         @Override
@@ -205,7 +218,8 @@
     public void writeToParcel(Parcel destination, int flags) {
         destination.writeString(mId);
         destination.writeString(mState.name());
-        destination.writeInt(mDisconnectCause);
+        destination.writeInt(mDisconnectCauseCode);
+        destination.writeString(mDisconnectCauseMsg);
         destination.writeInt(mCapabilities);
         destination.writeLong(mConnectTimeMillis);
         destination.writeParcelable(mHandle, 0);
diff --git a/telecomm/java/android/telecomm/Response.java b/telecomm/java/android/telecomm/Response.java
index 14f8340..13c0702 100644
--- a/telecomm/java/android/telecomm/Response.java
+++ b/telecomm/java/android/telecomm/Response.java
@@ -33,7 +33,8 @@
      * Indicates the inability to provide results.
      *
      * @param request The original request.
-     * @param reason The reason for the failure.
+     * @param code An integer code indicating the reason for failure.
+     * @param msg A message explaining the reason for failure.
      */
-    void onError(IN request, String reason);
+    void onError(IN request, int code, String msg);
 }
diff --git a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
index a92b176..f94eb32 100644
--- a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
@@ -17,6 +17,7 @@
 package com.android.internal.telecomm;
 
 import android.telecomm.CallInfo;
+import android.telecomm.ConnectionRequest;
 
 /**
  * Internal remote callback interface for call services.
@@ -32,7 +33,7 @@
 
     void handleSuccessfulOutgoingCall(String callId);
 
-    void handleFailedOutgoingCall(String callId, String errorMessage);
+    void handleFailedOutgoingCall(in ConnectionRequest request, int errorCode, String errorMessage);
 
     void setActive(String callId);
 
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index 8681344..604b895 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -18,6 +18,8 @@
 
 /**
  * Contains disconnect call causes generated by the framework and the RIL.
+ *
+ * @hide
  */
 public class DisconnectCause {
 
@@ -97,11 +99,62 @@
     public static final int CDMA_ACCESS_BLOCKED            = 35;
     /** Unknown error or not specified */
     public static final int ERROR_UNSPECIFIED              = 36;
+    /**
+     * Only emergency numbers are allowed, but we tried to dial
+     * a non-emergency number.
+     */
+    // TODO: This should be the same as NOT_EMERGENCY
+    public static final int EMERGENCY_ONLY                 = 37;
+    /**
+     * The supplied CALL Intent didn't contain a valid phone number.
+     */
+    public static final int NO_PHONE_NUMBER_SUPPLIED       = 38;
+    /**
+     * Our initial phone number was actually an MMI sequence.
+     */
+    public static final int DIALED_MMI                     = 39;
+    /**
+     * We tried to call a voicemail: URI but the device has no
+     * voicemail number configured.
+     */
+    public static final int VOICEMAIL_NUMBER_MISSING       = 40;
+    /**
+     * This status indicates that InCallScreen should display the
+     * CDMA-specific "call lost" dialog.  (If an outgoing call fails,
+     * and the CDMA "auto-retry" feature is enabled, *and* the retried
+     * call fails too, we display this specific dialog.)
+     *
+     * TODO: this is currently unused, since the "call lost" dialog
+     * needs to be triggered by a *disconnect* event, rather than when
+     * the InCallScreen first comes to the foreground.  For now we use
+     * the needToShowCallLostDialog field for this (see below.)
+     */
+    public static final int CDMA_CALL_LOST                 = 41;
+    /**
+     * This status indicates that the call was placed successfully,
+     * but additionally, the InCallScreen needs to display the
+     * "Exiting ECM" dialog.
+     *
+     * (Details: "Emergency callback mode" is a CDMA-specific concept
+     * where the phone disallows data connections over the cell
+     * network for some period of time after you make an emergency
+     * call.  If the phone is in ECM and you dial a non-emergency
+     * number, that automatically *cancels* ECM, but we additionally
+     * need to warn the user that ECM has been canceled (see bug
+     * 4207607.))
+     *
+     * TODO: Rethink where the best place to put this is. It is not a notification
+     * of a failure of the connection -- it is an additional message that accompanies
+     * a successful connection giving the user important information about what happened.
+     *
+     * {@hide}
+     */
+    public static final int EXITED_ECM                     = 42;
 
     /** Smallest valid value for call disconnect codes. */
     public static final int MINIMUM_VALID_VALUE = NOT_DISCONNECTED;
     /** Largest valid value for call disconnect codes. */
-    public static final int MAXIMUM_VALID_VALUE = ERROR_UNSPECIFIED;
+    public static final int MAXIMUM_VALID_VALUE = EXITED_ECM;
 
     /** Private constructor to avoid class instantiation. */
     private DisconnectCause() {
@@ -181,6 +234,18 @@
             return "CDMA_NOT_EMERGENCY";
         case CDMA_ACCESS_BLOCKED:
             return "CDMA_ACCESS_BLOCKED";
+        case EMERGENCY_ONLY:
+            return "EMERGENCY_ONLY";
+        case NO_PHONE_NUMBER_SUPPLIED:
+            return "NO_PHONE_NUMBER_SUPPLIED";
+        case DIALED_MMI:
+            return "DIALED_MMI";
+        case VOICEMAIL_NUMBER_MISSING:
+            return "VOICEMAIL_NUMBER_MISSING";
+        case CDMA_CALL_LOST:
+            return "CDMA_CALL_LOST";
+        case EXITED_ECM:
+            return "EXITED_ECM";
         case ERROR_UNSPECIFIED:
             return "ERROR_UNSPECIFIED";
         default:
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 9da032a..6996659 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -1742,16 +1742,14 @@
 
     /**
      * Checks if a given number is an emergency number for the country that the user is in.
-     *
-     * @param number the number to look up.
      * @param context the specific context which the number should be checked against
+     * @param number the number to look up.
+     *
      * @return true if the specified number is an emergency number for the country the user
      * is currently in.
      */
-    public static boolean isLocalEmergencyNumber(String number, Context context) {
-        return isLocalEmergencyNumberInternal(number,
-                                              context,
-                                              true /* useExactMatch */);
+    public static boolean isLocalEmergencyNumber(Context context, String number) {
+        return isLocalEmergencyNumberInternal(context, number, true /* useExactMatch */);
     }
 
     /**
@@ -1767,27 +1765,24 @@
      * This method is intended for internal use by the phone app when
      * deciding whether to allow ACTION_CALL intents from 3rd party apps
      * (where we're required to *not* allow emergency calls to be placed.)
-     *
-     * @param number the number to look up.
      * @param context the specific context which the number should be checked against
+     * @param number the number to look up.
+     *
      * @return true if the specified number is an emergency number for a local country, based on the
      *              CountryDetector.
      *
      * @see android.location.CountryDetector
      * @hide
      */
-    public static boolean isPotentialLocalEmergencyNumber(String number, Context context) {
-        return isLocalEmergencyNumberInternal(number,
-                                              context,
-                                              false /* useExactMatch */);
+    public static boolean isPotentialLocalEmergencyNumber(Context context, String number) {
+        return isLocalEmergencyNumberInternal(context, number, false /* useExactMatch */);
     }
 
     /**
      * Helper function for isLocalEmergencyNumber() and
      * isPotentialLocalEmergencyNumber().
-     *
-     * @param number the number to look up.
      * @param context the specific context which the number should be checked against
+     * @param number the number to look up.
      * @param useExactMatch if true, consider a number to be an emergency
      *           number only if it *exactly* matches a number listed in
      *           the RIL / SIM.  If false, a number is considered to be an
@@ -1799,9 +1794,8 @@
      *
      * @see android.location.CountryDetector
      */
-    private static boolean isLocalEmergencyNumberInternal(String number,
-                                                          Context context,
-                                                          boolean useExactMatch) {
+    private static boolean isLocalEmergencyNumberInternal(Context context, String number,
+            boolean useExactMatch) {
         String countryIso;
         CountryDetector detector = (CountryDetector) context.getSystemService(
                 Context.COUNTRY_DETECTOR);
diff --git a/telephony/java/com/android/internal/telephony/CallerInfo.java b/telephony/java/com/android/internal/telephony/CallerInfo.java
index f6143ed..f8dd7cf 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfo.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfo.java
@@ -276,7 +276,7 @@
         // Change the callerInfo number ONLY if it is an emergency number
         // or if it is the voicemail number.  If it is either, take a
         // shortcut and skip the query.
-        if (PhoneNumberUtils.isLocalEmergencyNumber(number, context)) {
+        if (PhoneNumberUtils.isLocalEmergencyNumber(context, number)) {
             return new CallerInfo().markAsEmergency(context);
         } else if (PhoneNumberUtils.isVoiceMailNumber(number)) {
             return new CallerInfo().markAsVoiceMail();
diff --git a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
index 74f73b5..34fed5e 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
@@ -399,7 +399,7 @@
         cw.number = number;
 
         // check to see if these are recognized numbers, and use shortcuts if we can.
-        if (PhoneNumberUtils.isLocalEmergencyNumber(number, context)) {
+        if (PhoneNumberUtils.isLocalEmergencyNumber(context, number)) {
             cw.event = EVENT_EMERGENCY_NUMBER;
         } else if (PhoneNumberUtils.isVoiceMailNumber(number)) {
             cw.event = EVENT_VOICEMAIL_NUMBER;