Merge "More granular reporting of size configurations."
diff --git a/Android.mk b/Android.mk
index 5713f3e..f0c0117 100644
--- a/Android.mk
+++ b/Android.mk
@@ -68,6 +68,8 @@
 	core/java/android/app/IActivityContainerCallback.aidl \
 	core/java/android/app/IActivityController.aidl \
 	core/java/android/app/IActivityPendingResult.aidl \
+	core/java/android/app/IAlarmCompleteListener.aidl \
+	core/java/android/app/IAlarmListener.aidl \
 	core/java/android/app/IAlarmManager.aidl \
 	core/java/android/app/IAppTask.aidl \
 	core/java/android/app/ITaskStackListener.aidl \
diff --git a/api/current.txt b/api/current.txt
index eb888af..0bbf2f9 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3764,17 +3764,21 @@
 
   public class AlarmManager {
     method public void cancel(android.app.PendingIntent);
+    method public void cancel(android.app.AlarmManager.OnAlarmListener);
     method public android.app.AlarmManager.AlarmClockInfo getNextAlarmClock();
     method public void set(int, long, android.app.PendingIntent);
+    method public void set(int, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
     method public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent);
     method public void setAndAllowWhileIdle(int, long, android.app.PendingIntent);
     method public void setExact(int, long, android.app.PendingIntent);
+    method public void setExact(int, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
     method public void setExactAndAllowWhileIdle(int, long, android.app.PendingIntent);
     method public void setInexactRepeating(int, long, long, android.app.PendingIntent);
     method public void setRepeating(int, long, long, android.app.PendingIntent);
     method public void setTime(long);
     method public void setTimeZone(java.lang.String);
     method public void setWindow(int, long, long, android.app.PendingIntent);
+    method public void setWindow(int, long, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
     field public static final java.lang.String ACTION_NEXT_ALARM_CLOCK_CHANGED = "android.app.action.NEXT_ALARM_CLOCK_CHANGED";
     field public static final int ELAPSED_REALTIME = 3; // 0x3
     field public static final int ELAPSED_REALTIME_WAKEUP = 2; // 0x2
@@ -3796,6 +3800,10 @@
     field public static final android.os.Parcelable.Creator<android.app.AlarmManager.AlarmClockInfo> CREATOR;
   }
 
+  public static abstract interface AlarmManager.OnAlarmListener {
+    method public abstract void onAlarm();
+  }
+
   public class AlertDialog extends android.app.Dialog implements android.content.DialogInterface {
     ctor protected AlertDialog(android.content.Context);
     ctor protected AlertDialog(android.content.Context, boolean, android.content.DialogInterface.OnCancelListener);
@@ -5881,6 +5889,7 @@
     method public java.lang.String getStructuredData();
     method public android.net.Uri getWebUri();
     method public boolean isAppProvidedIntent();
+    method public boolean isAppProvidedWebUri();
     method public void setClipData(android.content.ClipData);
     method public void setIntent(android.content.Intent);
     method public void setStructuredData(java.lang.String);
diff --git a/api/system-current.txt b/api/system-current.txt
index d288ad1..c5ad20d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3873,18 +3873,22 @@
 
   public class AlarmManager {
     method public void cancel(android.app.PendingIntent);
+    method public void cancel(android.app.AlarmManager.OnAlarmListener);
     method public android.app.AlarmManager.AlarmClockInfo getNextAlarmClock();
     method public void set(int, long, android.app.PendingIntent);
+    method public void set(int, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
     method public void set(int, long, long, long, android.app.PendingIntent, android.os.WorkSource);
     method public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent);
     method public void setAndAllowWhileIdle(int, long, android.app.PendingIntent);
     method public void setExact(int, long, android.app.PendingIntent);
+    method public void setExact(int, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
     method public void setExactAndAllowWhileIdle(int, long, android.app.PendingIntent);
     method public void setInexactRepeating(int, long, long, android.app.PendingIntent);
     method public void setRepeating(int, long, long, android.app.PendingIntent);
     method public void setTime(long);
     method public void setTimeZone(java.lang.String);
     method public void setWindow(int, long, long, android.app.PendingIntent);
+    method public void setWindow(int, long, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
     field public static final java.lang.String ACTION_NEXT_ALARM_CLOCK_CHANGED = "android.app.action.NEXT_ALARM_CLOCK_CHANGED";
     field public static final int ELAPSED_REALTIME = 3; // 0x3
     field public static final int ELAPSED_REALTIME_WAKEUP = 2; // 0x2
@@ -3906,6 +3910,10 @@
     field public static final android.os.Parcelable.Creator<android.app.AlarmManager.AlarmClockInfo> CREATOR;
   }
 
+  public static abstract interface AlarmManager.OnAlarmListener {
+    method public abstract void onAlarm();
+  }
+
   public class AlertDialog extends android.app.Dialog implements android.content.DialogInterface {
     ctor protected AlertDialog(android.content.Context);
     ctor protected AlertDialog(android.content.Context, boolean, android.content.DialogInterface.OnCancelListener);
@@ -6014,6 +6022,7 @@
     method public java.lang.String getStructuredData();
     method public android.net.Uri getWebUri();
     method public boolean isAppProvidedIntent();
+    method public boolean isAppProvidedWebUri();
     method public void setClipData(android.content.ClipData);
     method public void setIntent(android.content.Intent);
     method public void setStructuredData(java.lang.String);
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index d46bab2..da743f8 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -116,21 +116,22 @@
     static public void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg,
             String tag) {
         try {
-            getDefault().noteWakeupAlarm(ps.getTarget(), sourceUid, sourcePkg, tag);
+            getDefault().noteWakeupAlarm((ps != null) ? ps.getTarget() : null,
+                    sourceUid, sourcePkg, tag);
         } catch (RemoteException ex) {
         }
     }
 
     static public void noteAlarmStart(PendingIntent ps, int sourceUid, String tag) {
         try {
-            getDefault().noteAlarmStart(ps.getTarget(), sourceUid, tag);
+            getDefault().noteAlarmStart((ps != null) ? ps.getTarget() : null, sourceUid, tag);
         } catch (RemoteException ex) {
         }
     }
 
     static public void noteAlarmFinish(PendingIntent ps, int sourceUid, String tag) {
         try {
-            getDefault().noteAlarmFinish(ps.getTarget(), sourceUid, tag);
+            getDefault().noteAlarmFinish((ps != null) ? ps.getTarget() : null, sourceUid, tag);
         } catch (RemoteException ex) {
         }
     }
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
index fb03b62..3dc9582 100644
--- a/core/java/android/app/AlarmManager.java
+++ b/core/java/android/app/AlarmManager.java
@@ -21,15 +21,21 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.Build;
+import android.os.Handler;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.text.TextUtils;
+import android.util.Log;
+
 import libcore.util.ZoneInfoDB;
 
 import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.WeakHashMap;
 
 /**
  * This class provides access to the system alarm services.  These allow you
@@ -72,6 +78,8 @@
  * Context.getSystemService(Context.ALARM_SERVICE)}.
  */
 public class AlarmManager {
+    private static final String TAG = "AlarmManager";
+
     /**
      * Alarm time in {@link System#currentTimeMillis System.currentTimeMillis()}
      * (wall clock time in UTC), which will wake up the device when
@@ -160,9 +168,89 @@
     public static final int FLAG_IDLE_UNTIL = 1<<4;
 
     private final IAlarmManager mService;
+    private final String mPackageName;
     private final boolean mAlwaysExact;
     private final int mTargetSdkVersion;
+    private final Handler mMainThreadHandler;
 
+    /**
+     * Direct-notification alarms: the requester must be running continuously from the
+     * time the alarm is set to the time it is delivered, or delivery will fail.  Only
+     * one-shot alarms can be set using this mechanism, not repeating alarms.
+     */
+    public interface OnAlarmListener {
+        /**
+         * Callback method that is invoked by the system when the alarm time is reached.
+         */
+        public void onAlarm();
+    }
+
+    final class ListenerWrapper extends IAlarmListener.Stub implements Runnable {
+        final OnAlarmListener mListener;
+        Handler mHandler;
+        IAlarmCompleteListener mCompletion;
+
+        public ListenerWrapper(OnAlarmListener listener) {
+            mListener = listener;
+        }
+
+        public void setHandler(Handler h) {
+           mHandler = h;
+        }
+
+        public void cancel() {
+            try {
+                mService.remove(null, this);
+            } catch (RemoteException ex) {
+            }
+
+            synchronized (AlarmManager.class) {
+                if (sWrappers != null) {
+                    sWrappers.remove(mListener);
+                }
+            }
+        }
+
+        @Override
+        public void doAlarm(IAlarmCompleteListener alarmManager) {
+            mCompletion = alarmManager;
+            mHandler.post(this);
+        }
+
+        @Override
+        public void run() {
+            // Remove this listener from the wrapper cache first; the server side
+            // already considers it gone
+            synchronized (AlarmManager.class) {
+                if (sWrappers != null) {
+                    sWrappers.remove(mListener);
+                }
+            }
+
+            // Now deliver it to the app
+            try {
+                mListener.onAlarm();
+            } finally {
+                // No catch -- make sure to report completion to the system process,
+                // but continue to allow the exception to crash the app.
+
+                try {
+                    mCompletion.alarmComplete(this);
+                } catch (Exception e) {
+                    Log.e(TAG, "Unable to report completion to Alarm Manager!", e);
+                }
+            }
+        }
+    }
+
+    // Tracking of the OnAlarmListener -> wrapper mapping, for cancel() support.
+    // Access is synchronized on the AlarmManager class object.
+    //
+    // These are weak references so that we don't leak listener references if, for
+    // example, the pending-alarm messages are posted to a HandlerThread that is
+    // disposed of prior to alarm delivery.  The underlying messages will be GC'd
+    // but this static reference would still persist, orphaned, never deallocated.
+    private static WeakHashMap<OnAlarmListener, WeakReference<ListenerWrapper>> sWrappers;
 
     /**
      * package private on purpose
@@ -170,8 +258,10 @@
     AlarmManager(IAlarmManager service, Context ctx) {
         mService = service;
 
+        mPackageName = ctx.getPackageName();
         mTargetSdkVersion = ctx.getApplicationInfo().targetSdkVersion;
         mAlwaysExact = (mTargetSdkVersion < Build.VERSION_CODES.KITKAT);
+        mMainThreadHandler = new Handler(ctx.getMainLooper());
     }
 
     private long legacyExactLength() {
@@ -249,7 +339,37 @@
      * @see #RTC_WAKEUP
      */
     public void set(int type, long triggerAtMillis, PendingIntent operation) {
-        setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null);
+        setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null,
+                null, null, null);
+    }
+
+    /**
+     * Direct callback version of {@link #set(int, long, PendingIntent)}.  Rather than
+     * supplying a PendingIntent to be sent when the alarm time is reached, this variant
+     * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+     * <p>
+     * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+     * invoked via the specified target Handler, or on the application's main looper
+     * if {@code null} is passed as the {@code targetHandler} parameter.
+     *
+     * @param type One of {@link #ELAPSED_REALTIME}, {@link #ELAPSED_REALTIME_WAKEUP},
+     *         {@link #RTC}, or {@link #RTC_WAKEUP}.
+     * @param triggerAtMillis time in milliseconds that the alarm should go
+     *         off, using the appropriate clock (depending on the alarm type).
+     * @param tag string describing the alarm, used for logging and battery-use
+     *         attribution
+     * @param listener {@link OnAlarmListener} instance whose
+     *         {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+     *         called when the alarm time is reached.  A given OnAlarmListener instance can
+     *         only be the target of a single pending alarm, just as a given PendingIntent
+     *         can only be used with one alarm at a time.
+     * @param targetHandler {@link Handler} on which to execute the listener's onAlarm()
+     *         callback, or {@code null} to run that callback on the main looper.
+     */
+    public void set(int type, long triggerAtMillis, String tag, OnAlarmListener listener,
+            Handler targetHandler) {
+        setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, null, listener, tag,
+                targetHandler, null, null);
     }
 
     /**
@@ -310,8 +430,8 @@
      */
     public void setRepeating(int type, long triggerAtMillis,
             long intervalMillis, PendingIntent operation) {
-        setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, 0, operation, null,
-                null);
+        setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, 0, operation,
+                null, null, null, null, null);
     }
 
     /**
@@ -361,7 +481,23 @@
      */
     public void setWindow(int type, long windowStartMillis, long windowLengthMillis,
             PendingIntent operation) {
-        setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, operation, null, null);
+        setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, operation,
+                null, null, null, null, null);
+    }
+
+    /**
+     * Direct callback version of {@link #setWindow(int, long, long, PendingIntent)}.  Rather
+     * than supplying a PendingIntent to be sent when the alarm time is reached, this variant
+     * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+     * <p>
+     * The OnAlarmListener {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+     * invoked via the specified target Handler, or on the application's main looper
+     * if {@code null} is passed as the {@code targetHandler} parameter.
+     */
+    public void setWindow(int type, long windowStartMillis, long windowLengthMillis,
+            String tag, OnAlarmListener listener, Handler targetHandler) {
+        setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, null, listener, tag,
+                targetHandler, null, null);
     }
 
     /**
@@ -399,7 +535,23 @@
      * @see #RTC_WAKEUP
      */
     public void setExact(int type, long triggerAtMillis, PendingIntent operation) {
-        setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null);
+        setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null, null,
+                null, null);
+    }
+
+    /**
+     * Direct callback version of {@link #setExact(int, long, PendingIntent)}.  Rather
+     * than supplying a PendingIntent to be sent when the alarm time is reached, this variant
+     * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+     * <p>
+     * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+     * invoked via the specified target Handler, or on the application's main looper
+     * if {@code null} is passed as the {@code targetHandler} parameter.
+     */
+    public void setExact(int type, long triggerAtMillis, String tag, OnAlarmListener listener,
+            Handler targetHandler) {
+        setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, null, listener, tag,
+                targetHandler, null, null);
     }
 
     /**
@@ -408,7 +560,8 @@
      * @hide
      */
     public void setIdleUntil(int type, long triggerAtMillis, PendingIntent operation) {
-        setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_IDLE_UNTIL, operation, null, null);
+        setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_IDLE_UNTIL, operation,
+                null, null, null, null, null);
     }
 
     /**
@@ -436,19 +589,38 @@
      * @see android.content.Intent#filterEquals
      */
     public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) {
-        setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation, null, info);
+        setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation,
+                null, null, null, null, info);
     }
 
     /** @hide */
     @SystemApi
     public void set(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
             PendingIntent operation, WorkSource workSource) {
-        setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, operation, workSource,
-                null);
+        setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, operation, null, null,
+                null, workSource, null);
+    }
+
+    /**
+     * Direct callback version of {@link #set(int, long, long, long, PendingIntent, WorkSource)}.
+     * Note that repeating alarms must use the PendingIntent variant, not an OnAlarmListener.
+     * <p>
+     * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+     * invoked via the specified target Handler, or on the application's main looper
+     * if {@code null} is passed as the {@code targetHandler} parameter.
+     *
+     * @hide
+     */
+    //@SystemApi
+    public void set(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
+            OnAlarmListener listener, Handler targetHandler, WorkSource workSource) {
+        setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener, null,
+                targetHandler, workSource, null);
     }
 
     private void setImpl(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
-            int flags, PendingIntent operation, WorkSource workSource, AlarmClockInfo alarmClock) {
+            int flags, PendingIntent operation, final OnAlarmListener listener, String listenerTag,
+            Handler targetHandler, WorkSource workSource, AlarmClockInfo alarmClock) {
         if (triggerAtMillis < 0) {
             /* NOTYET
             if (mAlwaysExact) {
@@ -460,9 +632,30 @@
             triggerAtMillis = 0;
         }
 
+        ListenerWrapper recipientWrapper = null;
+        if (listener != null) {
+            synchronized (AlarmManager.class) {
+                if (sWrappers == null) {
+                    sWrappers = new WeakHashMap<OnAlarmListener, WeakReference<ListenerWrapper>>();
+                }
+
+                WeakReference<ListenerWrapper> wrapperRef = sWrappers.get(listener);
+                // no existing wrapper *or* we've lost our weak ref to it => build a new one
+                if (wrapperRef == null ||
+                        (recipientWrapper = wrapperRef.get()) == null) {
+                    recipientWrapper = new ListenerWrapper(listener);
+                    wrapperRef = new WeakReference<ListenerWrapper>(recipientWrapper);
+                    sWrappers.put(listener, wrapperRef);
+                }
+            }
+
+            final Handler handler = (targetHandler != null) ? targetHandler : mMainThreadHandler;
+            recipientWrapper.setHandler(handler);
+        }
+
         try {
-            mService.set(type, triggerAtMillis, windowMillis, intervalMillis, flags, operation,
-                    workSource, alarmClock);
+            mService.set(mPackageName, type, triggerAtMillis, windowMillis, intervalMillis, flags,
+                    operation, recipientWrapper, listenerTag, workSource, alarmClock);
         } catch (RemoteException ex) {
         }
     }
@@ -562,7 +755,8 @@
      */
     public void setInexactRepeating(int type, long triggerAtMillis,
             long intervalMillis, PendingIntent operation) {
-        setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, 0, operation, null, null);
+        setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, 0, operation, null,
+                null, null, null, null);
     }
 
     /**
@@ -611,8 +805,8 @@
      * @see #RTC_WAKEUP
      */
     public void setAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) {
-        setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE, operation,
-                null, null);
+        setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE,
+                operation, null, null, null, null, null);
     }
 
     /**
@@ -666,7 +860,7 @@
      */
     public void setExactAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) {
         setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, operation,
-                null, null);
+                null, null, null, null, null);
     }
 
     /**
@@ -680,13 +874,44 @@
      * @see #set
      */
     public void cancel(PendingIntent operation) {
+        if (operation == null) {
+            throw new NullPointerException("operation");
+        }
+
         try {
-            mService.remove(operation);
+            mService.remove(operation, null);
         } catch (RemoteException ex) {
         }
     }
 
     /**
+     * Remove any alarm scheduled to be delivered to the given {@link OnAlarmListener}.
+     *
+     * @param listener OnAlarmListener instance that is the target of a currently-set alarm.
+     */
+    public void cancel(OnAlarmListener listener) {
+        if (listener == null) {
+            throw new NullPointerException("listener");
+        }
+
+        ListenerWrapper wrapper = null;
+        synchronized (AlarmManager.class) {
+            final WeakReference<ListenerWrapper> wrapperRef;
+            wrapperRef = sWrappers.get(listener);
+            if (wrapperRef != null) {
+                wrapper = wrapperRef.get();
+            }
+        }
+
+        if (wrapper == null) {
+            Log.w(TAG, "Unrecognized alarm listener " + listener);
+            return;
+        }
+
+        wrapper.cancel();
+    }
+
+    /**
      * Set the system wall clock time.
      * Requires the permission android.permission.SET_TIME.
      *
diff --git a/core/java/android/app/IAlarmCompleteListener.aidl b/core/java/android/app/IAlarmCompleteListener.aidl
new file mode 100644
index 0000000..9f9ee40
--- /dev/null
+++ b/core/java/android/app/IAlarmCompleteListener.aidl
@@ -0,0 +1,27 @@
+/*
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+package android.app;
+
+import android.os.IBinder;
+
+/**
+ * Callback from app into system process to indicate that processing of
+ * a direct-call alarm has completed.
+ * {@hide}
+ */
+interface IAlarmCompleteListener {
+    void alarmComplete(in IBinder who);
+}
diff --git a/core/java/android/app/IAlarmListener.aidl b/core/java/android/app/IAlarmListener.aidl
new file mode 100644
index 0000000..a110d4d
--- /dev/null
+++ b/core/java/android/app/IAlarmListener.aidl
@@ -0,0 +1,29 @@
+/*
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+package android.app;
+
+import android.app.IAlarmCompleteListener;
+
+/**
+ * System private API for direct alarm callbacks (non-broadcast deliver).  See the
+ * AlarmManager#set() variants that take an AlarmReceiver callback object
+ * rather than a PendingIntent.
+ *
+ * {@hide}
+ */
+oneway interface IAlarmListener {
+    void doAlarm(in IAlarmCompleteListener callback);
+}
diff --git a/core/java/android/app/IAlarmManager.aidl b/core/java/android/app/IAlarmManager.aidl
index 327c00b..7b05b49 100644
--- a/core/java/android/app/IAlarmManager.aidl
+++ b/core/java/android/app/IAlarmManager.aidl
@@ -17,7 +17,9 @@
 package android.app;
 
 import android.app.AlarmManager;
+import android.app.IAlarmListener;
 import android.app.PendingIntent;
+import android.content.Intent;
 import android.os.WorkSource;
 
 /**
@@ -27,14 +29,12 @@
  */
 interface IAlarmManager {
 	/** windowLength == 0 means exact; windowLength < 0 means the let the OS decide */
-    void set(int type, long triggerAtTime, long windowLength,
-            long interval, int flags, in PendingIntent operation, in WorkSource workSource,
-            in AlarmManager.AlarmClockInfo alarmClock);
+    void set(String callingPackage, int type, long triggerAtTime, long windowLength,
+            long interval, int flags, in PendingIntent operation, in IAlarmListener listener,
+            String listenerTag, in WorkSource workSource, in AlarmManager.AlarmClockInfo alarmClock);
     boolean setTime(long millis);
     void setTimeZone(String zone);
-    void remove(in PendingIntent operation);
+    void remove(in PendingIntent operation, in IAlarmListener listener);
     long getNextWakeFromIdleTime();
     AlarmManager.AlarmClockInfo getNextAlarmClock(int userId);
 }
-
-
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index e95a35a..30232da 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -83,10 +83,8 @@
 
     int getZenMode();
     ZenModeConfig getZenModeConfig();
-    boolean setZenModeConfig(in ZenModeConfig config, String reason);
     oneway void setZenMode(int mode, in Uri conditionId, String reason);
     oneway void notifyConditions(String pkg, in IConditionProvider provider, in Condition[] conditions);
-    oneway void requestZenModeConditions(in IConditionListener callback, int relevance);
     boolean isNotificationPolicyAccessGranted(String pkg);
     NotificationManager.Policy getNotificationPolicy(String pkg);
     void setNotificationPolicy(String pkg, in NotificationManager.Policy policy);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index f75b22a..07b4d39 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -348,29 +348,6 @@
     /**
      * @hide
      */
-    public boolean setZenModeConfig(ZenModeConfig config, String reason) {
-        INotificationManager service = getService();
-        try {
-            return service.setZenModeConfig(config, reason);
-        } catch (RemoteException e) {
-            return false;
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public void requestZenModeConditions(IConditionListener listener, int relevance) {
-        INotificationManager service = getService();
-        try {
-            service.requestZenModeConditions(listener, relevance);
-        } catch (RemoteException e) {
-        }
-    }
-
-    /**
-     * @hide
-     */
     public int getZenMode() {
         INotificationManager service = getService();
         try {
diff --git a/core/java/android/app/assist/AssistContent.java b/core/java/android/app/assist/AssistContent.java
index cddf47a..39902d7 100644
--- a/core/java/android/app/assist/AssistContent.java
+++ b/core/java/android/app/assist/AssistContent.java
@@ -14,6 +14,7 @@
  */
 public class AssistContent implements Parcelable {
     private boolean mIsAppProvidedIntent = false;
+    private boolean mIsAppProvidedWebUri = false;
     private Intent mIntent;
     private String mStructuredData;
     private ClipData mClipData;
@@ -39,7 +40,7 @@
             Uri uri = intent.getData();
             if (uri != null) {
                 if ("http".equals(uri.getScheme()) || "https".equals(uri.getScheme())) {
-                    setWebUri(uri);
+                    mUri = uri;
                 }
             }
         }
@@ -116,6 +117,7 @@
      * leave the null and only report the local intent and clip data.
      */
     public void setWebUri(Uri uri) {
+        mIsAppProvidedWebUri = true;
         mUri = uri;
     }
 
@@ -128,6 +130,16 @@
     }
 
     /**
+     * Returns whether or not the current {@link #getWebUri} was explicitly provided in
+     * {@link android.app.Activity#onProvideAssistContent Activity.onProvideAssistContent}. If not,
+     * the Intent was automatically set based on
+     * {@link android.app.Activity#getIntent Activity.getIntent}.
+     */
+    public boolean isAppProvidedWebUri() {
+        return mIsAppProvidedWebUri;
+    }
+
+    /**
      * Return Bundle for extra vendor-specific data that can be modified and examined.
      */
     public Bundle getExtras() {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 6740d43..d7e94d0 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1658,6 +1658,14 @@
             = "android.intent.extra.GET_PERMISSIONS_APP_LABEL_LIST_RESULT";
 
     /**
+     * Boolean list describing if the app is a system app for apps that have one or more runtime
+     * permissions.
+     * @hide
+     */
+    public static final String EXTRA_GET_PERMISSIONS_IS_SYSTEM_APP_LIST_RESULT
+            = "android.intent.extra.GET_PERMISSIONS_IS_SYSTEM_APP_LIST_RESULT";
+
+    /**
      * Required extra to be sent with {@link #ACTION_GET_PERMISSIONS_COUNT} broadcasts.
      * @hide
      */
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 17e1a28..2e31ab6 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -533,14 +533,9 @@
     /** @hide */
     public static final int PIN_VERIFICATION_SUCCESS = -1;
 
-    private static UserManager sInstance = null;
-
     /** @hide */
-    public synchronized static UserManager get(Context context) {
-        if (sInstance == null) {
-            sInstance = (UserManager) context.getSystemService(Context.USER_SERVICE);
-        }
-        return sInstance;
+    public static UserManager get(Context context) {
+        return (UserManager) context.getSystemService(Context.USER_SERVICE);
     }
 
     /** @hide */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a1f9743..ad46c3d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5604,11 +5604,6 @@
         public static final String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES =
                 "enabled_notification_policy_access_packages";
 
-        /**
-         * @hide
-         */
-        public static final String ENABLED_CONDITION_PROVIDERS = "enabled_condition_providers";
-
         /** @hide */
         public static final String BAR_SERVICE_COMPONENT = "bar_service_component";
 
diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java
index c679eda..bbac023 100644
--- a/core/java/android/service/notification/ConditionProviderService.java
+++ b/core/java/android/service/notification/ConditionProviderService.java
@@ -35,7 +35,8 @@
  * the {@link android.Manifest.permission#BIND_CONDITION_PROVIDER_SERVICE} permission
  * and include an intent filter with the {@link #SERVICE_INTERFACE} action. If you want users to be
  * able to create and update conditions for this service to monitor, include the
- * {@link #META_DATA_RULE_TYPE} and {@link #META_DATA_CONFIGURATION_ACTIVITY} tags. For example:</p>
+ * {@link #META_DATA_RULE_TYPE} and {@link #META_DATA_CONFIGURATION_ACTIVITY} tags and request the
+ * {@link android.Manifest.permission#ACCESS_NOTIFICATION_POLICY} permission. For example:</p>
  * <pre>
  * &lt;service android:name=".MyConditionProvider"
  *          android:label="&#64;string/service_name"
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 4a1d7e7..c9586e4 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -42,15 +42,14 @@
 static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds
 static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds
 
-// Time to wait between animation frames.
-static const nsecs_t ANIMATION_FRAME_INTERVAL = 1000000000LL / 60;
-
 // Time to spend fading out the spot completely.
 static const nsecs_t SPOT_FADE_DURATION = 200 * 1000000LL; // 200 ms
 
 // Time to spend fading out the pointer completely.
 static const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
 
+// The number of events to be read at once for DisplayEventReceiver.
+static const int EVENT_BUFFER_SIZE = 100;
 
 // --- PointerController ---
 
@@ -59,6 +58,13 @@
         mPolicy(policy), mLooper(looper), mSpriteController(spriteController) {
     mHandler = new WeakMessageHandler(this);
 
+    if (mDisplayEventReceiver.initCheck() == NO_ERROR) {
+        mLooper->addFd(mDisplayEventReceiver.getFd(), Looper::POLL_CALLBACK,
+                       Looper::EVENT_INPUT, this, nullptr);
+    } else {
+        ALOGE("Failed to initialize DisplayEventReceiver.");
+    }
+
     AutoMutex _l(mLock);
 
     mLocked.animationPending = false;
@@ -416,21 +422,49 @@
 
 void PointerController::handleMessage(const Message& message) {
     switch (message.what) {
-    case MSG_ANIMATE:
-        doAnimate();
-        break;
     case MSG_INACTIVITY_TIMEOUT:
         doInactivityTimeout();
         break;
     }
 }
 
-void PointerController::doAnimate() {
+int PointerController::handleEvent(int /* fd */, int events, void* /* data */) {
+    if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
+        ALOGE("Display event receiver pipe was closed or an error occurred.  "
+              "events=0x%x", events);
+        return 0; // remove the callback
+    }
+
+    if (!(events & Looper::EVENT_INPUT)) {
+        ALOGW("Received spurious callback for unhandled poll event.  "
+              "events=0x%x", events);
+        return 1; // keep the callback
+    }
+
+    bool gotVsync = false;
+    ssize_t n;
+    nsecs_t timestamp;
+    DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
+    while ((n = mDisplayEventReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
+        for (size_t i = 0; i < static_cast<size_t>(n); ++i) {
+            if (buf[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
+                timestamp = buf[i].header.timestamp;
+                gotVsync = true;
+            }
+        }
+    }
+    if (gotVsync) {
+        doAnimate(timestamp);
+    }
+    return 1;  // keep the callback
+}
+
+void PointerController::doAnimate(nsecs_t timestamp) {
     AutoMutex _l(mLock);
 
     bool keepAnimating = false;
     mLocked.animationPending = false;
-    nsecs_t frameDelay = systemTime(SYSTEM_TIME_MONOTONIC) - mLocked.animationTime;
+    nsecs_t frameDelay = timestamp - mLocked.animationTime;
 
     // Animate pointer fade.
     if (mLocked.pointerFadeDirection < 0) {
@@ -481,7 +515,7 @@
     if (!mLocked.animationPending) {
         mLocked.animationPending = true;
         mLocked.animationTime = systemTime(SYSTEM_TIME_MONOTONIC);
-        mLooper->sendMessageDelayed(ANIMATION_FRAME_INTERVAL, mHandler, Message(MSG_ANIMATE));
+        mDisplayEventReceiver.requestNextVsync();
     }
 }
 
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 24a1681..6d840db 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -28,6 +28,7 @@
 #include <utils/RefBase.h>
 #include <utils/Looper.h>
 #include <utils/String8.h>
+#include <gui/DisplayEventReceiver.h>
 
 #include <SkBitmap.h>
 
@@ -68,7 +69,8 @@
  *
  * Handles pointer acceleration and animation.
  */
-class PointerController : public PointerControllerInterface, public MessageHandler {
+class PointerController : public PointerControllerInterface, public MessageHandler,
+                          public LooperCallback {
 protected:
     virtual ~PointerController();
 
@@ -106,7 +108,6 @@
     static const size_t MAX_SPOTS = 12;
 
     enum {
-        MSG_ANIMATE,
         MSG_INACTIVITY_TIMEOUT,
     };
 
@@ -136,6 +137,8 @@
     sp<SpriteController> mSpriteController;
     sp<WeakMessageHandler> mHandler;
 
+    DisplayEventReceiver mDisplayEventReceiver;
+
     PointerResources mResources;
 
     struct Locked {
@@ -173,7 +176,8 @@
     void setPositionLocked(float x, float y);
 
     void handleMessage(const Message& message);
-    void doAnimate();
+    int handleEvent(int fd, int events, void* data);
+    void doAnimate(nsecs_t timestamp);
     void doInactivityTimeout();
 
     void startAnimationLocked();
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index f9e8027..d45345e 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -40,7 +40,7 @@
 
         <activity
             android:name=".ManageRootActivity"
-            android:theme="@style/DocumentsNonDialogTheme"
+            android:theme="@style/DocumentsFullScreenTheme"
             android:icon="@drawable/ic_doc_text">
             <intent-filter>
                 <action android:name="android.provider.action.MANAGE_ROOT" />
@@ -63,7 +63,7 @@
 
         <activity
             android:name=".FilesActivity"
-            android:theme="@style/FilesTheme"
+            android:theme="@style/DocumentsFullScreenTheme"
             android:icon="@drawable/ic_files_app"
             android:label="@string/files_label"
             android:documentLaunchMode="intoExisting">
diff --git a/packages/DocumentsUI/res/color/item_doc_grid_overlay.xml b/packages/DocumentsUI/res/color/item_doc_grid_overlay.xml
index ab414a9..bf19d4e 100644
--- a/packages/DocumentsUI/res/color/item_doc_grid_overlay.xml
+++ b/packages/DocumentsUI/res/color/item_doc_grid_overlay.xml
@@ -16,10 +16,6 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item
-        android:state_focused="true"
-        android:color="@color/platform_blue_a200"
-        android:alpha="0.1" />
-    <item
         android:state_activated="true"
         android:color="?android:attr/colorAccent"
         android:alpha="0.1" />
diff --git a/packages/DocumentsUI/res/values-sw720dp/styles.xml b/packages/DocumentsUI/res/values-sw720dp/styles.xml
index d415a84..a8dcbb0 100644
--- a/packages/DocumentsUI/res/values-sw720dp/styles.xml
+++ b/packages/DocumentsUI/res/values-sw720dp/styles.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <style name="DocumentsBaseTheme" parent="@style/Theme.AppCompat.Dialog">
+    <style name="DocumentsBaseTheme" parent="@style/Theme.AppCompat.Light.Dialog">
         <!-- We do not specify width of window here because the max size of
              floating window specified by windowFixedWidthis is limited. -->
         <item name="*android:windowFixedHeightMajor">80%</item>
diff --git a/packages/DocumentsUI/res/values/colors.xml b/packages/DocumentsUI/res/values/colors.xml
index cb6957d..a376418 100644
--- a/packages/DocumentsUI/res/values/colors.xml
+++ b/packages/DocumentsUI/res/values/colors.xml
@@ -15,23 +15,15 @@
 -->
 
 <resources>
-    <color name="material_grey_50">#fffafafa</color>
-    <color name="material_grey_300">#ffeeeeee</color>
-    <color name="material_grey_600">#ff757575</color>
-    <color name="material_grey_800">#ff424242</color>
+    <color name="material_grey_400">#ffbdbdbd</color>
 
-    <color name="primary_dark">@*android:color/material_blue_grey_900</color>
-    <color name="primary">@*android:color/material_blue_grey_800</color>
-    <color name="accent">@*android:color/material_deep_teal_500</color>
-
-    <color name="platform_blue_100">#ffd0d9ff</color>
-    <color name="platform_blue_500">#ff5677fc</color>
-    <color name="platform_blue_700">#ff455ede</color>
-    <color name="platform_blue_a100">#ffa6baff</color>
-    <color name="platform_blue_a200">#ffff5177</color>
+    <color name="primary_dark">@*android:color/primary_dark_material_dark</color>
+    <color name="primary">@*android:color/material_blue_grey_900</color>
+    <color name="accent">@*android:color/accent_material_light</color>
+    <color name="action_mode">@color/material_grey_400</color>
     
-    <color name="directory_background">@color/material_grey_300</color>
-    <color name="item_doc_grid_background">#FFFFFFFF</color>
+    <color name="directory_background">@*android:color/material_grey_300</color>
+    <color name="item_doc_grid_background">@android:color/white</color>
     <color name="item_doc_grid_protect_background">#88000000</color>
     <color name="band_select_background">#88ffffff</color>
     <color name="band_select_border">#44000000</color>
diff --git a/packages/DocumentsUI/res/values/styles.xml b/packages/DocumentsUI/res/values/styles.xml
index c13f144..15d17cc 100644
--- a/packages/DocumentsUI/res/values/styles.xml
+++ b/packages/DocumentsUI/res/values/styles.xml
@@ -28,7 +28,7 @@
         <item name="android:colorPrimaryDark">@color/primary_dark</item>
         <item name="android:colorPrimary">@color/primary</item>
         <item name="android:colorAccent">@color/accent</item>
-        <item name="colorActionMode">@color/material_grey_800</item>
+        <item name="colorActionMode">@color/action_mode</item>
 
         <item name="android:listDivider">@*android:drawable/list_divider_material</item>
 
@@ -37,14 +37,17 @@
         <item name="android:windowNoTitle">true</item>
 
         <item name="android:windowSoftInputMode">stateUnspecified|adjustUnspecified</item>
-        <item name="android:alertDialogTheme">@android:style/Theme.Material.Light.Dialog.Alert</item>
     </style>
 
-    <style name="DocumentsBaseTheme.FullScreen" parent="@style/Theme.AppCompat.Light.DarkActionBar">
+    <style name="DocumentsFullScreenTheme" parent="@style/Theme.AppCompat.Light.DarkActionBar">
         <item name="actionBarWidgetTheme">@null</item>
         <item name="actionBarTheme">@style/ActionBarTheme</item>
         <item name="actionBarPopupTheme">@style/ActionBarPopupTheme</item>
-        <item name="colorActionMode">@color/material_grey_800</item>
+
+        <item name="android:colorPrimaryDark">@color/primary_dark</item>
+        <item name="android:colorPrimary">@color/primary</item>
+        <item name="android:colorAccent">@color/accent</item>
+        <item name="colorActionMode">@color/action_mode</item>
 
         <item name="android:listDivider">@*android:drawable/list_divider_material</item>
 
@@ -55,38 +58,6 @@
         <item name="android:windowSoftInputMode">stateUnspecified|adjustUnspecified</item>
     </style>
 
-    <style name="DocumentsNonDialogTheme" parent="@style/DocumentsBaseTheme.FullScreen">
-        <item name="android:colorPrimaryDark">@color/primary_dark</item>
-        <item name="android:colorPrimary">@color/primary</item>
-        <item name="android:colorAccent">@color/accent</item>
-
-        <item name="android:actionModeStyle">@style/ActionModeStyle</item>
-
-        <item name="android:alertDialogTheme">@style/AlertDialogTheme</item>
-    </style>
-
-    <style name="ActionModeStyle" parent="@android:style/Widget.Material.Light.ActionMode">
-        <item name="android:background">@color/material_grey_600</item>
-    </style>
-
-    <style name="AlertDialogTheme" parent="@android:style/Theme.Material.Light.Dialog.Alert">
-        <item name="android:colorAccent">@color/platform_blue_700</item>
-    </style>
-
-    <style name="FilesTheme" parent="@style/DocumentsBaseTheme.FullScreen">
-        <item name="android:colorPrimaryDark">@color/platform_blue_700</item>
-        <item name="android:colorPrimary">@color/platform_blue_500</item>
-        <item name="android:colorAccent">@color/platform_blue_700</item>
-        <item name="colorControlActivated">@color/platform_blue_a100</item>
-        <item name="android:actionModeStyle">@style/FilesActionModeStyle</item>
-        <item name="colorActionMode">@color/platform_blue_700</item>
-        <item name="android:alertDialogTheme">@style/AlertDialogTheme</item>
-    </style>
-
-    <style name="FilesActionModeStyle" parent="@android:style/Widget.Material.Light.ActionMode">
-        <item name="android:background">@color/platform_blue_100</item>
-    </style>
-
     <style name="TrimmedHorizontalProgressBar" parent="android:Widget.Material.ProgressBar.Horizontal">
         <item name="android:indeterminateDrawable">@drawable/progress_indeterminate_horizontal_material_trimmed</item>
         <item name="android:minHeight">3dp</item>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index aae5269..18957ee 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -86,7 +86,7 @@
         mShowAsDialog = res.getBoolean(R.bool.show_as_dialog);
 
         if (!mShowAsDialog) {
-            setTheme(R.style.DocumentsNonDialogTheme);
+            setTheme(R.style.DocumentsFullScreenTheme);
         }
 
         if (mShowAsDialog) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
new file mode 100644
index 0000000..cf17ea5
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.settingslib.accessibility;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+public class AccessibilityUtils {
+    public static final char ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ':';
+
+    final static TextUtils.SimpleStringSplitter sStringColonSplitter =
+            new TextUtils.SimpleStringSplitter(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
+
+    /**
+     * @return the set of enabled accessibility services. If there are not services
+     * it returned the unmodifiable {@link Collections#emptySet()}.
+     */
+    public static Set<ComponentName> getEnabledServicesFromSettings(Context context) {
+        final String enabledServicesSetting = Settings.Secure.getString(
+                context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+        if (enabledServicesSetting == null) {
+            return Collections.emptySet();
+        }
+
+        final Set<ComponentName> enabledServices = new HashSet<ComponentName>();
+        final TextUtils.SimpleStringSplitter colonSplitter = sStringColonSplitter;
+        colonSplitter.setString(enabledServicesSetting);
+
+        while (colonSplitter.hasNext()) {
+            final String componentNameString = colonSplitter.next();
+            final ComponentName enabledService = ComponentName.unflattenFromString(
+                    componentNameString);
+            if (enabledService != null) {
+                enabledServices.add(enabledService);
+            }
+        }
+
+        return enabledServices;
+    }
+
+    /**
+     * @return a localized version of the text resource specified by resId
+     */
+    public static CharSequence getTextForLocale(Context context, Locale locale, int resId) {
+        final Resources res = context.getResources();
+        final Configuration config = new Configuration(res.getConfiguration());
+        config.setLocale(locale);
+        final Context langContext = context.createConfigurationContext(config);
+        return langContext.getText(resId);
+    }
+}
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
index d83b516..0d1ad19 100644
--- a/packages/Shell/src/com/android/shell/BugreportReceiver.java
+++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java
@@ -25,6 +25,7 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
+import android.content.ClipData;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
@@ -41,14 +42,11 @@
 import libcore.io.Streams;
 
 import java.io.BufferedOutputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 import java.util.ArrayList;
@@ -107,11 +105,6 @@
      */
     private void triggerLocalNotification(final Context context, final File bugreportFile,
             final File screenshotFile) {
-        // Files are kept on private storage, so turn into Uris that we can
-        // grant temporary permissions for.
-        final Uri bugreportUri = FileProvider.getUriForFile(context, AUTHORITY, bugreportFile);
-        final Uri screenshotUri = FileProvider.getUriForFile(context, AUTHORITY, screenshotFile);
-
         boolean isPlainText = bugreportFile.getName().toLowerCase().endsWith(".txt");
         if (!isPlainText) {
             // Already zipped, send it right away.
@@ -133,12 +126,22 @@
      */
     private static Intent buildSendIntent(Context context, Uri bugreportUri, Uri screenshotUri) {
         final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
+        final String mimeType = "application/vnd.android.bugreport";
         intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
         intent.addCategory(Intent.CATEGORY_DEFAULT);
-        intent.setType("application/vnd.android.bugreport");
+        intent.setType(mimeType);
 
         intent.putExtra(Intent.EXTRA_SUBJECT, bugreportUri.getLastPathSegment());
+
+        // EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
+        // So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually
+        // create the ClipData object with the attachments URIs.
         intent.putExtra(Intent.EXTRA_TEXT, SystemProperties.get("ro.build.description"));
+        final ClipData clipData = new ClipData(
+                null, new String[] { mimeType },
+                new ClipData.Item(null, null, null, bugreportUri));
+        clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
+        intent.setClipData(clipData);
 
         final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri, screenshotUri);
         intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 5d622a0..be5c0fe 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -214,6 +214,7 @@
                   android:resumeWhilePausing="true"
                   android:screenOrientation="behind"
                   android:resizeableActivity="true"
+                  android:configChanges="orientation|screenSize|smallestScreenSize"
                   android:theme="@style/RecentsTheme.Wallpaper">
             <intent-filter>
                 <action android:name="com.android.systemui.recents.TOGGLE_RECENTS" />
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 6c8bf0f..dc80259 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -616,15 +616,17 @@
         ViewAnimation.TaskViewEnterContext ctx = new ViewAnimation.TaskViewEnterContext(t);
         ctx.postAnimationTrigger.increment();
         if (mSearchWidgetInfo != null) {
-            ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
-                @Override
-                public void run() {
-                    // Start listening for widget package changes if there is one bound
-                    if (!Constants.DebugFlags.App.DisableSearchBar && mAppWidgetHost != null) {
-                        mAppWidgetHost.startListening();
+            if (!Constants.DebugFlags.App.DisableSearchBar) {
+                ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
+                    @Override
+                    public void run() {
+                        // Start listening for widget package changes if there is one bound
+                        if (mAppWidgetHost != null) {
+                            mAppWidgetHost.startListening();
+                        }
                     }
-                }
-            });
+                });
+            }
         }
         ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index d02e2af..c314f3f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -57,7 +57,7 @@
 import com.android.systemui.recents.model.TaskGrouping;
 import com.android.systemui.recents.model.TaskStack;
 import com.android.systemui.recents.views.TaskStackView;
-import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm;
+import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
 import com.android.systemui.recents.views.TaskViewHeader;
 import com.android.systemui.recents.views.TaskViewTransform;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
@@ -469,10 +469,10 @@
                 mSearchBarBounds, mTaskStackBounds);
 
         // Rebind the header bar and draw it for the transition
-        TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
+        TaskStackLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
         Rect taskStackBounds = new Rect(mTaskStackBounds);
         algo.setSystemInsets(systemInsets);
-        algo.computeRects(taskStackBounds);
+        algo.initialize(taskStackBounds);
         Rect taskViewBounds = algo.getUntransformedTaskViewBounds();
         if (!taskViewBounds.equals(mLastTaskViewBounds)) {
             mLastTaskViewBounds.set(taskViewBounds);
@@ -695,7 +695,7 @@
 
         // Prepare the dummy stack for the transition
         mDummyStackView.updateMinMaxScrollForStack(stack);
-        TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
+        TaskStackLayoutAlgorithm.VisibilityReport stackVr =
                 mDummyStackView.computeStackVisibilityReport();
         boolean hasRecentTasks = stack.getTaskCount() > 0;
         boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
@@ -742,7 +742,7 @@
      */
     private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
               ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail,
-              TaskStackViewLayoutAlgorithm.VisibilityReport vr) {
+              TaskStackLayoutAlgorithm.VisibilityReport vr) {
         mStartAnimationTriggered = false;
 
         // Update the configuration based on the launch options
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 6fc4e20..6c83b87 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -137,12 +137,19 @@
             task.thumbnail = loader.getAndUpdateThumbnail(taskKey, ssp, false);
             if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail);
 
-            stackTasks.add(task);
+            if (task.isFreeformTask()) {
+                freeformTasks.add(task);
+            } else {
+                stackTasks.add(task);
+            }
         }
 
         // Initialize the stacks
+        ArrayList<Task> allTasks = new ArrayList<>();
+        allTasks.addAll(stackTasks);
+        allTasks.addAll(freeformTasks);
         mStack = new TaskStack();
-        mStack.setTasks(stackTasks);
+        mStack.setTasks(allTasks);
         mStack.createAffiliatedGroupings(mContext);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index b3937c3..a96fe98 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -399,6 +399,21 @@
         return mTaskList.size();
     }
 
+    /**
+     * Returns the task in this stack which is the launch target.
+     */
+    public Task getLaunchTarget() {
+        ArrayList<Task> tasks = mTaskList.getTasks();
+        int taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            Task task = tasks.get(i);
+            if (task.isLaunchTarget) {
+                return task;
+            }
+        }
+        return null;
+    }
+
     /** Returns the index of this task in this current task stack */
     public int indexOfTask(Task t) {
         return mTaskList.indexOf(t);
@@ -417,6 +432,21 @@
         return null;
     }
 
+    /**
+     * Returns whether this stack has freeform tasks.
+     */
+    public boolean hasFreeformTasks() {
+        ArrayList<Task> tasks = mTaskList.getTasks();
+        int taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            Task task = tasks.get(i);
+            if (task.isFreeformTask()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /******** Filtering ********/
 
     /** Filters the stack into tasks similar to the one specified */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
new file mode 100644
index 0000000..ce993c5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.views;
+
+import android.util.Log;
+import com.android.systemui.recents.model.Task;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * The layout logic for the contents of the freeform workspace.
+ */
+public class FreeformWorkspaceLayoutAlgorithm {
+
+    private static final String TAG = "FreeformWorkspaceLayoutAlgorithm";
+    private static final boolean DEBUG = false;
+
+    // The number of cells in the freeform workspace
+    private int mFreeformCellXCount;
+    private int mFreeformCellYCount;
+    // The width and height of the cells in the freeform workspace
+    private int mFreeformCellWidth;
+    private int mFreeformCellHeight;
+
+    // Optimization, allows for quick lookup of task -> index
+    private HashMap<Task.TaskKey, Integer> mTaskIndexMap = new HashMap<>();
+
+    /**
+     * Updates the layout for each of the freeform workspace tasks.  This is called after the stack
+     * layout is updated.
+     */
+    public void update(ArrayList<Task> freeformTasks, TaskStackLayoutAlgorithm stackLayout) {
+        int numFreeformTasks = stackLayout.mNumFreeformTasks;
+        if (!freeformTasks.isEmpty()) {
+            // Calculate the cell width/height depending on the number of freeform tasks
+            mFreeformCellXCount = Math.max(2, (int) Math.ceil(Math.sqrt(numFreeformTasks)));
+            mFreeformCellYCount = Math.max(2, (int) Math.ceil((float) numFreeformTasks / mFreeformCellXCount));
+            mFreeformCellWidth = stackLayout.mFreeformRect.width() / mFreeformCellXCount;
+            // For now, make the cells square
+            mFreeformCellHeight = mFreeformCellWidth;
+
+            // Put each of the tasks in the progress map at a fixed index (does not need to actually
+            // map to a scroll position, just by index)
+            int taskCount = freeformTasks.size();
+            for (int i = 0; i < taskCount; i++) {
+                Task task = freeformTasks.get(i);
+                mTaskIndexMap.put(task.key, i);
+            }
+
+            if (DEBUG) {
+                Log.d(TAG, "mFreeformCellXCount: " + mFreeformCellXCount);
+                Log.d(TAG, "mFreeformCellYCount: " + mFreeformCellYCount);
+                Log.d(TAG, "mFreeformCellWidth: " + mFreeformCellWidth);
+                Log.d(TAG, "mFreeformCellHeight: " + mFreeformCellHeight);
+            }
+        }
+    }
+
+    /**
+     * Returns whether the transform is available for the given task.
+     */
+    public boolean isTransformAvailable(Task task, float stackScroll,
+            TaskStackLayoutAlgorithm stackLayout) {
+        if (stackLayout.mNumFreeformTasks == 0 || task == null ||
+                !mTaskIndexMap.containsKey(task.key)) {
+            return false;
+        }
+        return stackScroll > stackLayout.mStackEndScrollP;
+    }
+
+    /**
+     * Returns the transform for the given task.  Any rect returned will be offset by the actual
+     * transform for the freeform workspace.
+     */
+    public TaskViewTransform getTransform(Task task, float stackScroll,
+            TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout) {
+        if (Float.compare(stackScroll, stackLayout.mStackEndScrollP) > 0) {
+            // This is a freeform task, so lay it out in the freeform workspace
+            int taskIndex = mTaskIndexMap.get(task.key);
+            int x = taskIndex % mFreeformCellXCount;
+            int y = taskIndex / mFreeformCellXCount;
+            float scale = (float) mFreeformCellWidth / stackLayout.mTaskRect.width();
+            int scaleXOffset = (int) (((1f - scale) * stackLayout.mTaskRect.width()) / 2);
+            int scaleYOffset = (int) (((1f - scale) * stackLayout.mTaskRect.height()) / 2);
+            transformOut.scale = scale * 0.9f;
+            transformOut.translationX = x * mFreeformCellWidth - scaleXOffset;
+            transformOut.translationY = y * mFreeformCellHeight - scaleYOffset;
+            transformOut.visible = true;
+            return transformOut;
+        }
+        return null;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
similarity index 68%
rename from packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
rename to packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index c74c654..ff02e03 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -26,6 +26,7 @@
 import com.android.systemui.recents.misc.ParametricCurve;
 import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -33,9 +34,8 @@
 
 /**
  * The layout logic for a TaskStackView.
- *
  */
-public class TaskStackViewLayoutAlgorithm {
+public class TaskStackLayoutAlgorithm {
 
     private static final String TAG = "TaskStackViewLayoutAlgorithm";
     private static final boolean DEBUG = false;
@@ -46,6 +46,10 @@
     private static final float SINGLE_TASK_SCALE = 0.95f;
     // The percentage of height of task to show between tasks
     private static final float VISIBLE_TASK_HEIGHT_BETWEEN_TASKS = 0.5f;
+    // The percentage between the maxStackScroll and the maxScroll where a given scroll will still
+    // snap back to the maxStackScroll instead of to the maxScroll (which shows the freeform
+    // workspace)
+    private static final float SNAP_TO_MAX_STACK_SCROLL_FACTOR = 0.3f;
 
     // A report of the visibility state of the stack
     public class VisibilityReport {
@@ -64,11 +68,9 @@
     // This is the view bounds inset exactly by the search bar, but without the bottom inset
     // see RecentsConfiguration.getTaskStackBounds()
     public Rect mStackRect = new Rect();
-
     // This is the task view bounds for layout (untransformed), the rect is top-aligned to the top
     // of the stack rect
     public Rect mTaskRect = new Rect();
-
     // The bounds of the freeform workspace, the rect is top-aligned to the top of the stack rect
     public Rect mFreeformRect = new Rect();
     // This is the current system insets
@@ -79,10 +81,14 @@
     // The largest scroll progress, at this value, the front most task will be visible above the
     // navigation bar
     float mMaxScrollP;
-    // The scroll progress at which the stack scroll ends and the overscroll begins.  This serves
-    // as the point at which we can show the freeform space.
-    float mMaxStackScrollP;
-    // The initial progress that the scroller is set
+    // The scroll progress at which bottom of the first task of the stack is aligned with the bottom
+    // of the stack
+    float mStackEndScrollP;
+    // The scroll progress that we actually want to scroll the user to when they want to go to the
+    // end of the stack (it accounts for the nav bar, so that the bottom of the task is offset from
+    // the bottom of the stack)
+    float mPreferredStackEndScrollP;
+    // The initial progress that the scroller is set when you first enter recents
     float mInitialScrollP;
     // The task progress for the front-most task in the stack
     float mFrontMostTaskP;
@@ -96,20 +102,18 @@
     float mTaskHeightPOffset;
     // The relative progress to ensure that the half task height is respected
     float mTaskHalfHeightPOffset;
+    // The front-most task bottom offset
+    int mStackBottomOffset;
     // The relative progress to ensure that the offset from the bottom of the stack to the bottom
     // of the task is respected
-    float mTaskBottomPOffset;
-    // The relative progress to ensure that the freeform workspace height is respected
+    float mStackBottomPOffset;
+    // The freeform workspace gap
+    int mFreeformWorkspaceGapOffset;
+    float mFreeformWorkspaceGapPOffset;
+    // The relative progress to ensure that the freeform workspace height + gap + stack bottom
+    // padding is respected
+    int mFreeformWorkspaceOffset;
     float mFreeformWorkspacePOffset;
-    // The front-most task bottom offset
-    int mTaskBottomOffset;
-
-    // The number of cells in the freeform workspace
-    int mFreeformCellXCount;
-    int mFreeformCellYCount;
-    // The width and height of the cells in the freeform workspace
-    int mFreeformCellWidth;
-    int mFreeformCellHeight;
 
     // The last computed task counts
     int mNumStackTasks;
@@ -121,14 +125,21 @@
     // Optimization, allows for quick lookup of task -> progress
     HashMap<Task.TaskKey, Float> mTaskProgressMap = new HashMap<>();
 
+    // The freeform workspace layout
+    FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm;
+
+    // Temporary task view transform
+    TaskViewTransform mTmpTransform = new TaskViewTransform();
+
     // Log function
     static ParametricCurve sCurve;
 
-    public TaskStackViewLayoutAlgorithm(Context context) {
+    public TaskStackLayoutAlgorithm(Context context) {
         Resources res = context.getResources();
         mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_min);
         mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_max);
         mContext = context;
+        mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm();
         if (sCurve == null) {
             sCurve = new ParametricCurve(new ParametricCurve.CurveFunction() {
                 // The large the XScale, the longer the flat area of the curve
@@ -151,6 +162,11 @@
             }, new ParametricCurve.ParametricCurveFunction() {
                 @Override
                 public float f(float p) {
+                    // Don't scale when there are freeform tasks
+                    if (mNumFreeformTasks > 0) {
+                        return 1f;
+                    }
+
                     if (p < 0) return STACK_PEEK_MIN_SCALE;
                     if (p > 1) return 1f;
                     float scaleRange = (1f - STACK_PEEK_MIN_SCALE);
@@ -174,7 +190,7 @@
     /**
      * Computes the stack and task rects.
      */
-    public void computeRects(Rect taskStackBounds) {
+    public void initialize(Rect taskStackBounds) {
         RecentsConfiguration config = Recents.getConfiguration();
         int widthPadding = (int) (config.taskStackWidthPaddingPct * taskStackBounds.width());
         int heightPadding = mContext.getResources().getDimensionPixelSize(
@@ -183,14 +199,17 @@
         // Compute the stack rect, inset from the given task stack bounds
         mStackRect.set(taskStackBounds.left + widthPadding, taskStackBounds.top + heightPadding,
                 taskStackBounds.right - widthPadding, taskStackBounds.bottom);
-        mTaskBottomOffset = mSystemInsets.bottom + heightPadding;
+        mStackBottomOffset = mSystemInsets.bottom + heightPadding;
 
         // Compute the task rect, align it to the top-center square in the stack rect
-        int size = Math.min(mStackRect.width(), mStackRect.height() - mTaskBottomOffset);
+        int size = Math.min(mStackRect.width(), mStackRect.height() - mStackBottomOffset);
         int xOffset = (mStackRect.width() - size) / 2;
         mTaskRect.set(mStackRect.left + xOffset, mStackRect.top,
                 mStackRect.right - xOffset, mStackRect.top + size);
-        mFreeformRect.set(mTaskRect);
+
+        // Compute the freeform rect, align it to the top-left of the stack rect
+        mFreeformRect.set(mStackRect);
+        mFreeformRect.bottom = taskStackBounds.bottom - mStackBottomOffset;
 
         // Compute the progress offsets
         int withinAffiliationOffset = mContext.getResources().getDimensionPixelSize(
@@ -204,12 +223,17 @@
                 mStackRect);
         mTaskHalfHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height() / 2,
                 mStackRect);
-        mTaskBottomPOffset = sCurve.computePOffsetForHeight(mTaskBottomOffset, mStackRect);
-        mFreeformWorkspacePOffset = sCurve.computePOffsetForHeight(mFreeformRect.height(),
+        mStackBottomPOffset = sCurve.computePOffsetForHeight(mStackBottomOffset, mStackRect);
+        mFreeformWorkspaceGapOffset = mStackBottomOffset;
+        mFreeformWorkspaceGapPOffset = sCurve.computePOffsetForHeight(mFreeformWorkspaceGapOffset,
+                mStackRect);
+        mFreeformWorkspaceOffset = mFreeformWorkspaceGapOffset + mFreeformRect.height() +
+                mStackBottomOffset;
+        mFreeformWorkspacePOffset = sCurve.computePOffsetForHeight(mFreeformWorkspaceOffset,
                 mStackRect);
 
         if (DEBUG) {
-            Log.d(TAG, "computeRects");
+            Log.d(TAG, "initialize");
             Log.d(TAG, "\tarclength: " + sCurve.getArcLength());
             Log.d(TAG, "\tmStackRect: " + mStackRect);
             Log.d(TAG, "\tmTaskRect: " + mTaskRect);
@@ -219,12 +243,14 @@
             Log.d(TAG, "\tpBetweenAffiliateOffset: " + mBetweenAffiliationPOffset);
             Log.d(TAG, "\tmTaskHeightPOffset: " + mTaskHeightPOffset);
             Log.d(TAG, "\tmTaskHalfHeightPOffset: " + mTaskHalfHeightPOffset);
-            Log.d(TAG, "\tmTaskBottomPOffset: " + mTaskBottomPOffset);
+            Log.d(TAG, "\tmStackBottomPOffset: " + mStackBottomPOffset);
+            Log.d(TAG, "\tmFreeformWorkspacePOffset: " + mFreeformWorkspacePOffset);
+            Log.d(TAG, "\tmFreeformWorkspaceGapPOffset: " + mFreeformWorkspaceGapPOffset);
 
             Log.d(TAG, "\ty at p=0: " + sCurve.pToX(0f, mStackRect));
             Log.d(TAG, "\ty at p=1: " + sCurve.pToX(1f, mStackRect));
 
-            for (int height = 0; height <= 1000; height += 50) {
+            for (int height = 0; height <= 2000; height += 50) {
                 float p = sCurve.computePOffsetForScaledHeight(height, mStackRect);
                 float p2 = sCurve.computePOffsetForHeight(height, mStackRect);
                 Log.d(TAG, "offset: " + height + ", " +
@@ -236,20 +262,22 @@
     }
 
     /**
-     * Computes the minimum and maximum scroll progress values.  This method may be called before
-     * the RecentsConfiguration is set, so we need to pass in the alt-tab state.
+     * Computes the minimum and maximum scroll progress values and the progress values for each task
+     * in the stack.
      */
-    void computeMinMaxScroll(ArrayList<Task> tasks) {
+    void update(TaskStack stack) {
         if (DEBUG) {
-            Log.d(TAG, "computeMinMaxScroll");
+            Log.d(TAG, "update");
         }
 
         // Clear the progress map
         mTaskProgressMap.clear();
 
         // Return early if we have no tasks
+        ArrayList<Task> tasks = stack.getTasks();
         if (tasks.isEmpty()) {
-            mMinScrollP = mMaxScrollP = mMaxStackScrollP = 0;
+            mFrontMostTaskP = 0;
+            mMinScrollP = mMaxScrollP = mStackEndScrollP = mPreferredStackEndScrollP = 0;
             mNumStackTasks = mNumFreeformTasks = 0;
             return;
         }
@@ -268,9 +296,6 @@
         mNumStackTasks = stackTasks.size();
         mNumFreeformTasks = freeformTasks.size();
 
-        // TODO: In the case where there is only freeform tasks, then the scrolls should be set to
-        // zero
-
         if (!stackTasks.isEmpty()) {
             // Update the for each task from back to front.
             float pAtBackMostTaskTop = 0;
@@ -289,52 +314,47 @@
             }
 
             mFrontMostTaskP = pAtFrontMostTaskTop;
-            // Set the max scroll progress to the point at which the top of the front-most task
-            // is aligned to the bottom of the stack (offset by nav bar, padding, and task height)
-            mMaxStackScrollP = getBottomAlignedScrollProgress(pAtFrontMostTaskTop,
-                    mTaskBottomPOffset + mTaskHeightPOffset);
+            // Set the stack end scroll progress to the point at which the bottom of the front-most
+            // task is aligned to the bottom of the stack
+            mStackEndScrollP = alignToStackBottom(pAtFrontMostTaskTop,
+                    mTaskHeightPOffset);
+            // Set the preferred stack end scroll progress to the point where the bottom of the
+            // front-most task is offset by the navbar and padding from the bottom of the stack
+            mPreferredStackEndScrollP = mStackEndScrollP + mStackBottomPOffset;
             // Basically align the back-most task such that its progress is the same as the top of
             // the front most task at the max stack scroll
-            mMinScrollP = getBottomAlignedScrollProgress(pAtBackMostTaskTop,
-                    mTaskBottomPOffset + mTaskHeightPOffset);
+            mMinScrollP = alignToStackBottom(pAtBackMostTaskTop,
+                    mStackBottomPOffset + mTaskHeightPOffset);
+        } else {
+            // TODO: In the case where there is only freeform tasks, then the scrolls should be
+            // set to zero
         }
 
         if (!freeformTasks.isEmpty()) {
-            // Calculate the cell width/height depending on the number of freeform tasks
-            mFreeformCellXCount = Math.max(2, (int) Math.ceil(Math.sqrt(mNumFreeformTasks)));
-            mFreeformCellYCount = Math.max(2, (int) Math.ceil((float) mNumFreeformTasks / mFreeformCellXCount));
-            mFreeformCellWidth = mFreeformRect.width() / mFreeformCellXCount;
-            mFreeformCellHeight = mFreeformRect.height() / mFreeformCellYCount;
-
-            // Put each of the tasks in the progress map at a fixed index (does not need to actually
-            // map to a scroll position, just by index)
-            int taskCount = freeformTasks.size();
-            for (int i = 0; i < taskCount; i++) {
-                Task task = freeformTasks.get(i);
-                mTaskProgressMap.put(task.key, (mFrontMostTaskP + 1) + i);
-            }
-
             // The max scroll includes the freeform workspace offset. As the scroll progress exceeds
-            // mMaxStackScrollP up to mMaxScrollP, the stack will translate upwards and the freeform
+            // mStackEndScrollP up to mMaxScrollP, the stack will translate upwards and the freeform
             // workspace will be visible
-            mMaxScrollP = mMaxStackScrollP + mFreeformWorkspacePOffset;
-            mInitialScrollP = mMaxScrollP;
+            mFreeformLayoutAlgorithm.update(freeformTasks, this);
+            mMaxScrollP = mStackEndScrollP + mFreeformWorkspacePOffset;
+            mInitialScrollP = isInitialStateFreeform(stack) ?
+                    mMaxScrollP : mPreferredStackEndScrollP;
         } else {
-            mMaxScrollP = mMaxStackScrollP;
-            mInitialScrollP = Math.max(mMinScrollP, mMaxStackScrollP - mTaskHalfHeightPOffset);
+            mMaxScrollP = mPreferredStackEndScrollP;
+            mInitialScrollP = Math.max(mMinScrollP, mMaxScrollP - mTaskHalfHeightPOffset);
         }
+
         if (DEBUG) {
             Log.d(TAG, "mNumStackTasks: " + mNumStackTasks);
             Log.d(TAG, "mNumFreeformTasks: " + mNumFreeformTasks);
             Log.d(TAG, "mMinScrollP: " + mMinScrollP);
-            Log.d(TAG, "mMaxStackScrollP: " + mMaxStackScrollP);
+            Log.d(TAG, "mStackEndScrollP: " + mStackEndScrollP);
             Log.d(TAG, "mMaxScrollP: " + mMaxScrollP);
         }
     }
 
     /**
      * Computes the maximum number of visible tasks and thumbnails.  Requires that
-     * computeMinMaxScroll() is called first.
+     * update() is called first.
      */
     public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
         // Ensure minimum visibility count
@@ -380,7 +400,7 @@
                     prevScreenY = screenY;
                 } else {
                     // Once we hit the next front most task that does not have a visible thumbnail,
-                    // walk through remaining visible set
+                    // w  alk through remaining visible set
                     for (int j = i; j >= 0; j--) {
                         numVisibleTasks++;
                         progress = mTaskProgressMap.get(tasks.get(j).key) - mInitialScrollP;
@@ -404,50 +424,42 @@
      */
     public TaskViewTransform getStackTransform(Task task, float stackScroll,
             TaskViewTransform transformOut, TaskViewTransform prevTransform) {
-        // Return early if we have an invalid index
-        if (task == null || !mTaskProgressMap.containsKey(task.key)) {
-            transformOut.reset();
+        if (mFreeformLayoutAlgorithm.isTransformAvailable(task, stackScroll, this)) {
+            mFreeformLayoutAlgorithm.getTransform(task, stackScroll, transformOut, this);
+            if (transformOut.visible) {
+                getFreeformWorkspaceBounds(stackScroll, mTmpTransform);
+                transformOut.translationY += mTmpTransform.translationY;
+                transformOut.translationZ = mMaxTranslationZ;
+                transformOut.rect.set(mTaskRect);
+                transformOut.rect.offset(0, transformOut.translationY);
+                Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
+                transformOut.p = 0;
+            }
             return transformOut;
+        } else {
+            // Return early if we have an invalid index
+            if (task == null || !mTaskProgressMap.containsKey(task.key)) {
+                transformOut.reset();
+                return transformOut;
+            }
+            return getStackTransform(mTaskProgressMap.get(task.key), stackScroll, transformOut,
+                    prevTransform);
         }
-        return getStackTransform(mTaskProgressMap.get(task.key), stackScroll, transformOut,
-                prevTransform);
     }
 
     /** Update/get the transform */
     public TaskViewTransform getStackTransform(float taskProgress, float stackScroll,
             TaskViewTransform transformOut, TaskViewTransform prevTransform) {
-        float stackOverscroll = (stackScroll - mMaxStackScrollP) / mFreeformWorkspacePOffset;
-        int overscrollYOffset = 0;
-        if (mNumFreeformTasks > 0) {
-            overscrollYOffset = (int) (Math.max(0, stackOverscroll) * mFreeformRect.height());
-        }
 
-        if ((mNumFreeformTasks > 0) && (stackScroll > mMaxStackScrollP) &&
-                (taskProgress > mFrontMostTaskP)) {
-            // This is a freeform task, so lay it out in the freeform workspace
-            int taskIndex = Math.round(taskProgress - (mFrontMostTaskP + 1));
-            int x = taskIndex % mFreeformCellXCount;
-            int y = taskIndex / mFreeformCellXCount;
-            int frontTaskBottom = mStackRect.height() - mTaskBottomOffset;
-            float scale = (float) mFreeformCellWidth / mTaskRect.width();
-            int scaleXOffset = (int) (((1f - scale) * mTaskRect.width()) / 2);
-            int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
-            transformOut.scale = scale;
-            transformOut.translationX = x * mFreeformCellWidth - scaleXOffset;
-            transformOut.translationY = frontTaskBottom - overscrollYOffset +
-                    (y * mFreeformCellHeight) - scaleYOffset;
-            transformOut.visible = true;
-            return transformOut;
-
-        } else if (mNumStackTasks == 1) {
+        if (mNumStackTasks == 1) {
             // Center the task in the stack, changing the scale will not follow the curve, but just
             // modulate some values directly
             float pTaskRelative = mMinScrollP - stackScroll;
             float scale = SINGLE_TASK_SCALE;
-            int topOffset = (mStackRect.height() - mTaskBottomOffset - mTaskRect.height()) / 2;
+            int topOffset = (mStackRect.height() - mTaskRect.height()) / 2;
             transformOut.scale = scale;
-            transformOut.translationY = (int) (topOffset + (pTaskRelative * mStackRect.height())) -
-                    overscrollYOffset;
+            transformOut.translationX = 0;
+            transformOut.translationY = (int) (topOffset + (pTaskRelative * mStackRect.height()));
             transformOut.translationZ = mMaxTranslationZ;
             transformOut.rect.set(mTaskRect);
             transformOut.rect.offset(0, transformOut.translationY);
@@ -457,11 +469,20 @@
             return transformOut;
 
         } else {
-            float pTaskRelative = taskProgress - stackScroll;
-            if (mNumFreeformTasks > 0) {
-                pTaskRelative = Math.min(mMaxStackScrollP, pTaskRelative);
+            // Once we scroll past the preferred stack end scroll, then we should start translating
+            // the cards in screen space and lock their final state at the end stack progress
+            int overscrollYOffset = 0;
+            if (mNumFreeformTasks > 0 && stackScroll > mStackEndScrollP) {
+                float stackOverscroll = (stackScroll - mPreferredStackEndScrollP) /
+                        (mFreeformWorkspacePOffset - mFreeformWorkspaceGapPOffset);
+               overscrollYOffset = (int) (Math.max(0, stackOverscroll) *
+                       (mFreeformWorkspaceOffset - mFreeformWorkspaceGapPOffset));
+                stackScroll = Math.min(mPreferredStackEndScrollP, stackScroll);
             }
+
+            float pTaskRelative = taskProgress - stackScroll;
             float pBounded = Math.max(0, Math.min(pTaskRelative, 1f));
+
             // If the task top is outside of the bounds below the screen, then immediately reset it
             if (pTaskRelative > 1f) {
                 transformOut.reset();
@@ -480,6 +501,7 @@
             float scale = sCurve.pToScale(pBounded);
             int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
             transformOut.scale = scale;
+            transformOut.translationX = 0;
             transformOut.translationY = sCurve.pToX(pBounded, mStackRect) - mStackRect.top -
                     scaleYOffset - overscrollYOffset;
             transformOut.translationZ = Math.max(mMinTranslationZ,
@@ -489,11 +511,31 @@
             Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
             transformOut.visible = true;
             transformOut.p = pTaskRelative;
+            if (DEBUG) {
+                Log.d(TAG, "getStackTransform (normal): " + taskProgress + ", " + stackScroll);
+                Log.d(TAG, "\t" + transformOut);
+            }
+
             return transformOut;
         }
     }
 
     /**
+     * Returns whether this stack should be initialized to show the freeform workspace or not.
+     */
+    public boolean isInitialStateFreeform(TaskStack stack) {
+        Task launchTarget = stack.getLaunchTarget();
+        if (launchTarget != null) {
+            return launchTarget.isFreeformTask();
+        }
+        Task frontTask = stack.getFrontMostTask();
+        if (frontTask != null) {
+            return frontTask.isFreeformTask();
+        }
+        return false;
+    }
+
+    /**
      * Update/get the transform
      */
     public TaskViewTransform getFreeformWorkspaceBounds(float stackScroll,
@@ -503,18 +545,37 @@
             return transformOut;
         }
 
-        if (stackScroll > mMaxStackScrollP) {
-            float stackOverscroll = (stackScroll - mMaxStackScrollP) / mFreeformWorkspacePOffset;
-            int overscrollYOffset = (int) (stackOverscroll * mFreeformRect.height());
-            int frontTaskBottom = mStackRect.height() - mTaskBottomOffset;
+        if (stackScroll > mStackEndScrollP) {
+            // mStackEndScroll is the point at which the first stack task is bottom aligned with the
+            // stack, so we offset from on the stack rect height.
+            float stackOverscroll = (Math.max(0, stackScroll - mStackEndScrollP)) /
+                    mFreeformWorkspacePOffset;
+            int overscrollYOffset = (int) (stackOverscroll * mFreeformWorkspaceOffset);
+            transformOut.scale = 1f;
+            transformOut.alpha = 1f;
+            transformOut.translationY = mStackRect.height() + mFreeformWorkspaceGapOffset -
+                    overscrollYOffset;
+            transformOut.rect.set(mFreeformRect);
+            transformOut.rect.offset(0, transformOut.translationY);
+            Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
             transformOut.visible = true;
-            transformOut.alpha =
-            transformOut.translationY = frontTaskBottom - overscrollYOffset;
         }
         return transformOut;
     }
 
     /**
+     * Returns the preferred maximum scroll position for a stack at the given {@param scroll}.
+     */
+    public float getPreferredMaxScrollPosition(float scroll) {
+        float maxStackScrollBounds = mStackEndScrollP + SNAP_TO_MAX_STACK_SCROLL_FACTOR *
+                (mMaxScrollP - mStackEndScrollP);
+        if (scroll < maxStackScrollBounds) {
+            return mPreferredStackEndScrollP;
+        }
+        return mMaxScrollP;
+    }
+
+    /**
      * Returns the untransformed task view bounds.
      */
     public Rect getUntransformedTaskViewBounds() {
@@ -549,7 +610,12 @@
         return -y;
     }
 
-    private float getBottomAlignedScrollProgress(float p, float pOffsetFromBottom) {
+    private float alignToStackTop(float p) {
+        // At scroll progress == p, then p is at the top of the stack
+        return p;
+    }
+
+    private float alignToStackBottom(float p, float pOffsetFromBottom) {
         // At scroll progress == p, then p is at the top of the stack
         // At scroll progress == p + 1, then p is at the bottom of the stack
         return p - (1 - pOffsetFromBottom);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index dfa36d8..a7bfc40 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -22,12 +22,12 @@
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.graphics.drawable.ColorDrawable;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
@@ -79,11 +79,12 @@
     }
 
     TaskStack mStack;
-    TaskStackViewLayoutAlgorithm mLayoutAlgorithm;
+    TaskStackLayoutAlgorithm mLayoutAlgorithm;
     TaskStackViewFilterAlgorithm mFilterAlgorithm;
     TaskStackViewScroller mStackScroller;
     TaskStackViewTouchHandler mTouchHandler;
     TaskStackViewCallbacks mCb;
+    ColorDrawable mFreeformWorkspaceBackground;
     ViewPool<TaskView, Task> mViewPool;
     ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
     DozeTrigger mUIDozeTrigger;
@@ -101,6 +102,8 @@
     Rect mTmpRect = new Rect();
     RectF mTmpTaskRect = new RectF();
     TaskViewTransform mTmpTransform = new TaskViewTransform();
+    TaskViewTransform mTmpStackBackTransform = new TaskViewTransform();
+    TaskViewTransform mTmpStackFrontTransform = new TaskViewTransform();
     HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<>();
     ArrayList<TaskView> mTaskViews = new ArrayList<>();
     List<TaskView> mImmutableTaskViews = new ArrayList<>();
@@ -122,7 +125,7 @@
         setStack(stack);
         mViewPool = new ViewPool<>(context, this);
         mInflater = LayoutInflater.from(context);
-        mLayoutAlgorithm = new TaskStackViewLayoutAlgorithm(context);
+        mLayoutAlgorithm = new TaskStackLayoutAlgorithm(context);
         mFilterAlgorithm = new TaskStackViewFilterAlgorithm(this, mViewPool);
         mStackScroller = new TaskStackViewScroller(context, mLayoutAlgorithm);
         mStackScroller.setCallbacks(this);
@@ -143,6 +146,8 @@
             }
         });
         setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+
+        mFreeformWorkspaceBackground = new ColorDrawable(0x33000000);
     }
 
     /** Sets the callbacks */
@@ -270,7 +275,7 @@
     }
 
     /** Returns the stack algorithm for this task stack. */
-    public TaskStackViewLayoutAlgorithm getStackAlgorithm() {
+    public TaskStackLayoutAlgorithm getStackAlgorithm() {
         return mLayoutAlgorithm;
     }
 
@@ -342,6 +347,11 @@
             int[] visibleRange = mTmpVisibleRange;
             boolean isValidVisibleRange = updateStackTransforms(mCurrentTaskTransforms, tasks,
                     stackScroll, visibleRange, false);
+            boolean hasStackBackTransform = false;
+            boolean hasStackFrontTransform = false;
+            if (DEBUG) {
+                Log.d(TAG, "visibleRange: " + visibleRange[0] + " to " + visibleRange[1]);
+            }
 
             // Return all the invisible children to the pool
             mTmpTaskViewMap.clear();
@@ -381,11 +391,20 @@
                         // For items in the list, put them in start animating them from the
                         // approriate ends of the list where they are expected to appear
                         if (Float.compare(transform.p, 0f) <= 0) {
-                            mLayoutAlgorithm.getStackTransform(0f, 0f, mTmpTransform, null);
+                            if (!hasStackBackTransform) {
+                                hasStackBackTransform = true;
+                                mLayoutAlgorithm.getStackTransform(0f, 0f, mTmpStackBackTransform,
+                                        null);
+                            }
+                            tv.updateViewPropertiesToTaskTransform(mTmpStackBackTransform, 0);
                         } else {
-                            mLayoutAlgorithm.getStackTransform(1f, 0f, mTmpTransform, null);
+                            if (!hasStackFrontTransform) {
+                                hasStackFrontTransform = true;
+                                mLayoutAlgorithm.getStackTransform(1f, 0f, mTmpStackFrontTransform,
+                                        null);
+                            }
+                            tv.updateViewPropertiesToTaskTransform(mTmpStackFrontTransform, 0);
                         }
-                        tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0);
                     }
                 }
 
@@ -403,6 +422,16 @@
                 }
             }
 
+            // Update the freeform workspace
+            mLayoutAlgorithm.getFreeformWorkspaceBounds(stackScroll, mTmpTransform);
+            if (mTmpTransform.visible) {
+                mTmpTransform.rect.roundOut(mTmpRect);
+                mFreeformWorkspaceBackground.setAlpha(255);
+                mFreeformWorkspaceBackground.setBounds(mTmpRect);
+            } else {
+                mFreeformWorkspaceBackground.setAlpha(0);
+            }
+
             // Reset the request-synchronize params
             mStackViewsAnimationDuration = 0;
             mStackViewsDirty = false;
@@ -460,7 +489,7 @@
     /** Updates the min and max virtual scroll bounds */
     void updateMinMaxScroll(boolean boundScrollToNewMinMax) {
         // Compute the min and max scroll values
-        mLayoutAlgorithm.computeMinMaxScroll(mStack.getTasks());
+        mLayoutAlgorithm.update(mStack);
 
         // Debug logging
         if (boundScrollToNewMinMax) {
@@ -512,7 +541,7 @@
             };
 
             if (scrollToTask) {
-                // TODO: Center the newly focused task view
+                // TODO: Center the newly focused task view, only if not freeform
                 float newScroll = mLayoutAlgorithm.getStackScrollForTask(newFocusedTask) - 0.5f;
                 newScroll = mStackScroller.getBoundedStackScroll(newScroll);
                 mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll,
@@ -633,7 +662,7 @@
     /** Computes the stack and task rects */
     public void computeRects(Rect taskStackBounds) {
         // Compute the rects in the stack algorithm
-        mLayoutAlgorithm.computeRects(taskStackBounds);
+        mLayoutAlgorithm.initialize(taskStackBounds);
 
         // Update the scroll bounds
         updateMinMaxScroll(false);
@@ -652,7 +681,7 @@
      * Computes the maximum number of visible tasks and thumbnails.  Requires that
      * updateMinMaxScrollForStack() is called first.
      */
-    public TaskStackViewLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
+    public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
         return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getTasks());
     }
 
@@ -710,7 +739,7 @@
      */
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        // Layout each of the children
+        // Layout each of the TaskViews
         List<TaskView> taskViews = getTaskViews();
         int taskViewCount = taskViews.size();
         for (int i = 0; i < taskViewCount; i++) {
@@ -720,10 +749,9 @@
             } else {
                 mTmpRect.setEmpty();
             }
-            tv.layout(mLayoutAlgorithm.mTaskRect.left - mTmpRect.left,
-                    mLayoutAlgorithm.mTaskRect.top - mTmpRect.top,
-                    mLayoutAlgorithm.mTaskRect.right + mTmpRect.right,
-                    mLayoutAlgorithm.mTaskRect.bottom + mTmpRect.bottom);
+            Rect taskRect = mLayoutAlgorithm.mTaskRect;
+            tv.layout(taskRect.left - mTmpRect.left, taskRect.top - mTmpRect.top,
+                    taskRect.right + mTmpRect.right, taskRect.bottom + mTmpRect.bottom);
         }
 
         if (mAwaitingFirstLayout) {
@@ -737,17 +765,9 @@
         int offscreenY = mLayoutAlgorithm.mStackRect.bottom;
 
         // Find the launch target task
-        Task launchTargetTask = null;
+        Task launchTargetTask = mStack.getLaunchTarget();
         List<TaskView> taskViews = getTaskViews();
         int taskViewCount = taskViews.size();
-        for (int i = taskViewCount - 1; i >= 0; i--) {
-            TaskView tv = taskViews.get(i);
-            Task task = tv.getTask();
-            if (task.isLaunchTarget) {
-                launchTargetTask = task;
-                break;
-            }
-        }
 
         // Prepare the first view for its enter animation
         for (int i = taskViewCount - 1; i >= 0; i--) {
@@ -755,7 +775,8 @@
             Task task = tv.getTask();
             boolean occludesLaunchTarget = (launchTargetTask != null) &&
                     launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
-            tv.prepareEnterRecentsAnimation(task.isLaunchTarget, occludesLaunchTarget, offscreenY);
+            tv.prepareEnterRecentsAnimation(task.isLaunchTarget, occludesLaunchTarget,
+                    offscreenY);
         }
 
         // If the enter animation started already and we haven't completed a layout yet, do the
@@ -794,17 +815,9 @@
 
         if (mStack.getTaskCount() > 0) {
             // Find the launch target task
-            Task launchTargetTask = null;
+            Task launchTargetTask = mStack.getLaunchTarget();
             List<TaskView> taskViews = getTaskViews();
             int taskViewCount = taskViews.size();
-            for (int i = taskViewCount - 1; i >= 0; i--) {
-                TaskView tv = taskViews.get(i);
-                Task task = tv.getTask();
-                if (task.isLaunchTarget) {
-                    launchTargetTask = task;
-                    break;
-                }
-            }
 
             // Animate all the task views into view
             for (int i = taskViewCount - 1; i >= 0; i--) {
@@ -817,7 +830,8 @@
                 ctx.currentTaskOccludesLaunchTarget = (launchTargetTask != null) &&
                         launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
                 ctx.updateListener = mRequestUpdateClippingListener;
-                mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(), ctx.currentTaskTransform, null);
+                mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(),
+                        ctx.currentTaskTransform, null);
                 tv.startEnterRecentsAnimation(ctx);
             }
 
@@ -898,6 +912,12 @@
     @Override
     protected void dispatchDraw(Canvas canvas) {
         mLayersDisabled = false;
+
+        // Draw the freeform workspace background
+        if (mFreeformWorkspaceBackground.getAlpha() > 0) {
+            mFreeformWorkspaceBackground.draw(canvas);
+        }
+
         super.dispatchDraw(canvas);
     }
 
@@ -919,42 +939,44 @@
     @Override
     public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
             Task newFrontMostTask) {
-        // Remove the view associated with this task, we can't rely on updateTransforms
-        // to work here because the task is no longer in the list
-        TaskView tv = getChildViewForTask(removedTask);
-        if (tv != null) {
-            mViewPool.returnViewToPool(tv);
+        if (!removedTask.isFreeformTask()) {
+            // Remove the view associated with this task, we can't rely on updateTransforms
+            // to work here because the task is no longer in the list
+            TaskView tv = getChildViewForTask(removedTask);
+            if (tv != null) {
+                mViewPool.returnViewToPool(tv);
+            }
+
+            // Get the stack scroll of the task to anchor to (since we are removing something, the front
+            // most task will be our anchor task)
+            Task anchorTask = null;
+            float prevAnchorTaskScroll = 0;
+            boolean pullStackForward = stack.getTaskCount() > 0;
+            if (pullStackForward) {
+                anchorTask = mStack.getFrontMostTask();
+                prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
+            }
+
+            // Update the min/max scroll and animate other task views into their new positions
+            updateMinMaxScroll(true);
+
+            if (wasFrontMostTask) {
+                // Since the max scroll progress is offset from the bottom of the stack, just scroll
+                // to ensure that the new front most task is now fully visible
+                mStackScroller.setStackScroll(mLayoutAlgorithm.mMaxScrollP);
+            } else if (pullStackForward) {
+                // Otherwise, offset the scroll by half the movement of the anchor task to allow the
+                // tasks behind the removed task to move forward, and the tasks in front to move back
+                float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
+                mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll
+                        - prevAnchorTaskScroll) / 2);
+                mStackScroller.boundScroll();
+            }
+
+            // Animate all the tasks into place
+            requestSynchronizeStackViewsWithModel(200);
         }
 
-        // Get the stack scroll of the task to anchor to (since we are removing something, the front
-        // most task will be our anchor task)
-        Task anchorTask = null;
-        float prevAnchorTaskScroll = 0;
-        boolean pullStackForward = stack.getTaskCount() > 0;
-        if (pullStackForward) {
-            anchorTask = mStack.getFrontMostTask();
-            prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
-        }
-
-        // Update the min/max scroll and animate other task views into their new positions
-        updateMinMaxScroll(true);
-
-        if (wasFrontMostTask) {
-            // Since the max scroll progress is offset from the bottom of the stack, just scroll
-            // to ensure that the new front most task is now fully visible
-            mStackScroller.setStackScroll(mLayoutAlgorithm.mMaxScrollP);
-        } else if (pullStackForward) {
-            // Otherwise, offset the scroll by half the movement of the anchor task to allow the
-            // tasks behind the removed task to move forward, and the tasks in front to move back
-            float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
-            mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll
-                    - prevAnchorTaskScroll) / 2);
-            mStackScroller.boundScroll();
-        }
-
-        // Animate all the tasks into place
-        requestSynchronizeStackViewsWithModel(200);
-
         // Update the new front most task
         if (newFrontMostTask != null) {
             TaskView frontTv = getChildViewForTask(newFrontMostTask);
@@ -1103,7 +1125,6 @@
         int insertIndex = -1;
         int taskIndex = mStack.indexOfTask(task);
         if (taskIndex != -1) {
-
             List<TaskView> taskViews = getTaskViews();
             int taskViewCount = taskViews.size();
             for (int i = 0; i < taskViewCount; i++) {
@@ -1252,7 +1273,9 @@
             // the next task
             RecentsConfiguration config = Recents.getConfiguration();
             RecentsActivityLaunchState launchState = config.getLaunchState();
-            setFocusedTask(taskIndex - 1, true /* scrollToTask */, launchState.launchedWithAltTab);
+            setFocusedTask(taskIndex - 1,
+                    !mStack.getTasks().get(taskIndex - 1).isFreeformTask() /* scrollToTask */,
+                    launchState.launchedWithAltTab);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index 15fcab4..6b92aed 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -39,7 +39,7 @@
     }
 
     Context mContext;
-    TaskStackViewLayoutAlgorithm mLayoutAlgorithm;
+    TaskStackLayoutAlgorithm mLayoutAlgorithm;
     TaskStackViewScrollerCallbacks mCb;
 
     float mStackScrollP;
@@ -52,7 +52,7 @@
 
     Interpolator mLinearOutSlowInInterpolator;
 
-    public TaskStackViewScroller(Context context, TaskStackViewLayoutAlgorithm layoutAlgorithm) {
+    public TaskStackViewScroller(Context context, TaskStackLayoutAlgorithm layoutAlgorithm) {
         mContext = context;
         mScroller = new OverScroller(context);
         mLayoutAlgorithm = layoutAlgorithm;
@@ -121,7 +121,8 @@
 
     /** Returns the bounded stack scroll */
     float getBoundedStackScroll(float scroll) {
-        return Math.max(mLayoutAlgorithm.mMinScrollP, Math.min(mLayoutAlgorithm.mMaxScrollP, scroll));
+        return Math.max(mLayoutAlgorithm.mMinScrollP,
+                Math.min(mLayoutAlgorithm.getPreferredMaxScrollPosition(scroll), scroll));
     }
 
     /** Returns the amount that the absolute value of how much the scroll is out of bounds. */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 08889c5..9e6fb7b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -16,8 +16,10 @@
 
 package com.android.systemui.recents.views;
 
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.Resources;
+import android.util.Log;
 import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
@@ -30,6 +32,8 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
 import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
+import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.statusbar.FlingAnimationUtils;
 
 import java.util.List;
 
@@ -37,7 +41,7 @@
 class TaskStackViewTouchHandler implements SwipeHelper.Callback {
 
     private static final String TAG = "TaskStackViewTouchHandler";
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = false;
 
     private static int INACTIVE_POINTER_ID = -1;
 
@@ -45,6 +49,8 @@
     TaskStackView mSv;
     TaskStackViewScroller mScroller;
     VelocityTracker mVelocityTracker;
+    FlingAnimationUtils mFlingAnimUtils;
+    ValueAnimator mScrollFlingAnimator;
 
     boolean mIsScrolling;
     float mDownScrollP;
@@ -74,6 +80,7 @@
         mWindowTouchSlop = configuration.getScaledWindowTouchSlop();
         mSv = sv;
         mScroller = scroller;
+        mFlingAnimUtils = new FlingAnimationUtils(context, 0.2f);
 
         float densityScale = res.getDisplayMetrics().density;
         mOverscrollSize = res.getDimensionPixelSize(R.dimen.recents_stack_overscroll);
@@ -140,7 +147,7 @@
             return false;
         }
 
-        TaskStackViewLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm;
+        final TaskStackLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm;
         int action = ev.getAction();
         switch (action & MotionEvent.ACTION_MASK) {
             case MotionEvent.ACTION_DOWN: {
@@ -154,6 +161,7 @@
                 // Stop the current scroll if it is still flinging
                 mScroller.stopScroller();
                 mScroller.stopBoundScrollAnimation();
+                Utilities.cancelAnimationWithoutCallbacks(mScrollFlingAnimator);
 
                 // Initialize the velocity tracker
                 initOrResetVelocityTracker();
@@ -189,6 +197,9 @@
                     float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y);
                     float curScrollP = mDownScrollP + deltaP;
                     mScroller.setStackScroll(curScrollP);
+                    if (DEBUG) {
+                        Log.d(TAG, "scroll: " + curScrollP);
+                    }
                 }
 
                 mVelocityTracker.addMovement(ev);
@@ -211,21 +222,53 @@
                 int activePointerIndex = ev.findPointerIndex(mActivePointerId);
                 int y = (int) ev.getY(activePointerIndex);
                 int velocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
+                float curScrollP = mScroller.getStackScroll();
                 if (mIsScrolling) {
-                    if (mScroller.isScrollOutOfBounds()) {
-                        // Animate the scroll back into bounds
+                    boolean hasFreeformTasks = mSv.mStack.hasFreeformTasks();
+                    if (hasFreeformTasks && velocity > 0 &&
+                            curScrollP > layoutAlgorithm.mStackEndScrollP) {
+                        // Snap to workspace
+                        float finalY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
+                                layoutAlgorithm.mPreferredStackEndScrollP);
+                        mScrollFlingAnimator = ValueAnimator.ofInt(y, (int) finalY);
+                        mScrollFlingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+                            @Override
+                            public void onAnimationUpdate(ValueAnimator animation) {
+                                float deltaP = layoutAlgorithm.getDeltaPForY(mDownY,
+                                        (Integer) animation.getAnimatedValue());
+                                float scroll = mDownScrollP + deltaP;
+                                mScroller.setStackScroll(scroll);
+                            }
+                        });
+                        mFlingAnimUtils.apply(mScrollFlingAnimator, y, finalY, velocity);
+                        mScrollFlingAnimator.start();
+                    } else if (hasFreeformTasks && velocity < 0 &&
+                            curScrollP > (layoutAlgorithm.mStackEndScrollP -
+                                    layoutAlgorithm.mTaskHalfHeightPOffset)) {
+                        // Snap to stack
+                        float finalY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
+                                layoutAlgorithm.mMaxScrollP);
+                        mScrollFlingAnimator = ValueAnimator.ofInt(y, (int) finalY);
+                        mScrollFlingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+                            @Override
+                            public void onAnimationUpdate(ValueAnimator animation) {
+                                float deltaP = layoutAlgorithm.getDeltaPForY(mDownY,
+                                        (Integer) animation.getAnimatedValue());
+                                float scroll = mDownScrollP + deltaP;
+                                mScroller.setStackScroll(scroll);
+                            }
+                        });
+                        mFlingAnimUtils.apply(mScrollFlingAnimator, y, finalY, velocity);
+                        mScrollFlingAnimator.start();
+                    } else if (mScroller.isScrollOutOfBounds()) {
                         mScroller.animateBoundScroll();
                     } else if (Math.abs(velocity) > mMinimumVelocity) {
-                        float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y);
-                        float curScrollP = mDownScrollP + deltaP;
-                        float downToCurY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
-                                curScrollP);
-                        float downToMinY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
-                                layoutAlgorithm.mMaxScrollP);
-                        float downToMaxY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
+                        float minY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
+                                layoutAlgorithm.mPreferredStackEndScrollP);
+                        float maxY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
                                 layoutAlgorithm.mMinScrollP);
-                        mScroller.fling(mDownScrollP, mDownY, (int) downToCurY, velocity,
-                                (int) downToMinY, (int) downToMaxY, mOverscrollSize);
+                        mScroller.fling(mDownScrollP, mDownY, y, velocity, (int) minY, (int) maxY,
+                                mOverscrollSize);
                         mSv.invalidate();
                     }
                 } else if (mActiveTaskView == null) {
@@ -239,10 +282,6 @@
                 break;
             }
             case MotionEvent.ACTION_CANCEL: {
-                if (mScroller.isScrollOutOfBounds()) {
-                    // Animate the scroll back into bounds
-                    mScroller.animateBoundScroll();
-                }
                 mActivePointerId = INACTIVE_POINTER_ID;
                 mIsScrolling = false;
                 recycleVelocityTracker();
@@ -308,9 +347,6 @@
 
     @Override
     public boolean canChildBeDismissed(View v) {
-        if (v instanceof TaskView) {
-            return !((TaskView) v).getTask().isFreeformTask();
-        }
         return true;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index aaacc6c..57cb599 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -552,7 +552,11 @@
      * view.
      */
     boolean shouldClipViewInStack() {
-        return mClipViewInStack && (getVisibility() == View.VISIBLE);
+        // Never clip for freeform tasks or if invisible
+        if (mTask.isFreeformTask() || getVisibility() != View.VISIBLE) {
+            return false;
+        }
+        return mClipViewInStack;
     }
 
     /** Sets whether this view should be clipped, or clipped against. */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index f6353f8..649199e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -255,6 +255,10 @@
         mApplicationIcon.setImageDrawable(null);
         mApplicationIcon.setOnClickListener(null);
         mMoveTaskButton.setOnClickListener(null);
+
+        // Stop any focus animations
+        Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
+        mBackground.jumpToCurrentState();
     }
 
     /** Updates the resize task bar button. */
@@ -370,8 +374,9 @@
         boolean isRunning = false;
         if (mFocusAnimator != null) {
             isRunning = mFocusAnimator.isRunning();
-            Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
         }
+        Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
+        mBackground.jumpToCurrentState();
 
         if (focused) {
             // If we are not animating the visible state, just return
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
index 1cf7a70f..673a30b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
@@ -125,10 +125,6 @@
         return mAudio;
     }
 
-    public ZenModeConfig getZenModeConfig() {
-        return mNoMan.getZenModeConfig();
-    }
-
     public void dismiss() {
         mCallbacks.onDismissRequested(Events.DISMISS_REASON_VOLUME_CONTROLLER);
     }
@@ -348,7 +344,6 @@
         updateRingerModeExternalW(mAudio.getRingerMode());
         updateZenModeW();
         updateEffectsSuppressorW(mNoMan.getEffectsSuppressor());
-        updateZenModeConfigW();
         mCallbacks.onStateChanged(mState);
     }
 
@@ -401,13 +396,6 @@
         return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION;
     }
 
-    private boolean updateZenModeConfigW() {
-        final ZenModeConfig zenModeConfig = getZenModeConfig();
-        if (Objects.equals(mState.zenModeConfig, zenModeConfig)) return false;
-        mState.zenModeConfig = zenModeConfig;
-        return true;
-    }
-
     private boolean updateEffectsSuppressorW(ComponentName effectsSuppressor) {
         if (Objects.equals(mState.effectsSuppressor, effectsSuppressor)) return false;
         mState.effectsSuppressor = effectsSuppressor;
@@ -748,9 +736,6 @@
             if (ZEN_MODE_URI.equals(uri)) {
                 changed = updateZenModeW();
             }
-            if (ZEN_MODE_CONFIG_URI.equals(uri)) {
-                changed = updateZenModeConfigW();
-            }
             if (changed) {
                 mCallbacks.onStateChanged(mState);
             }
@@ -947,7 +932,6 @@
         public int zenMode;
         public ComponentName effectsSuppressor;
         public String effectsSuppressorName;
-        public ZenModeConfig zenModeConfig;
         public int activeStream = NO_ACTIVE_STREAM;
 
         public State copy() {
@@ -960,7 +944,6 @@
             rt.zenMode = zenMode;
             if (effectsSuppressor != null) rt.effectsSuppressor = effectsSuppressor.clone();
             rt.effectsSuppressorName = effectsSuppressorName;
-            if (zenModeConfig != null) rt.zenModeConfig = zenModeConfig.copy();
             rt.activeStream = activeStream;
             return rt;
         }
@@ -989,7 +972,6 @@
             sep(sb, indent); sb.append("zenMode:").append(zenMode);
             sep(sb, indent); sb.append("effectsSuppressor:").append(effectsSuppressor);
             sep(sb, indent); sb.append("effectsSuppressorName:").append(effectsSuppressorName);
-            sep(sb, indent); sb.append("zenModeConfig:").append(zenModeConfig);
             sep(sb, indent); sb.append("activeStream:").append(activeStream);
             if (indent > 0) sep(sb, indent);
             return sb.append('}').toString();
@@ -1005,11 +987,6 @@
                 sb.append(',');
             }
         }
-
-        public Condition getManualExitCondition() {
-            return zenModeConfig != null && zenModeConfig.manualRule != null
-                    ? zenModeConfig.manualRule.condition : null;
-        }
     }
 
     public interface Callbacks {
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index ed6fc00..a5ddc12 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -20,7 +20,10 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
 import android.app.AlarmManager;
+import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
+import android.app.IAlarmCompleteListener;
+import android.app.IAlarmListener;
 import android.app.IAlarmManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -94,12 +97,13 @@
     static final boolean DEBUG_BATCH = localLOGV || false;
     static final boolean DEBUG_VALIDATE = localLOGV || false;
     static final boolean DEBUG_ALARM_CLOCK = localLOGV || false;
+    static final boolean DEBUG_LISTENER_CALLBACK = localLOGV || false;
     static final boolean RECORD_ALARMS_IN_HISTORY = true;
     static final boolean RECORD_DEVICE_IDLE_ALARMS = false;
     static final int ALARM_EVENT = 1;
     static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
-    static final Intent mBackgroundIntent
+    private final Intent mBackgroundIntent
             = new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND);
     static final IncreasingTimeOrder sIncreasingTimeOrder = new IncreasingTimeOrder();
     
@@ -110,6 +114,8 @@
 
     final LocalLog mLog = new LocalLog(TAG);
 
+    AppOpsManager mAppOps;
+
     final Object mLock = new Object();
 
     long mNativeData;
@@ -124,7 +130,7 @@
     ClockReceiver mClockReceiver;
     InteractiveStateReceiver mInteractiveStateReceiver;
     private UninstallReceiver mUninstallReceiver;
-    final ResultReceiver mResultReceiver = new ResultReceiver();
+    final DeliveryTracker mDeliveryTracker = new DeliveryTracker();
     PendingIntent mTimeTickSender;
     PendingIntent mDateChangeSender;
     Random mRandom;
@@ -185,6 +191,7 @@
         private static final String KEY_ALLOW_WHILE_IDLE_LONG_TIME = "allow_while_idle_long_time";
         private static final String KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION
                 = "allow_while_idle_whitelist_duration";
+        private static final String KEY_LISTENER_TIMEOUT = "listener_timeout";
 
         private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
         private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
@@ -192,6 +199,8 @@
         private static final long DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME = 9*60*1000;
         private static final long DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION = 10*1000;
 
+        private static final long DEFAULT_LISTENER_TIMEOUT = 5 * 1000;
+
         // Minimum futurity of a new alarm
         public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY;
 
@@ -208,6 +217,9 @@
         public long ALLOW_WHILE_IDLE_WHITELIST_DURATION
                 = DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
 
+        // Direct alarm listener callback timeout
+        public long LISTENER_TIMEOUT = DEFAULT_LISTENER_TIMEOUT;
+
         private ContentResolver mResolver;
         private final KeyValueListParser mParser = new KeyValueListParser(',');
         private long mLastAllowWhileIdleWhitelistDuration = -1;
@@ -264,6 +276,8 @@
                 ALLOW_WHILE_IDLE_WHITELIST_DURATION = mParser.getLong(
                         KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION,
                         DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION);
+                LISTENER_TIMEOUT = mParser.getLong(KEY_LISTENER_TIMEOUT,
+                        DEFAULT_LISTENER_TIMEOUT);
 
                 updateAllowWhileIdleMinTimeLocked();
                 updateAllowWhileIdleWhitelistDurationLocked();
@@ -281,6 +295,10 @@
             TimeUtils.formatDuration(MIN_INTERVAL, pw);
             pw.println();
 
+            pw.print("    "); pw.print(KEY_LISTENER_TIMEOUT); pw.print("=");
+            TimeUtils.formatDuration(LISTENER_TIMEOUT, pw);
+            pw.println();
+
             pw.print("    "); pw.print(KEY_ALLOW_WHILE_IDLE_SHORT_TIME); pw.print("=");
             TimeUtils.formatDuration(ALLOW_WHILE_IDLE_SHORT_TIME, pw);
             pw.println();
@@ -388,14 +406,21 @@
             return newStart;
         }
 
-        boolean remove(final PendingIntent operation) {
+        boolean remove(final PendingIntent operation, final IAlarmListener listener) {
+            if (operation == null && listener == null) {
+                if (localLOGV) {
+                    Slog.w(TAG, "requested remove() of null operation",
+                            new RuntimeException("here"));
+                }
+                return false;
+            }
             boolean didRemove = false;
             long newStart = 0;  // recalculate endpoints as we go
             long newEnd = Long.MAX_VALUE;
             int newFlags = 0;
             for (int i = 0; i < alarms.size(); ) {
                 Alarm alarm = alarms.get(i);
-                if (alarm.operation.equals(operation)) {
+                if (alarm.matches(operation, listener)) {
                     alarms.remove(i);
                     didRemove = true;
                     if (alarm.alarmClock != null) {
@@ -422,13 +447,20 @@
         }
 
         boolean remove(final String packageName) {
+            if (packageName == null) {
+                if (localLOGV) {
+                    Slog.w(TAG, "requested remove() of null packageName",
+                            new RuntimeException("here"));
+                }
+                return false;
+            }
             boolean didRemove = false;
             long newStart = 0;  // recalculate endpoints as we go
             long newEnd = Long.MAX_VALUE;
             int newFlags = 0;
             for (int i = 0; i < alarms.size(); ) {
                 Alarm alarm = alarms.get(i);
-                if (alarm.operation.getTargetPackage().equals(packageName)) {
+                if (alarm.matches(packageName)) {
                     alarms.remove(i);
                     didRemove = true;
                     if (alarm.alarmClock != null) {
@@ -460,7 +492,7 @@
             long newEnd = Long.MAX_VALUE;
             for (int i = 0; i < alarms.size(); ) {
                 Alarm alarm = alarms.get(i);
-                if (UserHandle.getUserId(alarm.operation.getCreatorUid()) == userHandle) {
+                if (UserHandle.getUserId(alarm.creatorUid) == userHandle) {
                     alarms.remove(i);
                     didRemove = true;
                     if (alarm.alarmClock != null) {
@@ -488,7 +520,7 @@
             final int N = alarms.size();
             for (int i = 0; i < N; i++) {
                 Alarm a = alarms.get(i);
-                if (a.operation.getTargetPackage().equals(packageName)) {
+                if (a.matches(packageName)) {
                     return true;
                 }
             }
@@ -565,7 +597,8 @@
             Alarm a = alarms.get(i);
 
             final int alarmPrio;
-            if (Intent.ACTION_TIME_TICK.equals(a.operation.getIntent().getAction())) {
+            if (a.operation != null
+                    && Intent.ACTION_TIME_TICK.equals(a.operation.getIntent().getAction())) {
                 alarmPrio = PRIO_TICK;
             } else if (a.wakeup) {
                 alarmPrio = PRIO_WAKEUP;
@@ -574,10 +607,13 @@
             }
 
             PriorityClass packagePrio = a.priorityClass;
-            if (packagePrio == null) packagePrio = mPriorities.get(a.operation.getCreatorPackage());
+            String alarmPackage = (a.operation != null)
+                    ? a.operation.getCreatorPackage()
+                    : a.packageName;
+            if (packagePrio == null) packagePrio = mPriorities.get(alarmPackage);
             if (packagePrio == null) {
                 packagePrio = a.priorityClass = new PriorityClass(); // lowest prio & stale sequence
-                mPriorities.put(a.operation.getCreatorPackage(), packagePrio);
+                mPriorities.put(alarmPackage, packagePrio);
             }
             a.priorityClass = packagePrio;
 
@@ -744,20 +780,27 @@
         }
     }
 
-    static final class InFlight extends Intent {
+    static final class InFlight {
         final PendingIntent mPendingIntent;
+        final IBinder mListener;
         final WorkSource mWorkSource;
+        final int mUid;
         final String mTag;
         final BroadcastStats mBroadcastStats;
         final FilterStats mFilterStats;
         final int mAlarmType;
 
-        InFlight(AlarmManagerService service, PendingIntent pendingIntent, WorkSource workSource,
-                int alarmType, String tag, long nowELAPSED) {
+        InFlight(AlarmManagerService service, PendingIntent pendingIntent, IAlarmListener listener,
+                WorkSource workSource, int uid, String alarmPkg, int alarmType, String tag,
+                long nowELAPSED) {
             mPendingIntent = pendingIntent;
+            mListener = listener != null ? listener.asBinder() : null;
             mWorkSource = workSource;
+            mUid = uid;
             mTag = tag;
-            mBroadcastStats = service.getStatsLocked(pendingIntent);
+            mBroadcastStats = (pendingIntent != null)
+                    ? service.getStatsLocked(pendingIntent)
+                    : service.getStatsLocked(uid, alarmPkg);
             FilterStats fs = mBroadcastStats.filterStats.get(mTag);
             if (fs == null) {
                 fs = new FilterStats(mBroadcastStats, mTag);
@@ -853,6 +896,7 @@
     public void onBootPhase(int phase) {
         if (phase == PHASE_SYSTEM_SERVICES_READY) {
             mConstants.start(getContext().getContentResolver());
+            mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
         }
     }
 
@@ -905,15 +949,20 @@
             return;
         }
         synchronized (mLock) {
-            removeLocked(operation);
+            removeLocked(operation, null);
         }
     }
 
     void setImpl(int type, long triggerAtTime, long windowLength, long interval,
-            PendingIntent operation, int flags, WorkSource workSource,
-            AlarmManager.AlarmClockInfo alarmClock, int callingUid) {
-        if (operation == null) {
-            Slog.w(TAG, "set/setRepeating ignored because there is no intent");
+            PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
+            int flags, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
+            int callingUid, String callingPackage) {
+        // must be *either* PendingIntent or AlarmReceiver, but not both
+        if ((operation == null && directReceiver == null)
+                || (operation != null && directReceiver != null)) {
+            Slog.w(TAG, "Alarms must either supply a PendingIntent or an AlarmReceiver");
+            // NB: previous releases failed silently here, so we are continuing to do the same
+            // rather than throw an IllegalArgumentException.
             return;
         }
 
@@ -971,17 +1020,19 @@
                         + " interval=" + interval + " flags=0x" + Integer.toHexString(flags));
             }
             setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
-                    interval, operation, flags, true, workSource, alarmClock, callingUid);
+                    interval, operation, directReceiver, listenerTag, flags, true, workSource,
+                    alarmClock, callingUid, callingPackage);
         }
     }
 
     private void setImplLocked(int type, long when, long whenElapsed, long windowLength,
-            long maxWhen, long interval, PendingIntent operation, int flags,
-            boolean doValidate, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
-            int uid) {
+            long maxWhen, long interval, PendingIntent operation, IAlarmListener directReceiver,
+            String listenerTag, int flags, boolean doValidate, WorkSource workSource,
+            AlarmManager.AlarmClockInfo alarmClock, int callingUid, String callingPackage) {
         Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval,
-                operation, workSource, flags, alarmClock, uid);
-        removeLocked(operation);
+                operation, directReceiver, listenerTag, workSource, flags, alarmClock,
+                callingUid, callingPackage);
+        removeLocked(operation, directReceiver);
         setImplLocked(a, false, doValidate);
     }
 
@@ -1109,10 +1160,31 @@
 
     private final IBinder mService = new IAlarmManager.Stub() {
         @Override
-        public void set(int type, long triggerAtTime, long windowLength, long interval, int flags,
-                PendingIntent operation, WorkSource workSource,
-                AlarmManager.AlarmClockInfo alarmClock) {
+        public void set(String callingPackage,
+                int type, long triggerAtTime, long windowLength, long interval, int flags,
+                PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
+                WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock) {
             final int callingUid = Binder.getCallingUid();
+
+            // make sure the caller is not lying about which package should be blamed for
+            // wakelock time spent in alarm delivery
+            mAppOps.checkPackage(callingUid, callingPackage);
+
+            // Repeating alarms must use PendingIntent, not direct listener
+            if (interval != 0) {
+                if (directReceiver != null) {
+                    throw new IllegalArgumentException("Repeating alarms cannot use AlarmReceivers");
+                }
+            }
+
+            // direct-callback alarms must be wakeup alarms (otherwise they should just be
+            // posting work to a Handler)
+            if (directReceiver != null) {
+                if (type != RTC_WAKEUP && type != ELAPSED_REALTIME_WAKEUP) {
+                    throw new IllegalArgumentException("Only wakeup alarms can use AlarmReceivers");
+                }
+            }
+
             if (workSource != null) {
                 getContext().enforcePermission(
                         android.Manifest.permission.UPDATE_DEVICE_STATS,
@@ -1149,8 +1221,8 @@
                 flags |= AlarmManager.FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE;
             }
 
-            setImpl(type, triggerAtTime, windowLength, interval, operation,
-                    flags, workSource, alarmClock, callingUid);
+            setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver,
+                    listenerTag, flags, workSource, alarmClock, callingUid, callingPackage);
         }
 
         @Override
@@ -1184,9 +1256,15 @@
         }
 
         @Override
-        public void remove(PendingIntent operation) {
-            removeImpl(operation);
+        public void remove(PendingIntent operation, IAlarmListener listener) {
+            if (operation == null && listener == null) {
+                Slog.w(TAG, "remove() with no intent or listener");
+                return;
+            }
 
+            synchronized (mLock) {
+                removeLocked(operation, listener);
+            }
         }
 
         @Override
@@ -1697,17 +1775,17 @@
         }
     }
 
-    private void removeLocked(PendingIntent operation) {
+    private void removeLocked(PendingIntent operation, IAlarmListener directReceiver) {
         boolean didRemove = false;
         for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
             Batch b = mAlarmBatches.get(i);
-            didRemove |= b.remove(operation);
+            didRemove |= b.remove(operation, directReceiver);
             if (b.size() == 0) {
                 mAlarmBatches.remove(i);
             }
         }
         for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
-            if (mPendingWhileIdleAlarms.get(i).operation.equals(operation)) {
+            if (mPendingWhileIdleAlarms.get(i).matches(operation, directReceiver)) {
                 // Don't set didRemove, since this doesn't impact the scheduled alarms.
                 mPendingWhileIdleAlarms.remove(i);
             }
@@ -1718,11 +1796,11 @@
                 Slog.v(TAG, "remove(operation) changed bounds; rebatching");
             }
             boolean restorePending = false;
-            if (mPendingIdleUntil != null && mPendingIdleUntil.operation.equals(operation)) {
+            if (mPendingIdleUntil != null && mPendingIdleUntil.matches(operation, directReceiver)) {
                 mPendingIdleUntil = null;
                 restorePending = true;
             }
-            if (mNextWakeFromIdle != null && mNextWakeFromIdle.operation.equals(operation)) {
+            if (mNextWakeFromIdle != null && mNextWakeFromIdle.matches(operation, directReceiver)) {
                 mNextWakeFromIdle = null;
             }
             rebatchAllAlarmsLocked(true);
@@ -1743,7 +1821,8 @@
             }
         }
         for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
-            if (mPendingWhileIdleAlarms.get(i).operation.getTargetPackage().equals(packageName)) {
+            final Alarm a = mPendingWhileIdleAlarms.get(i);
+            if (a.matches(packageName)) {
                 // Don't set didRemove, since this doesn't impact the scheduled alarms.
                 mPendingWhileIdleAlarms.remove(i);
             }
@@ -1769,7 +1848,7 @@
             }
         }
         for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
-            if (UserHandle.getUserId(mPendingWhileIdleAlarms.get(i).operation.getCreatorUid())
+            if (UserHandle.getUserId(mPendingWhileIdleAlarms.get(i).creatorUid)
                     == userHandle) {
                 // Don't set didRemove, since this doesn't impact the scheduled alarms.
                 mPendingWhileIdleAlarms.remove(i);
@@ -1825,7 +1904,8 @@
             }
         }
         for (int i = 0; i < mPendingWhileIdleAlarms.size(); i++) {
-            if (mPendingWhileIdleAlarms.get(i).operation.getTargetPackage().equals(packageName)) {
+            final Alarm a = mPendingWhileIdleAlarms.get(i);
+            if (a.matches(packageName)) {
                 return true;
             }
         }
@@ -1948,7 +2028,7 @@
                 triggerList.add(alarm);
                 if ((alarm.flags&AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
                     EventLogTags.writeDeviceIdleWakeFromIdle(mPendingIdleUntil != null ? 1 : 0,
-                            alarm.tag);
+                            alarm.statsTag);
                 }
                 if (mPendingIdleUntil == alarm) {
                     mPendingIdleUntil = null;
@@ -1972,8 +2052,8 @@
                     final long nextElapsed = alarm.whenElapsed + delta;
                     setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength,
                             maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval),
-                            alarm.repeatInterval, alarm.operation, alarm.flags, true,
-                            alarm.workSource, alarm.alarmClock, alarm.uid);
+                            alarm.repeatInterval, alarm.operation, null, null, alarm.flags, true,
+                            alarm.workSource, alarm.alarmClock, alarm.uid, alarm.packageName);
                 }
 
                 if (alarm.wakeup) {
@@ -2024,11 +2104,15 @@
         public final long origWhen;
         public final boolean wakeup;
         public final PendingIntent operation;
-        public final String tag;
+        public final IAlarmListener listener;
+        public final String listenerTag;
+        public final String statsTag;
         public final WorkSource workSource;
         public final int flags;
         public final AlarmManager.AlarmClockInfo alarmClock;
         public final int uid;
+        public final int creatorUid;
+        public final String packageName;
         public int count;
         public long when;
         public long windowLength;
@@ -2038,8 +2122,9 @@
         public PriorityClass priorityClass;
 
         public Alarm(int _type, long _when, long _whenElapsed, long _windowLength, long _maxWhen,
-                long _interval, PendingIntent _op, WorkSource _ws, int _flags,
-                AlarmManager.AlarmClockInfo _info, int _uid) {
+                long _interval, PendingIntent _op, IAlarmListener _rec, String _listenerTag,
+                WorkSource _ws, int _flags, AlarmManager.AlarmClockInfo _info,
+                int _uid, String _pkgName) {
             type = _type;
             origWhen = _when;
             wakeup = _type == AlarmManager.ELAPSED_REALTIME_WAKEUP
@@ -2050,16 +2135,42 @@
             maxWhenElapsed = _maxWhen;
             repeatInterval = _interval;
             operation = _op;
-            tag = makeTag(_op, _type);
+            listener = _rec;
+            listenerTag = _listenerTag;
+            statsTag = makeTag(_op, _listenerTag, _type);
             workSource = _ws;
             flags = _flags;
             alarmClock = _info;
             uid = _uid;
+            packageName = _pkgName;
+
+            creatorUid = (operation != null) ? operation.getCreatorUid() : uid;
         }
 
-        public static String makeTag(PendingIntent pi, int type) {
-            return pi.getTag(type == ELAPSED_REALTIME_WAKEUP || type == RTC_WAKEUP
-                    ? "*walarm*:" : "*alarm*:");
+        public static String makeTag(PendingIntent pi, String tag, int type) {
+            final String alarmString = type == ELAPSED_REALTIME_WAKEUP || type == RTC_WAKEUP
+                    ? "*walarm*:" : "*alarm*:";
+            return (pi != null) ? pi.getTag(alarmString) : (alarmString + tag);
+        }
+
+        public WakeupEvent makeWakeupEvent(long nowRTC) {
+            return new WakeupEvent(nowRTC, creatorUid,
+                    (operation != null)
+                        ? operation.getIntent().getAction()
+                        : ("<listener>:" + listenerTag));
+        }
+
+        // Returns true if either matches
+        public boolean matches(PendingIntent pi, IAlarmListener rec) {
+            return (operation != null)
+                    ? operation.equals(pi)
+                    : listener.asBinder().equals(rec.asBinder());
+        }
+
+        public boolean matches(String packageName) {
+            return (operation != null)
+                    ? packageName.equals(operation.getTargetPackage())
+                    : packageName.equals(this.packageName);
         }
 
         @Override
@@ -2072,7 +2183,11 @@
             sb.append(" when ");
             sb.append(when);
             sb.append(" ");
-            sb.append(operation.getTargetPackage());
+            if (operation != null) {
+                sb.append(operation.getTargetPackage());
+            } else {
+                sb.append(listener.asBinder().toString());
+            }
             sb.append('}');
             return sb.toString();
         }
@@ -2080,14 +2195,15 @@
         public void dump(PrintWriter pw, String prefix, long nowRTC, long nowELAPSED,
                 SimpleDateFormat sdf) {
             final boolean isRtc = (type == RTC || type == RTC_WAKEUP);
-            pw.print(prefix); pw.print("tag="); pw.println(tag);
+            pw.print(prefix); pw.print("tag="); pw.println(statsTag);
             pw.print(prefix); pw.print("type="); pw.print(type);
                     pw.print(" whenElapsed="); TimeUtils.formatDuration(whenElapsed,
                             nowELAPSED, pw);
+                    pw.print(" when=");
                     if (isRtc) {
-                        pw.print(" when="); pw.print(sdf.format(new Date(when)));
+                        pw.print(sdf.format(new Date(when)));
                     } else {
-                        pw.print(" when="); TimeUtils.formatDuration(when, nowELAPSED, pw);
+                        TimeUtils.formatDuration(when, nowELAPSED, pw);
                     }
                     pw.println();
             pw.print(prefix); pw.print("window="); TimeUtils.formatDuration(windowLength, pw);
@@ -2101,6 +2217,9 @@
                 pw.print(prefix); pw.print("  showIntent="); pw.println(alarmClock.getShowIntent());
             }
             pw.print(prefix); pw.print("operation="); pw.println(operation);
+            if (listener != null) {
+                pw.print(prefix); pw.print("listener="); pw.println(listener.asBinder());
+            }
         }
     }
 
@@ -2115,10 +2234,7 @@
             final int numAlarms = b.alarms.size();
             for (int nextAlarm = 0; nextAlarm < numAlarms; nextAlarm++) {
                 Alarm a = b.alarms.get(nextAlarm);
-                WakeupEvent e = new WakeupEvent(nowRTC,
-                        a.operation.getCreatorUid(),
-                        a.operation.getIntent().getAction());
-                mRecentWakeups.add(e);
+                mRecentWakeups.add(a.makeWakeupEvent(nowRTC));
             }
         }
     }
@@ -2181,80 +2297,14 @@
                     if (alarm.workSource != null && alarm.workSource.size() > 0) {
                         for (int wi=0; wi<alarm.workSource.size(); wi++) {
                             ActivityManagerNative.noteAlarmStart(
-                                    alarm.operation, alarm.workSource.get(wi), alarm.tag);
+                                    alarm.operation, alarm.workSource.get(wi), alarm.statsTag);
                         }
                     } else {
                         ActivityManagerNative.noteAlarmStart(
-                                alarm.operation, -1, alarm.tag);
+                                alarm.operation, alarm.uid, alarm.statsTag);
                     }
                 }
-                alarm.operation.send(getContext(), 0,
-                        mBackgroundIntent.putExtra(
-                                Intent.EXTRA_ALARM_COUNT, alarm.count),
-                        mResultReceiver, mHandler, null, allowWhileIdle ? mIdleOptions : null);
-
-                // we have an active broadcast so stay awake.
-                if (mBroadcastRefCount == 0) {
-                    setWakelockWorkSource(alarm.operation, alarm.workSource,
-                            alarm.type, alarm.tag, true);
-                    mWakeLock.acquire();
-                }
-                final InFlight inflight = new InFlight(AlarmManagerService.this,
-                        alarm.operation, alarm.workSource, alarm.type, alarm.tag, nowELAPSED);
-                mInFlight.add(inflight);
-                mBroadcastRefCount++;
-
-                if (allowWhileIdle) {
-                    // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm.
-                    mLastAllowWhileIdleDispatch.put(alarm.uid, nowELAPSED);
-                    if (RECORD_DEVICE_IDLE_ALARMS) {
-                        IdleDispatchEntry ent = new IdleDispatchEntry();
-                        ent.uid = alarm.uid;
-                        ent.pkg = alarm.operation.getCreatorPackage();
-                        ent.tag = alarm.operation.getTag("");
-                        ent.op = "DELIVER";
-                        ent.elapsedRealtime = nowELAPSED;
-                        mAllowWhileIdleDispatches.add(ent);
-                    }
-                }
-
-                final BroadcastStats bs = inflight.mBroadcastStats;
-                bs.count++;
-                if (bs.nesting == 0) {
-                    bs.nesting = 1;
-                    bs.startTime = nowELAPSED;
-                } else {
-                    bs.nesting++;
-                }
-                final FilterStats fs = inflight.mFilterStats;
-                fs.count++;
-                if (fs.nesting == 0) {
-                    fs.nesting = 1;
-                    fs.startTime = nowELAPSED;
-                } else {
-                    fs.nesting++;
-                }
-                if (alarm.type == ELAPSED_REALTIME_WAKEUP
-                        || alarm.type == RTC_WAKEUP) {
-                    bs.numWakeup++;
-                    fs.numWakeup++;
-                    if (alarm.workSource != null && alarm.workSource.size() > 0) {
-                        for (int wi=0; wi<alarm.workSource.size(); wi++) {
-                            ActivityManagerNative.noteWakeupAlarm(
-                                    alarm.operation, alarm.workSource.get(wi),
-                                    alarm.workSource.getName(wi), alarm.tag);
-                        }
-                    } else {
-                        ActivityManagerNative.noteWakeupAlarm(
-                                alarm.operation, -1, null, alarm.tag);
-                    }
-                }
-            } catch (PendingIntent.CanceledException e) {
-                if (alarm.repeatInterval > 0) {
-                    // This IntentSender is no longer valid, but this
-                    // is a repeating alarm, so toss the hoser.
-                    removeImpl(alarm.operation);
-                }
+                mDeliveryTracker.deliverLocked(alarm, nowELAPSED, allowWhileIdle);
             } catch (RuntimeException e) {
                 Slog.w(TAG, "Failure sending alarm.", e);
             }
@@ -2384,9 +2434,10 @@
      * Attribute blame for a WakeLock.
      * @param pi PendingIntent to attribute blame to if ws is null.
      * @param ws WorkSource to attribute blame.
+     * @param knownUid attribution uid; < 0 if we need to derive it from the PendingIntent sender
      */
     void setWakelockWorkSource(PendingIntent pi, WorkSource ws, int type, String tag,
-            boolean first) {
+            int knownUid, boolean first) {
         try {
             final boolean unimportant = pi == mTimeTickSender;
             mWakeLock.setUnimportantForLogging(unimportant);
@@ -2401,8 +2452,9 @@
                 return;
             }
 
-            final int uid = ActivityManagerNative.getDefault()
-                    .getUidForIntentSender(pi.getTarget());
+            final int uid = (knownUid >= 0)
+                    ? knownUid
+                    : ActivityManagerNative.getDefault().getUidForIntentSender(pi.getTarget());
             if (uid >= 0) {
                 mWakeLock.setWorkSource(new WorkSource(uid));
                 return;
@@ -2419,35 +2471,49 @@
         public static final int MINUTE_CHANGE_EVENT = 2;
         public static final int DATE_CHANGE_EVENT = 3;
         public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 4;
+        public static final int LISTENER_TIMEOUT = 5;
         
         public AlarmHandler() {
         }
         
         public void handleMessage(Message msg) {
-            if (msg.what == ALARM_EVENT) {
-                ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
-                synchronized (mLock) {
-                    final long nowRTC = System.currentTimeMillis();
-                    final long nowELAPSED = SystemClock.elapsedRealtime();
-                    triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC);
-                    updateNextAlarmClockLocked();
-                }
+            switch (msg.what) {
+                case ALARM_EVENT: {
+                    ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
+                    synchronized (mLock) {
+                        final long nowRTC = System.currentTimeMillis();
+                        final long nowELAPSED = SystemClock.elapsedRealtime();
+                        triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC);
+                        updateNextAlarmClockLocked();
+                    }
 
-                // now trigger the alarms without the lock held
-                for (int i=0; i<triggerList.size(); i++) {
-                    Alarm alarm = triggerList.get(i);
-                    try {
-                        alarm.operation.send();
-                    } catch (PendingIntent.CanceledException e) {
-                        if (alarm.repeatInterval > 0) {
-                            // This IntentSender is no longer valid, but this
-                            // is a repeating alarm, so toss the hoser.
-                            removeImpl(alarm.operation);
+                    // now trigger the alarms without the lock held
+                    for (int i=0; i<triggerList.size(); i++) {
+                        Alarm alarm = triggerList.get(i);
+                        try {
+                            alarm.operation.send();
+                        } catch (PendingIntent.CanceledException e) {
+                            if (alarm.repeatInterval > 0) {
+                                // This IntentSender is no longer valid, but this
+                                // is a repeating alarm, so toss the hoser.
+                                removeImpl(alarm.operation);
+                            }
                         }
                     }
+                    break;
                 }
-            } else if (msg.what == SEND_NEXT_ALARM_CLOCK_CHANGED) {
-                sendNextAlarmClockChanged();
+
+                case SEND_NEXT_ALARM_CLOCK_CHANGED:
+                    sendNextAlarmClockChanged();
+                    break;
+
+                case LISTENER_TIMEOUT:
+                    mDeliveryTracker.alarmTimedOut((IBinder) msg.obj);
+                    break;
+
+                default:
+                    // nope, just ignore it
+                    break;
             }
         }
     }
@@ -2489,8 +2555,8 @@
 
             final WorkSource workSource = null; // Let system take blame for time tick events.
             setImpl(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, 0,
-                    0, mTimeTickSender, AlarmManager.FLAG_STANDALONE, workSource, null,
-                    Process.myUid());
+                    0, mTimeTickSender, null, null, AlarmManager.FLAG_STANDALONE, workSource,
+                    null, Process.myUid(), "android");
         }
 
         public void scheduleDateChangedEvent() {
@@ -2503,8 +2569,9 @@
             calendar.add(Calendar.DAY_OF_MONTH, 1);
 
             final WorkSource workSource = null; // Let system take blame for date change events.
-            setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender,
-                    AlarmManager.FLAG_STANDALONE, workSource, null, Process.myUid());
+            setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, null, null,
+                    AlarmManager.FLAG_STANDALONE, workSource, null,
+                    Process.myUid(), "android");
         }
     }
     
@@ -2602,80 +2669,260 @@
     private final BroadcastStats getStatsLocked(PendingIntent pi) {
         String pkg = pi.getCreatorPackage();
         int uid = pi.getCreatorUid();
+        return getStatsLocked(uid, pkg);
+    }
+
+    private final BroadcastStats getStatsLocked(int uid, String pkgName) {
         ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.get(uid);
         if (uidStats == null) {
             uidStats = new ArrayMap<String, BroadcastStats>();
             mBroadcastStats.put(uid, uidStats);
         }
-        BroadcastStats bs = uidStats.get(pkg);
+        BroadcastStats bs = uidStats.get(pkgName);
         if (bs == null) {
-            bs = new BroadcastStats(uid, pkg);
-            uidStats.put(pkg, bs);
+            bs = new BroadcastStats(uid, pkgName);
+            uidStats.put(pkgName, bs);
         }
         return bs;
     }
 
-    class ResultReceiver implements PendingIntent.OnFinished {
+    class DeliveryTracker extends IAlarmCompleteListener.Stub implements PendingIntent.OnFinished {
+        private InFlight removeLocked(PendingIntent pi, Intent intent) {
+            for (int i = 0; i < mInFlight.size(); i++) {
+                if (mInFlight.get(i).mPendingIntent == pi) {
+                    return mInFlight.remove(i);
+                }
+            }
+            mLog.w("No in-flight alarm for " + pi + " " + intent);
+            return null;
+        }
+
+        private InFlight removeLocked(IBinder listener) {
+            for (int i = 0; i < mInFlight.size(); i++) {
+                if (mInFlight.get(i).mListener == listener) {
+                    return mInFlight.remove(i);
+                }
+            }
+            mLog.w("No in-flight alarm for listener " + listener);
+            return null;
+        }
+
+        private void updateStatsLocked(InFlight inflight) {
+            final long nowELAPSED = SystemClock.elapsedRealtime();
+            BroadcastStats bs = inflight.mBroadcastStats;
+            bs.nesting--;
+            if (bs.nesting <= 0) {
+                bs.nesting = 0;
+                bs.aggregateTime += nowELAPSED - bs.startTime;
+            }
+            FilterStats fs = inflight.mFilterStats;
+            fs.nesting--;
+            if (fs.nesting <= 0) {
+                fs.nesting = 0;
+                fs.aggregateTime += nowELAPSED - fs.startTime;
+            }
+            if (RECORD_ALARMS_IN_HISTORY) {
+                if (inflight.mWorkSource != null && inflight.mWorkSource.size() > 0) {
+                    for (int wi=0; wi<inflight.mWorkSource.size(); wi++) {
+                        ActivityManagerNative.noteAlarmFinish(
+                                inflight.mPendingIntent, inflight.mWorkSource.get(wi), inflight.mTag);
+                    }
+                } else {
+                    ActivityManagerNative.noteAlarmFinish(
+                            inflight.mPendingIntent, inflight.mUid, inflight.mTag);
+                }
+            }
+        }
+
+        private void updateTrackingLocked(InFlight inflight) {
+            if (inflight != null) {
+                updateStatsLocked(inflight);
+            }
+            mBroadcastRefCount--;
+            if (mBroadcastRefCount == 0) {
+                mWakeLock.release();
+                if (mInFlight.size() > 0) {
+                    mLog.w("Finished all dispatches with " + mInFlight.size()
+                            + " remaining inflights");
+                    for (int i=0; i<mInFlight.size(); i++) {
+                        mLog.w("  Remaining #" + i + ": " + mInFlight.get(i));
+                    }
+                    mInFlight.clear();
+                }
+            } else {
+                // the next of our alarms is now in flight.  reattribute the wakelock.
+                if (mInFlight.size() > 0) {
+                    InFlight inFlight = mInFlight.get(0);
+                    setWakelockWorkSource(inFlight.mPendingIntent, inFlight.mWorkSource,
+                            inFlight.mAlarmType, inFlight.mTag, -1, false);
+                } else {
+                    // should never happen
+                    mLog.w("Alarm wakelock still held but sent queue empty");
+                    mWakeLock.setWorkSource(null);
+                }
+            }
+        }
+
+        /**
+         * Callback that arrives when a direct-call alarm reports that delivery has finished
+         */
+        @Override
+        public void alarmComplete(IBinder who) {
+            if (who == null) {
+                Slog.w(TAG, "Invalid alarmComplete: uid=" + Binder.getCallingUid()
+                        + " pid=" + Binder.getCallingPid());
+                return;
+            }
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    mHandler.removeMessages(AlarmHandler.LISTENER_TIMEOUT, who);
+                    InFlight inflight = removeLocked(who);
+                    if (inflight != null) {
+                        if (DEBUG_LISTENER_CALLBACK) {
+                            Slog.i(TAG, "alarmComplete() from " + who);
+                        }
+                        updateTrackingLocked(inflight);
+                    } else {
+                        // Delivery timed out, and the timeout handling already took care of
+                        // updating our tracking here, so we needn't do anything further.
+                        if (DEBUG_LISTENER_CALLBACK) {
+                            Slog.i(TAG, "Late alarmComplete() from " + who);
+                        }
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        /**
+         * Callback that arrives when a PendingIntent alarm has finished delivery
+         */
+        @Override
         public void onSendFinished(PendingIntent pi, Intent intent, int resultCode,
                 String resultData, Bundle resultExtras) {
             synchronized (mLock) {
-                InFlight inflight = null;
-                for (int i=0; i<mInFlight.size(); i++) {
-                    if (mInFlight.get(i).mPendingIntent == pi) {
-                        inflight = mInFlight.remove(i);
-                        break;
-                    }
-                }
+                updateTrackingLocked(removeLocked(pi, intent));
+            }
+        }
+
+        /**
+         * Timeout of a direct-call alarm delivery
+         */
+        public void alarmTimedOut(IBinder who) {
+            synchronized (mLock) {
+                InFlight inflight = removeLocked(who);
                 if (inflight != null) {
-                    final long nowELAPSED = SystemClock.elapsedRealtime();
-                    BroadcastStats bs = inflight.mBroadcastStats;
-                    bs.nesting--;
-                    if (bs.nesting <= 0) {
-                        bs.nesting = 0;
-                        bs.aggregateTime += nowELAPSED - bs.startTime;
+                    // TODO: implement ANR policy for the target
+                    if (DEBUG_LISTENER_CALLBACK) {
+                        Slog.i(TAG, "Alarm listener " + who + " timed out in delivery");
                     }
-                    FilterStats fs = inflight.mFilterStats;
-                    fs.nesting--;
-                    if (fs.nesting <= 0) {
-                        fs.nesting = 0;
-                        fs.aggregateTime += nowELAPSED - fs.startTime;
-                    }
-                    if (RECORD_ALARMS_IN_HISTORY) {
-                        if (inflight.mWorkSource != null && inflight.mWorkSource.size() > 0) {
-                            for (int wi=0; wi<inflight.mWorkSource.size(); wi++) {
-                                ActivityManagerNative.noteAlarmFinish(
-                                        pi, inflight.mWorkSource.get(wi), inflight.mTag);
-                            }
-                        } else {
-                            ActivityManagerNative.noteAlarmFinish(
-                                    pi, -1, inflight.mTag);
-                        }
-                    }
+                    updateTrackingLocked(inflight);
                 } else {
-                    mLog.w("No in-flight alarm for " + pi + " " + intent);
+                    if (DEBUG_LISTENER_CALLBACK) {
+                        Slog.i(TAG, "Spurious timeout of listener " + who);
+                    }
                 }
-                mBroadcastRefCount--;
-                if (mBroadcastRefCount == 0) {
-                    mWakeLock.release();
-                    if (mInFlight.size() > 0) {
-                        mLog.w("Finished all broadcasts with " + mInFlight.size()
-                                + " remaining inflights");
-                        for (int i=0; i<mInFlight.size(); i++) {
-                            mLog.w("  Remaining #" + i + ": " + mInFlight.get(i));
-                        }
-                        mInFlight.clear();
+            }
+        }
+
+        /**
+         * Deliver an alarm and set up the post-delivery handling appropriately
+         */
+        public void deliverLocked(Alarm alarm, long nowELAPSED, boolean allowWhileIdle) {
+            if (alarm.operation != null) {
+                // PendingIntent alarm
+                try {
+                    alarm.operation.send(getContext(), 0,
+                            mBackgroundIntent.putExtra(
+                                    Intent.EXTRA_ALARM_COUNT, alarm.count),
+                                    mDeliveryTracker, mHandler, null,
+                                    allowWhileIdle ? mIdleOptions : null);
+                } catch (PendingIntent.CanceledException e) {
+                    if (alarm.repeatInterval > 0) {
+                        // This IntentSender is no longer valid, but this
+                        // is a repeating alarm, so toss it
+                        removeImpl(alarm.operation);
+                    }
+                }
+            } else {
+                // Direct listener callback alarm
+                try {
+                    if (DEBUG_LISTENER_CALLBACK) {
+                        Slog.v(TAG, "Alarm to uid=" + alarm.uid
+                                + " listener=" + alarm.listener.asBinder());
+                    }
+                    alarm.listener.doAlarm(this);
+                    mHandler.sendMessageDelayed(
+                            mHandler.obtainMessage(AlarmHandler.LISTENER_TIMEOUT,
+                                    alarm.listener.asBinder()),
+                            mConstants.LISTENER_TIMEOUT);
+                } catch (Exception e) {
+                    if (DEBUG_LISTENER_CALLBACK) {
+                        Slog.i(TAG, "Alarm undeliverable to listener "
+                                + alarm.listener.asBinder(), e);
+                    }
+                }
+            }
+
+            // The alarm is now in flight; now arrange wakelock and stats tracking
+            if (mBroadcastRefCount == 0) {
+                setWakelockWorkSource(alarm.operation, alarm.workSource,
+                        alarm.type, alarm.statsTag, (alarm.operation == null) ? alarm.uid : -1,
+                        true);
+                mWakeLock.acquire();
+            }
+            final InFlight inflight = new InFlight(AlarmManagerService.this,
+                    alarm.operation, alarm.listener, alarm.workSource, alarm.uid,
+                    alarm.packageName, alarm.type, alarm.statsTag, nowELAPSED);
+            mInFlight.add(inflight);
+            mBroadcastRefCount++;
+
+            if (allowWhileIdle) {
+                // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm.
+                mLastAllowWhileIdleDispatch.put(alarm.uid, nowELAPSED);
+                if (RECORD_DEVICE_IDLE_ALARMS) {
+                    IdleDispatchEntry ent = new IdleDispatchEntry();
+                    ent.uid = alarm.uid;
+                    ent.pkg = alarm.packageName;
+                    ent.tag = alarm.statsTag;
+                    ent.op = "DELIVER";
+                    ent.elapsedRealtime = nowELAPSED;
+                    mAllowWhileIdleDispatches.add(ent);
+                }
+            }
+
+            final BroadcastStats bs = inflight.mBroadcastStats;
+            bs.count++;
+            if (bs.nesting == 0) {
+                bs.nesting = 1;
+                bs.startTime = nowELAPSED;
+            } else {
+                bs.nesting++;
+            }
+            final FilterStats fs = inflight.mFilterStats;
+            fs.count++;
+            if (fs.nesting == 0) {
+                fs.nesting = 1;
+                fs.startTime = nowELAPSED;
+            } else {
+                fs.nesting++;
+            }
+            if (alarm.type == ELAPSED_REALTIME_WAKEUP
+                    || alarm.type == RTC_WAKEUP) {
+                bs.numWakeup++;
+                fs.numWakeup++;
+                if (alarm.workSource != null && alarm.workSource.size() > 0) {
+                    for (int wi=0; wi<alarm.workSource.size(); wi++) {
+                        ActivityManagerNative.noteWakeupAlarm(
+                                alarm.operation, alarm.workSource.get(wi),
+                                alarm.workSource.getName(wi), alarm.statsTag);
                     }
                 } else {
-                    // the next of our alarms is now in flight.  reattribute the wakelock.
-                    if (mInFlight.size() > 0) {
-                        InFlight inFlight = mInFlight.get(0);
-                        setWakelockWorkSource(inFlight.mPendingIntent, inFlight.mWorkSource,
-                                inFlight.mAlarmType, inFlight.mTag, false);
-                    } else {
-                        // should never happen
-                        mLog.w("Alarm wakelock still held but sent queue empty");
-                        mWakeLock.setWorkSource(null);
-                    }
+                    ActivityManagerNative.noteWakeupAlarm(
+                            alarm.operation, -1, alarm.packageName, alarm.statsTag);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 29544f3..5aab804 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -11364,7 +11364,7 @@
     }
 
     public void noteWakeupAlarm(IIntentSender sender, int sourceUid, String sourcePkg, String tag) {
-        if (!(sender instanceof PendingIntentRecord)) {
+        if (sender != null && !(sender instanceof PendingIntentRecord)) {
             return;
         }
         final PendingIntentRecord rec = (PendingIntentRecord)sender;
@@ -11373,7 +11373,12 @@
             if (mBatteryStatsService.isOnBattery()) {
                 mBatteryStatsService.enforceCallingPermission();
                 int MY_UID = Binder.getCallingUid();
-                int uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+                final int uid;
+                if (sender == null) {
+                    uid = sourceUid;
+                } else {
+                    uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+                }
                 BatteryStatsImpl.Uid.Pkg pkg =
                     stats.getPackageStatsLocked(sourceUid >= 0 ? sourceUid : uid,
                             sourcePkg != null ? sourcePkg : rec.key.packageName);
@@ -11383,7 +11388,7 @@
     }
 
     public void noteAlarmStart(IIntentSender sender, int sourceUid, String tag) {
-        if (!(sender instanceof PendingIntentRecord)) {
+        if (sender != null && !(sender instanceof PendingIntentRecord)) {
             return;
         }
         final PendingIntentRecord rec = (PendingIntentRecord)sender;
@@ -11391,13 +11396,18 @@
         synchronized (stats) {
             mBatteryStatsService.enforceCallingPermission();
             int MY_UID = Binder.getCallingUid();
-            int uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+            final int uid;
+            if (sender == null) {
+                uid = sourceUid;
+            } else {
+                uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+            }
             mBatteryStatsService.noteAlarmStart(tag, sourceUid >= 0 ? sourceUid : uid);
         }
     }
 
     public void noteAlarmFinish(IIntentSender sender, int sourceUid, String tag) {
-        if (!(sender instanceof PendingIntentRecord)) {
+        if (sender != null && !(sender instanceof PendingIntentRecord)) {
             return;
         }
         final PendingIntentRecord rec = (PendingIntentRecord)sender;
@@ -11405,7 +11415,12 @@
         synchronized (stats) {
             mBatteryStatsService.enforceCallingPermission();
             int MY_UID = Binder.getCallingUid();
-            int uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+            final int uid;
+            if (sender == null) {
+                uid = sourceUid;
+            } else {
+                uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+            }
             mBatteryStatsService.noteAlarmFinish(tag, sourceUid >= 0 ? sourceUid : uid);
         }
     }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c4b57f1..66e731a 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -109,6 +109,7 @@
 import com.android.internal.util.XmlUtils;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
+import com.android.server.SystemService;
 import com.android.server.pm.UserManagerService;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -279,7 +280,7 @@
         0,  // STREAM_MUSIC
         0,  // STREAM_ALARM
         0,  // STREAM_NOTIFICATION
-        1,  // STREAM_BLUETOOTH_SCO
+        0,  // STREAM_BLUETOOTH_SCO
         0,  // STREAM_SYSTEM_ENFORCED
         0,  // STREAM_DTMF
         0   // STREAM_TTS
@@ -564,6 +565,27 @@
         return "card=" + card + ";device=" + device + ";";
     }
 
+    public static final class Lifecycle extends SystemService {
+        private AudioService mService;
+
+        public Lifecycle(Context context) {
+            super(context);
+            mService = new AudioService(context);
+        }
+
+        @Override
+        public void onStart() {
+            publishBinderService(Context.AUDIO_SERVICE, mService);
+        }
+
+        @Override
+        public void onBootPhase(int phase) {
+            if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+                mService.systemReady();
+            }
+        }
+    }
+
     ///////////////////////////////////////////////////////////////////////////
     // Construction
     ///////////////////////////////////////////////////////////////////////////
@@ -781,7 +803,8 @@
         int numStreamTypes = AudioSystem.getNumStreamTypes();
         for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
             VolumeStreamState streamState = mStreamStates[streamType];
-            AudioSystem.initStreamVolume(streamType, 0, (streamState.mIndexMax + 5) / 10);
+            AudioSystem.initStreamVolume(
+                streamType, streamState.mIndexMin / 10, streamState.mIndexMax / 10);
 
             streamState.applyAllVolumes();
         }
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 19d8538..9441d88 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -17,6 +17,7 @@
 package com.android.server.notification;
 
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.net.Uri;
 import android.os.Handler;
@@ -29,6 +30,7 @@
 import android.service.notification.ConditionProviderService;
 import android.service.notification.IConditionListener;
 import android.service.notification.IConditionProvider;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -79,7 +81,7 @@
         final Config c = new Config();
         c.caption = "condition provider";
         c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE;
-        c.secureSettingName = Settings.Secure.ENABLED_CONDITION_PROVIDERS;
+        c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES;
         c.bindPermission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE;
         c.settingsAction = Settings.ACTION_CONDITION_PROVIDER_SETTINGS;
         c.clientLabel = R.string.condition_provider_service_binding_label;
@@ -280,6 +282,26 @@
         }
     }
 
+    @Override
+    protected ArraySet<ComponentName> loadComponentNamesFromSetting(String settingName,
+            int userId) {
+        final ContentResolver cr = mContext.getContentResolver();
+        String settingValue = Settings.Secure.getStringForUser(
+                cr,
+                settingName,
+                userId);
+        if (TextUtils.isEmpty(settingValue))
+            return null;
+        String[] packages = settingValue.split(ENABLED_SERVICES_SEPARATOR);
+        ArraySet<ComponentName> result = new ArraySet<>(packages.length);
+        for (int i = 0; i < packages.length; i++) {
+            if (!TextUtils.isEmpty(packages[i])) {
+                result.addAll(queryPackageForServices(packages[i], userId));
+            }
+        }
+        return result;
+    }
+
     public boolean subscribeIfNecessary(ComponentName component, Uri conditionId) {
         synchronized (mMutex) {
             final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index a54a61a..d2a264d 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -70,7 +70,7 @@
     protected final String TAG = getClass().getSimpleName();
     protected final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    private static final String ENABLED_SERVICES_SEPARATOR = ":";
+    protected static final String ENABLED_SERVICES_SEPARATOR = ":";
 
     protected final Context mContext;
     protected final Object mMutex;
@@ -279,7 +279,8 @@
     }
 
 
-    private ArraySet<ComponentName> loadComponentNamesFromSetting(String settingName, int userId) {
+    protected ArraySet<ComponentName> loadComponentNamesFromSetting(String settingName,
+            int userId) {
         final ContentResolver cr = mContext.getContentResolver();
         String settingValue = Settings.Secure.getStringForUser(
                 cr,
@@ -319,7 +320,6 @@
                 userId);
     }
 
-
     /**
      * Remove access for any services that no longer exist.
      */
@@ -332,18 +332,15 @@
         rebuildRestoredPackages();
     }
 
-    private void updateSettingsAccordingToInstalledServices(int userId) {
-        boolean restoredChanged = false;
-        boolean currentChanged = false;
-        Set<ComponentName> restored =
-                loadComponentNamesFromSetting(restoredSettingName(mConfig), userId);
-        Set<ComponentName> current =
-                loadComponentNamesFromSetting(mConfig.secureSettingName, userId);
+    protected Set<ComponentName> queryPackageForServices(String packageName, int userId) {
         Set<ComponentName> installed = new ArraySet<>();
-
         final PackageManager pm = mContext.getPackageManager();
+        Intent queryIntent = new Intent(mConfig.serviceInterface);
+        if (!TextUtils.isEmpty(packageName)) {
+            queryIntent.setPackage(packageName);
+        }
         List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
-                new Intent(mConfig.serviceInterface),
+                queryIntent,
                 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
                 userId);
         if (DEBUG)
@@ -363,6 +360,18 @@
             }
             installed.add(component);
         }
+        return installed;
+    }
+
+    private void updateSettingsAccordingToInstalledServices(int userId) {
+        boolean restoredChanged = false;
+        boolean currentChanged = false;
+        Set<ComponentName> restored =
+                loadComponentNamesFromSetting(restoredSettingName(mConfig), userId);
+        Set<ComponentName> current =
+                loadComponentNamesFromSetting(mConfig.secureSettingName, userId);
+        // Load all services for all packages.
+        Set<ComponentName> installed = queryPackageForServices(null, userId);
 
         ArraySet<ComponentName> retained = new ArraySet<>();
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 361bbf9..b84811f 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1579,12 +1579,6 @@
         }
 
         @Override
-        public boolean setZenModeConfig(ZenModeConfig config, String reason) {
-            checkCallerIsSystem();
-            return mZenModeHelper.setConfig(config, reason);
-        }
-
-        @Override
         public void setZenMode(int mode, Uri conditionId, String reason) throws RemoteException {
             enforceSystemOrSystemUIOrVolume("INotificationManager.setZenMode");
             final long identity = Binder.clearCallingIdentity();
@@ -1669,12 +1663,6 @@
             });
         }
 
-        @Override
-        public void requestZenModeConditions(IConditionListener callback, int relevance) {
-            enforceSystemOrSystemUIOrVolume("INotificationManager.requestZenModeConditions");
-            mZenModeHelper.requestZenModeConditions(callback, relevance);
-        }
-
         private void enforceSystemOrSystemUIOrVolume(String message) {
             if (mAudioManagerInternal != null) {
                 final int vcuid = mAudioManagerInternal.getVolumeControllerUid();
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 76c6443..a1f8c41 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -121,8 +121,11 @@
 
     public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
             ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
-        return ZenModeFiltering.matchesCallFilter(mContext, mZenMode, mConfig, userHandle, extras,
-                validator, contactsTimeoutMs, timeoutAffinity);
+        synchronized (mConfig) {
+            return ZenModeFiltering.matchesCallFilter(mContext, mZenMode, mConfig, userHandle,
+                    extras,
+                    validator, contactsTimeoutMs, timeoutAffinity);
+        }
     }
 
     public boolean isCall(NotificationRecord record) {
@@ -130,7 +133,9 @@
     }
 
     public boolean shouldIntercept(NotificationRecord record) {
-        return mFiltering.shouldIntercept(mZenMode, mConfig, record);
+        synchronized (mConfig) {
+            return mFiltering.shouldIntercept(mZenMode, mConfig, record);
+        }
     }
 
     public void addCallback(Callback callback) {
@@ -175,10 +180,6 @@
         mConfigs.remove(user);
     }
 
-    public void requestZenModeConditions(IConditionListener callback, int relevance) {
-        mConditions.requestConditions(callback, relevance);
-    }
-
     public int getZenModeListenerInterruptionFilter() {
         return NotificationManager.zenModeToInterruptionFilter(mZenMode);
     }
@@ -203,18 +204,23 @@
 
     public List<AutomaticZenRule> getAutomaticZenRules() {
         List<AutomaticZenRule> rules = new ArrayList<>();
-        if (mConfig == null) return rules;
-        for(ZenRule rule : mConfig.automaticRules.values()) {
-            if (canManageAutomaticZenRule(rule)) {
-                rules.add(createAutomaticZenRule(rule));
+        synchronized (mConfig) {
+            if (mConfig == null) return rules;
+            for (ZenRule rule : mConfig.automaticRules.values()) {
+                if (canManageAutomaticZenRule(rule)) {
+                    rules.add(createAutomaticZenRule(rule));
+                }
             }
         }
         return rules;
     }
 
     public AutomaticZenRule getAutomaticZenRule(String id) {
-        if (mConfig == null) return null;
-        ZenRule rule = mConfig.automaticRules.get(id);
+        ZenRule rule;
+        synchronized (mConfig) {
+            if (mConfig == null) return null;
+             rule = mConfig.automaticRules.get(id);
+        }
         if (rule == null) return null;
         if (canManageAutomaticZenRule(rule)) {
              return createAutomaticZenRule(rule);
@@ -223,14 +229,18 @@
     }
 
     public AutomaticZenRule addAutomaticZenRule(AutomaticZenRule automaticZenRule, String reason) {
-        if (mConfig == null) return null;
-        if (DEBUG) {
-          Log.d(TAG, "addAutomaticZenRule zenRule= " + automaticZenRule + " reason=" +reason);
+        ZenModeConfig newConfig;
+        synchronized (mConfig) {
+            if (mConfig == null) return null;
+            if (DEBUG) {
+                Log.d(TAG,
+                        "addAutomaticZenRule zenRule= " + automaticZenRule + " reason=" + reason);
+            }
+            if (!TextUtils.isEmpty(automaticZenRule.getId())) {
+                throw new IllegalArgumentException("Rule already exists");
+            }
+            newConfig = mConfig.copy();
         }
-        if (!TextUtils.isEmpty(automaticZenRule.getId())) {
-            throw new IllegalArgumentException("Rule already exists");
-        }
-        final ZenModeConfig newConfig = mConfig.copy();
         ZenRule rule = new ZenRule();
         populateZenRule(automaticZenRule, rule, true);
         newConfig.automaticRules.put(rule.id, rule);
@@ -242,12 +252,15 @@
     }
 
     public boolean updateAutomaticZenRule(AutomaticZenRule automaticZenRule, String reason) {
-        if (mConfig == null) return false;
-        if (DEBUG) {
-            Log.d(TAG, "updateAutomaticZenRule zenRule=" + automaticZenRule
-                    + " reason=" + reason);
+        ZenModeConfig newConfig;
+        synchronized (mConfig) {
+            if (mConfig == null) return false;
+            if (DEBUG) {
+                Log.d(TAG, "updateAutomaticZenRule zenRule=" + automaticZenRule
+                        + " reason=" + reason);
+            }
+            newConfig = mConfig.copy();
         }
-        final ZenModeConfig newConfig = mConfig.copy();
         final String ruleId = automaticZenRule.getId();
         ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
         if (ruleId == null) {
@@ -265,8 +278,11 @@
     }
 
     public boolean removeAutomaticZenRule(String id, String reason) {
-        if (mConfig == null) return false;
-        final ZenModeConfig newConfig = mConfig.copy();
+        ZenModeConfig newConfig;
+        synchronized (mConfig) {
+            if (mConfig == null) return false;
+            newConfig = mConfig.copy();
+        }
         ZenRule rule = newConfig.automaticRules.get(id);
         if (rule == null) return false;
         if (canManageAutomaticZenRule(rule)) {
@@ -328,12 +344,15 @@
 
     private void setManualZenMode(int zenMode, Uri conditionId, String reason,
             boolean setRingerMode) {
-        if (mConfig == null) return;
-        if (!Global.isValidZenMode(zenMode)) return;
-        if (DEBUG) Log.d(TAG, "setManualZenMode " + Global.zenModeToString(zenMode)
-                + " conditionId=" + conditionId + " reason=" + reason
-                + " setRingerMode=" + setRingerMode);
-        final ZenModeConfig newConfig = mConfig.copy();
+        ZenModeConfig newConfig;
+        synchronized (mConfig) {
+            if (mConfig == null) return;
+            if (!Global.isValidZenMode(zenMode)) return;
+            if (DEBUG) Log.d(TAG, "setManualZenMode " + Global.zenModeToString(zenMode)
+                    + " conditionId=" + conditionId + " reason=" + reason
+                    + " setRingerMode=" + setRingerMode);
+            newConfig = mConfig.copy();
+        }
         if (zenMode == Global.ZEN_MODE_OFF) {
             newConfig.manualRule = null;
             for (ZenRule automaticRule : newConfig.automaticRules.values()) {
@@ -360,7 +379,9 @@
             dump(pw, prefix, "mConfigs[u=" + mConfigs.keyAt(i) + "]", mConfigs.valueAt(i));
         }
         pw.print(prefix); pw.print("mUser="); pw.println(mUser);
-        dump(pw, prefix, "mConfig", mConfig);
+        synchronized (mConfig) {
+            dump(pw, prefix, "mConfig", mConfig);
+        }
         pw.print(prefix); pw.print("mEffectsSuppressed="); pw.println(mEffectsSuppressed);
         mFiltering.dump(pw, prefix);
         mConditions.dump(pw, prefix);
@@ -437,7 +458,9 @@
     }
 
     public ZenModeConfig getConfig() {
-        return mConfig;
+        synchronized (mConfig) {
+            return mConfig.copy();
+        }
     }
 
     public boolean setConfig(ZenModeConfig config, String reason) {
@@ -462,19 +485,21 @@
                 return true;
             }
             mConditions.evaluateConfig(config, false /*processSubscriptions*/);  // may modify config
-            mConfigs.put(config.user, config);
-            if (DEBUG) Log.d(TAG, "setConfig reason=" + reason, new Throwable());
-            ZenLog.traceConfig(reason, mConfig, config);
-            final boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig),
-                    getNotificationPolicy(config));
-            mConfig = config;
-            if (config.equals(mConfig)) {
-                dispatchOnConfigChanged();
+            synchronized (mConfig) {
+                mConfigs.put(config.user, config);
+                if (DEBUG) Log.d(TAG, "setConfig reason=" + reason, new Throwable());
+                ZenLog.traceConfig(reason, mConfig, config);
+                final boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig),
+                        getNotificationPolicy(config));
+                mConfig = config;
+                if (config.equals(mConfig)) {
+                    dispatchOnConfigChanged();
+                }
+                if (policyChanged) {
+                    dispatchOnPolicyChanged();
+                }
             }
-            if (policyChanged){
-                dispatchOnPolicyChanged();
-            }
-            final String val = Integer.toString(mConfig.hashCode());
+            final String val = Integer.toString(config.hashCode());
             Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
             if (!evaluateZenMode(reason, setRingerMode)) {
                 applyRestrictions();  // evaluateZenMode will also apply restrictions if changed
@@ -529,17 +554,19 @@
     }
 
     private int computeZenMode() {
-        if (mConfig == null) return Global.ZEN_MODE_OFF;
-        if (mConfig.manualRule != null) return mConfig.manualRule.zenMode;
-        int zen = Global.ZEN_MODE_OFF;
-        for (ZenRule automaticRule : mConfig.automaticRules.values()) {
-            if (automaticRule.isAutomaticActive()) {
-                if (zenSeverity(automaticRule.zenMode) > zenSeverity(zen)) {
-                    zen = automaticRule.zenMode;
+        synchronized (mConfig) {
+            if (mConfig == null) return Global.ZEN_MODE_OFF;
+            if (mConfig.manualRule != null) return mConfig.manualRule.zenMode;
+            int zen = Global.ZEN_MODE_OFF;
+            for (ZenRule automaticRule : mConfig.automaticRules.values()) {
+                if (automaticRule.isAutomaticActive()) {
+                    if (zenSeverity(automaticRule.zenMode) > zenSeverity(zen)) {
+                        zen = automaticRule.zenMode;
+                    }
                 }
             }
+            return zen;
         }
-        return zen;
     }
 
     private void applyRestrictions() {
diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp
index 89b2a47..01acdef 100644
--- a/services/core/jni/com_android_server_tv_TvInputHal.cpp
+++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp
@@ -414,12 +414,9 @@
         return NO_ERROR;
     }
     if (Surface::isValid(connection.mSurface)) {
-        connection.mSurface.clear();
-    }
-    if (connection.mSurface != NULL) {
         connection.mSurface->setSidebandStream(NULL);
-        connection.mSurface.clear();
     }
+    connection.mSurface.clear();
     if (connection.mThread != NULL) {
         connection.mThread->shutdown();
         connection.mThread.clear();
@@ -616,6 +613,9 @@
         return BAD_VALUE;
     }
     sp<Surface> surface(android_view_Surface_getSurface(env, jsurface));
+    if (!Surface::isValid(surface)) {
+        return BAD_VALUE;
+    }
     return tvInputHal->addOrUpdateStream(deviceId, streamId, surface);
 }
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1ec1a46..e32af5c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -438,7 +438,6 @@
         InputManagerService inputManager = null;
         TelephonyRegistry telephonyRegistry = null;
         ConsumerIrService consumerIr = null;
-        AudioService audioService = null;
         MmsServiceBroker mmsService = null;
         EntropyMixer entropyMixer = null;
 
@@ -857,12 +856,7 @@
             }
 
             traceBeginAndSlog("StartAudioService");
-            try {
-                audioService = new AudioService(context);
-                ServiceManager.addService(Context.AUDIO_SERVICE, audioService);
-            } catch (Throwable e) {
-                reportWtf("starting Audio Service", e);
-            }
+            mSystemServiceManager.startService(AudioService.Lifecycle.class);
             Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
 
             if (!disableNonCoreServices) {
@@ -1163,7 +1157,6 @@
         final InputManagerService inputManagerF = inputManager;
         final TelephonyRegistry telephonyRegistryF = telephonyRegistry;
         final MediaRouterService mediaRouterF = mediaRouter;
-        final AudioService audioServiceF = audioService;
         final MmsServiceBroker mmsServiceF = mmsService;
 
         // We now tell the activity manager it is okay to run third party
@@ -1234,13 +1227,7 @@
                     reportWtf("making Connectivity Service ready", e);
                 }
                 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
-                Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "MakeAudioServiceReady");
-                try {
-                    if (audioServiceF != null) audioServiceF.systemReady();
-                } catch (Throwable e) {
-                    reportWtf("Notifying AudioService running", e);
-                }
-                Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+
                 Watchdog.getInstance().start();
 
                 // It is now okay to let the various system services start their
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 90b4f43..c12f978 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -876,11 +876,12 @@
     }
 
     private void expectSystemReady() throws Exception {
-        mAlarmManager.remove(isA(PendingIntent.class));
+        mAlarmManager.remove(isA(PendingIntent.class), null);
         expectLastCall().anyTimes();
 
-        mAlarmManager.set(eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), anyLong(),
-                anyInt(), isA(PendingIntent.class), isA(WorkSource.class),
+        mAlarmManager.set(getContext().getPackageName(),
+                eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), anyLong(),
+                anyInt(), isA(PendingIntent.class), null, null, isA(WorkSource.class),
                 isA(AlarmManager.AlarmClockInfo.class));
         expectLastCall().atLeastOnce();
 
diff --git a/telephony/java/com/android/ims/ImsCallProfile.java b/telephony/java/com/android/ims/ImsCallProfile.java
index f263b4d..861a379 100644
--- a/telephony/java/com/android/ims/ImsCallProfile.java
+++ b/telephony/java/com/android/ims/ImsCallProfile.java
@@ -178,6 +178,7 @@
      *  Codec: Codec info.
      *  DisplayText: Display text for the call.
      *  AdditionalCallInfo: Additional call info.
+     *  CallRadioTech: The radio tech on which the call is placed.
      */
     public static final String EXTRA_OI = "oi";
     public static final String EXTRA_CNA = "cna";
@@ -187,6 +188,7 @@
     public static final String EXTRA_CODEC = "Codec";
     public static final String EXTRA_DISPLAY_TEXT = "DisplayText";
     public static final String EXTRA_ADDITIONAL_CALL_INFO = "AdditionalCallInfo";
+    public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech";
 
     public int mServiceType;
     public int mCallType;
diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/JavaClassGenerator_test.cpp
index becf99b..cc5e981 100644
--- a/tools/aapt2/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/JavaClassGenerator_test.cpp
@@ -105,11 +105,9 @@
             .addSimple(u"@android:id/one", ResourceId(0x01020000))
             .addSimple(u"@android:id/two", ResourceId(0x01020001))
             .addSimple(u"@android:id/three", ResourceId(0x01020002))
+            .setSymbolState(u"@android:id/one", ResourceId(0x01020000), SymbolState::kPublic)
+            .setSymbolState(u"@android:id/two", ResourceId(0x01020001), SymbolState::kPrivate)
             .build();
-    ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:id/one"), {}, {},
-                                      SymbolState::kPublic, &diag));
-    ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:id/two"), {}, {},
-                                      SymbolState::kPrivate, &diag));
 
     JavaClassGeneratorOptions options;
     options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index bfef9d0..44710eb 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -186,6 +186,7 @@
     Source source;
     ResourceId id;
     SymbolState symbolState = SymbolState::kUndefined;
+    std::u16string comment;
     std::unique_ptr<Value> value;
     std::list<ParsedResource> childResources;
 };
@@ -194,7 +195,11 @@
 static bool addResourcesToTable(ResourceTable* table, const ConfigDescription& config,
                                 IDiagnostics* diag, ParsedResource* res) {
     if (res->symbolState != SymbolState::kUndefined) {
-        if (!table->setSymbolState(res->name, res->id, res->source, res->symbolState, diag)) {
+        Symbol symbol;
+        symbol.state = res->symbolState;
+        symbol.source = res->source;
+        symbol.comment = res->comment;
+        if (!table->setSymbolState(res->name, res->id, symbol, diag)) {
             return false;
         }
     }
@@ -203,7 +208,11 @@
         return true;
     }
 
-    if (!table->addResource(res->name, res->id, config, res->source, std::move(res->value), diag)) {
+    // Attach the comment, source and config to the value.
+    res->value->setComment(std::move(res->comment));
+    res->value->setSource(std::move(res->source));
+
+    if (!table->addResource(res->name, res->id, config, std::move(res->value), diag)) {
         return false;
     }
 
@@ -275,6 +284,7 @@
         ParsedResource parsedResource;
         parsedResource.name.entry = maybeName.value().toString();
         parsedResource.source = mSource.withLine(parser->getLineNumber());
+        parsedResource.comment = std::move(comment);
 
         bool result = true;
         if (elementName == u"id") {
@@ -368,8 +378,8 @@
  * an Item. If allowRawValue is false, nullptr is returned in this
  * case.
  */
-std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, uint32_t typeMask,
-                                               bool allowRawValue) {
+std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, const uint32_t typeMask,
+                                               const bool allowRawValue) {
     const size_t beginXmlLine = parser->getLineNumber();
 
     std::u16string rawValue;
@@ -386,8 +396,9 @@
 
     auto onCreateReference = [&](const ResourceName& name) {
         // name.package can be empty here, as it will assume the package name of the table.
-        mTable->addResource(name, {}, mSource.withLine(beginXmlLine), util::make_unique<Id>(),
-                            mDiag);
+        std::unique_ptr<Id> id = util::make_unique<Id>();
+        id->setSource(mSource.withLine(beginXmlLine));
+        mTable->addResource(name, {}, std::move(id), mDiag);
     };
 
     // Process the raw value.
@@ -411,11 +422,12 @@
                 mTable->stringPool.makeRef(styleString.str, StringPool::Context{ 1, mConfig }));
     }
 
-    // We can't parse this so return a RawString if we are allowed.
     if (allowRawValue) {
+        // We can't parse this so return a RawString if we are allowed.
         return util::make_unique<RawString>(
                 mTable->stringPool.makeRef(rawValue, StringPool::Context{ 1, mConfig }));
     }
+
     return {};
 }
 
@@ -683,8 +695,8 @@
     }
 
     return Attribute::Symbol{
-        Reference(ResourceName{ {}, ResourceType::kId, maybeName.value().toString() }),
-                val.data };
+            Reference(ResourceName({}, ResourceType::kId, maybeName.value().toString())),
+            val.data };
 }
 
 static Maybe<ResourceName> parseXmlAttributeName(StringPiece16 str) {
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 34c68d7..2d5a29d 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -65,12 +65,13 @@
                            StyleString* outStyleString);
 
     /*
-     * Parses the XML subtree and converts it to an Item. The type of Item that can be
-     * parsed is denoted by the `typeMask`. If `allowRawValue` is true and the subtree
-     * can not be parsed as a regular Item, then a RawString is returned. Otherwise
-     * this returns nullptr.
+     * Parses the XML subtree and returns an Item.
+     * The type of Item that can be parsed is denoted by the `typeMask`.
+     * If `allowRawValue` is true and the subtree can not be parsed as a regular Item, then a
+     * RawString is returned. Otherwise this returns false;
      */
-    std::unique_ptr<Item> parseXml(XmlPullParser* parser, uint32_t typeMask, bool allowRawValue);
+    std::unique_ptr<Item> parseXml(XmlPullParser* parser, const uint32_t typeMask,
+                                   const bool allowRawValue);
 
     bool parseResources(XmlPullParser* parser);
     bool parseString(XmlPullParser* parser, ParsedResource* outResource);
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index a7e9d39..af6bf67 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -379,18 +379,39 @@
 }
 
 TEST_F(ResourceParserTest, ParseCommentsWithResource) {
-    std::string input = "<!-- This is a comment -->\n"
+    std::string input = "<!--This is a comment-->\n"
                         "<string name=\"foo\">Hi</string>";
     ASSERT_TRUE(testParse(input));
 
-    Maybe<ResourceTable::SearchResult> result = mTable.findResource(
-            test::parseNameOrDie(u"@string/foo"));
-    AAPT_ASSERT_TRUE(result);
+    String* value = test::getValue<String>(&mTable, u"@string/foo");
+    ASSERT_NE(nullptr, value);
+    EXPECT_EQ(value->getComment(), u"This is a comment");
+}
 
-    ResourceEntry* entry = result.value().entry;
-    ASSERT_NE(entry, nullptr);
-    ASSERT_FALSE(entry->values.empty());
-    EXPECT_EQ(entry->values.front().comment, u"This is a comment");
+TEST_F(ResourceParserTest, DoNotCombineMultipleComments) {
+    std::string input = "<!--One-->\n"
+                        "<!--Two-->\n"
+                        "<string name=\"foo\">Hi</string>";
+
+    ASSERT_TRUE(testParse(input));
+
+    String* value = test::getValue<String>(&mTable, u"@string/foo");
+    ASSERT_NE(nullptr, value);
+    EXPECT_EQ(value->getComment(), u"Two");
+}
+
+TEST_F(ResourceParserTest, IgnoreCommentBeforeEndTag) {
+    std::string input = "<!--One-->\n"
+                        "<string name=\"foo\">\n"
+                        "  Hi\n"
+                        "<!--Two-->\n"
+                        "</string>";
+
+    ASSERT_TRUE(testParse(input));
+
+    String* value = test::getValue<String>(&mTable, u"@string/foo");
+    ASSERT_NE(nullptr, value);
+    EXPECT_EQ(value->getComment(), u"One");
 }
 
 /*
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 84674e8..fa4b109 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -19,6 +19,8 @@
 #include "ResourceTable.h"
 #include "ResourceValues.h"
 #include "ValueVisitor.h"
+
+#include "util/Comparators.h"
 #include "util/Util.h"
 
 #include <algorithm>
@@ -29,10 +31,6 @@
 
 namespace aapt {
 
-static bool compareConfigs(const ResourceConfigValue& lhs, const ConfigDescription& rhs) {
-    return lhs.config < rhs;
-}
-
 static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs, ResourceType rhs) {
     return lhs->type < rhs;
 }
@@ -191,52 +189,50 @@
 static constexpr const char16_t* kValidNameMangledChars = u"._-$";
 
 bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config,
-                                const Source& source, std::unique_ptr<Value> value,
-                                IDiagnostics* diag) {
-    return addResourceImpl(name, ResourceId{}, config, source, std::move(value), kValidNameChars,
-                           diag);
+                                std::unique_ptr<Value> value, IDiagnostics* diag) {
+    return addResourceImpl(name, {}, config, std::move(value), kValidNameChars, diag);
 }
 
 bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId,
-                                const ConfigDescription& config, const Source& source,
-                                std::unique_ptr<Value> value, IDiagnostics* diag) {
-    return addResourceImpl(name, resId, config, source, std::move(value), kValidNameChars, diag);
+                                const ConfigDescription& config, std::unique_ptr<Value> value,
+                                IDiagnostics* diag) {
+    return addResourceImpl(name, resId, config, std::move(value), kValidNameChars, diag);
 }
 
 bool ResourceTable::addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
                                      const Source& source, const StringPiece16& path,
                                      IDiagnostics* diag) {
-    return addResourceImpl(name, ResourceId{}, config, source,
-                           util::make_unique<FileReference>(stringPool.makeRef(path)),
-                           kValidNameChars, diag);
+    std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
+            stringPool.makeRef(path));
+    fileRef->setSource(source);
+    return addResourceImpl(name, ResourceId{}, config, std::move(fileRef), kValidNameChars, diag);
 }
 
 bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
                                             const ConfigDescription& config,
-                                            const Source& source,
                                             std::unique_ptr<Value> value,
                                             IDiagnostics* diag) {
-    return addResourceImpl(name, ResourceId{}, config, source, std::move(value),
-                           kValidNameMangledChars, diag);
+    return addResourceImpl(name, ResourceId{}, config, std::move(value), kValidNameMangledChars,
+                           diag);
 }
 
 bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
                                             const ResourceId id,
                                             const ConfigDescription& config,
-                                            const Source& source,
                                             std::unique_ptr<Value> value,
                                             IDiagnostics* diag) {
-    return addResourceImpl(name, id, config, source, std::move(value),
-                           kValidNameMangledChars, diag);
+    return addResourceImpl(name, id, config, std::move(value), kValidNameMangledChars, diag);
 }
 
 bool ResourceTable::addResourceImpl(const ResourceNameRef& name, const ResourceId resId,
-                                    const ConfigDescription& config, const Source& source,
-                                    std::unique_ptr<Value> value, const char16_t* validChars,
-                                    IDiagnostics* diag) {
+                                    const ConfigDescription& config, std::unique_ptr<Value> value,
+                                    const char16_t* validChars, IDiagnostics* diag) {
+    assert(value && "value can't be nullptr");
+    assert(diag && "diagnostics can't be nullptr");
+
     auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
     if (badCharIter != name.entry.end()) {
-        diag->error(DiagMessage(source)
+        diag->error(DiagMessage(value->getSource())
                     << "resource '"
                     << name
                     << "' has invalid entry name '"
@@ -249,7 +245,7 @@
 
     ResourceTablePackage* package = findOrCreatePackage(name.package);
     if (resId.isValid() && package->id && package->id.value() != resId.packageId()) {
-        diag->error(DiagMessage(source)
+        diag->error(DiagMessage(value->getSource())
                     << "trying to add resource '"
                     << name
                     << "' with ID "
@@ -263,7 +259,7 @@
 
     ResourceTableType* type = package->findOrCreateType(name.type);
     if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
-        diag->error(DiagMessage(source)
+        diag->error(DiagMessage(value->getSource())
                     << "trying to add resource '"
                     << name
                     << "' with ID "
@@ -277,7 +273,7 @@
 
     ResourceEntry* entry = type->findOrCreateEntry(name.entry);
     if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
-        diag->error(DiagMessage(source)
+        diag->error(DiagMessage(value->getSource())
                     << "trying to add resource '"
                     << name
                     << "' with ID "
@@ -288,20 +284,20 @@
     }
 
     const auto endIter = entry->values.end();
-    auto iter = std::lower_bound(entry->values.begin(), endIter, config, compareConfigs);
+    auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmp::lessThan);
     if (iter == endIter || iter->config != config) {
         // This resource did not exist before, add it.
-        entry->values.insert(iter, ResourceConfigValue{ config, source, {}, std::move(value) });
+        entry->values.insert(iter, ResourceConfigValue{ config, std::move(value) });
     } else {
         int collisionResult = resolveValueCollision(iter->value.get(), value.get());
         if (collisionResult > 0) {
             // Take the incoming value.
-            *iter = ResourceConfigValue{ config, source, {}, std::move(value) };
+            iter->value = std::move(value);
         } else if (collisionResult == 0) {
-            diag->error(DiagMessage(source)
+            diag->error(DiagMessage(value->getSource())
                         << "duplicate value for resource '" << name << "' "
-                        << "with config '" << iter->config << "'");
-            diag->error(DiagMessage(iter->source)
+                        << "with config '" << config << "'");
+            diag->error(DiagMessage(iter->value->getSource())
                         << "resource previously defined here");
             return false;
         }
@@ -316,27 +312,29 @@
 }
 
 bool ResourceTable::setSymbolState(const ResourceNameRef& name, const ResourceId resId,
-                                   const Source& source, SymbolState state, IDiagnostics* diag) {
-    return setSymbolStateImpl(name, resId, source, state, kValidNameChars, diag);
+                                   const Symbol& symbol, IDiagnostics* diag) {
+    return setSymbolStateImpl(name, resId, symbol, kValidNameChars, diag);
 }
 
-bool ResourceTable::setSymbolStateAllowMangled(const ResourceNameRef& name, const ResourceId resId,
-                                               const Source& source, SymbolState state,
-                                               IDiagnostics* diag) {
-    return setSymbolStateImpl(name, resId, source, state, kValidNameMangledChars, diag);
+bool ResourceTable::setSymbolStateAllowMangled(const ResourceNameRef& name,
+                                               const ResourceId resId,
+                                               const Symbol& symbol, IDiagnostics* diag) {
+    return setSymbolStateImpl(name, resId, symbol, kValidNameMangledChars, diag);
 }
 
 bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name, const ResourceId resId,
-                                       const Source& source, SymbolState state,
-                                       const char16_t* validChars, IDiagnostics* diag) {
-    if (state == SymbolState::kUndefined) {
+                                       const Symbol& symbol, const char16_t* validChars,
+                                       IDiagnostics* diag) {
+    assert(diag && "diagnostics can't be nullptr");
+
+    if (symbol.state == SymbolState::kUndefined) {
         // Nothing to do.
         return true;
     }
 
     auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
     if (badCharIter != name.entry.end()) {
-        diag->error(DiagMessage(source)
+        diag->error(DiagMessage(symbol.source)
                     << "resource '"
                     << name
                     << "' has invalid entry name '"
@@ -349,7 +347,7 @@
 
     ResourceTablePackage* package = findOrCreatePackage(name.package);
     if (resId.isValid() && package->id && package->id.value() != resId.packageId()) {
-        diag->error(DiagMessage(source)
+        diag->error(DiagMessage(symbol.source)
                     << "trying to add resource '"
                     << name
                     << "' with ID "
@@ -363,7 +361,7 @@
 
     ResourceTableType* type = package->findOrCreateType(name.type);
     if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
-        diag->error(DiagMessage(source)
+        diag->error(DiagMessage(symbol.source)
                     << "trying to add resource '"
                     << name
                     << "' with ID "
@@ -377,7 +375,7 @@
 
     ResourceEntry* entry = type->findOrCreateEntry(name.entry);
     if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
-        diag->error(DiagMessage(source)
+        diag->error(DiagMessage(symbol.source)
                     << "trying to add resource '"
                     << name
                     << "' with ID "
@@ -388,15 +386,14 @@
     }
 
     // Only mark the type state as public, it doesn't care about being private.
-    if (state == SymbolState::kPublic) {
+    if (symbol.state == SymbolState::kPublic) {
         type->symbolStatus.state = SymbolState::kPublic;
     }
 
     // Downgrading to a private symbol from a public one is not allowed.
     if (entry->symbolStatus.state != SymbolState::kPublic) {
-        if (entry->symbolStatus.state != state) {
-            entry->symbolStatus.state = state;
-            entry->symbolStatus.source = source;
+        if (entry->symbolStatus.state != symbol.state) {
+            entry->symbolStatus = std::move(symbol);
         }
     }
 
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index be90936..980504b 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -47,12 +47,10 @@
 };
 
 /**
- * The resource value for a specific configuration.
+ * Represents a value defined for a given configuration.
  */
 struct ResourceConfigValue {
     ConfigDescription config;
-    Source source;
-    std::u16string comment;
     std::unique_ptr<Value> value;
 };
 
@@ -158,12 +156,11 @@
     static int resolveValueCollision(Value* existing, Value* incoming);
 
     bool addResource(const ResourceNameRef& name, const ConfigDescription& config,
-                     const Source& source, std::unique_ptr<Value> value,
-                     IDiagnostics* diag);
+                     std::unique_ptr<Value> value, IDiagnostics* diag);
 
     bool addResource(const ResourceNameRef& name, const ResourceId resId,
-                     const ConfigDescription& config, const Source& source,
-                     std::unique_ptr<Value> value, IDiagnostics* diag);
+                     const ConfigDescription& config, std::unique_ptr<Value> value,
+                     IDiagnostics* diag);
 
     bool addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
                           const Source& source, const StringPiece16& path, IDiagnostics* diag);
@@ -174,18 +171,18 @@
      * names.
      */
     bool addResourceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config,
-                                 const Source& source, std::unique_ptr<Value> value,
-                                 IDiagnostics* diag);
+                                 std::unique_ptr<Value> value, IDiagnostics* diag);
 
     bool addResourceAllowMangled(const ResourceNameRef& name, const ResourceId id,
-                                 const ConfigDescription& config,
-                                 const Source& source, std::unique_ptr<Value> value,
+                                 const ConfigDescription& config, std::unique_ptr<Value> value,
                                  IDiagnostics* diag);
 
-    bool setSymbolState(const ResourceNameRef& name, const ResourceId resId, const Source& source,
-                        SymbolState state, IDiagnostics* diag);
+    bool setSymbolState(const ResourceNameRef& name, const ResourceId resId,
+                        const Symbol& symbol, IDiagnostics* diag);
+
     bool setSymbolStateAllowMangled(const ResourceNameRef& name, const ResourceId resId,
-                                    const Source& source, SymbolState state, IDiagnostics* diag);
+                                    const Symbol& symbol, IDiagnostics* diag);
+
     struct SearchResult {
         ResourceTablePackage* package;
         ResourceTableType* type;
@@ -224,13 +221,11 @@
 private:
     ResourceTablePackage* findOrCreatePackage(const StringPiece16& name);
 
-    bool addResourceImpl(const ResourceNameRef& name, const ResourceId resId,
-                         const ConfigDescription& config, const Source& source,
-                         std::unique_ptr<Value> value, const char16_t* validChars,
-                         IDiagnostics* diag);
-    bool setSymbolStateImpl(const ResourceNameRef& name, const ResourceId resId,
-                            const Source& source, SymbolState state, const char16_t* validChars,
-                            IDiagnostics* diag);
+    bool addResourceImpl(const ResourceNameRef& name, ResourceId resId,
+                         const ConfigDescription& config, std::unique_ptr<Value> value,
+                         const char16_t* validChars, IDiagnostics* diag);
+    bool setSymbolStateImpl(const ResourceNameRef& name, ResourceId resId,
+                            const Symbol& symbol, const char16_t* validChars, IDiagnostics* diag);
 };
 
 } // namespace aapt
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index 2055a80..42508fe 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -19,7 +19,7 @@
 #include "ResourceValues.h"
 #include "util/Util.h"
 
-#include "test/Common.h"
+#include "test/Builders.h"
 
 #include <algorithm>
 #include <gtest/gtest.h>
@@ -42,22 +42,26 @@
     ResourceTable table;
 
     EXPECT_FALSE(table.addResource(
-            ResourceNameRef{ u"android", ResourceType::kId, u"hey,there" },
-            {}, Source{ "test.xml", 21 },
-            util::make_unique<Id>(), &mDiagnostics));
+            ResourceNameRef(u"android", ResourceType::kId, u"hey,there"),
+            ConfigDescription{},
+            test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
+            &mDiagnostics));
 
     EXPECT_FALSE(table.addResource(
-            ResourceNameRef{ u"android", ResourceType::kId, u"hey:there" },
-            {}, Source{ "test.xml", 21 },
-            util::make_unique<Id>(), &mDiagnostics));
+            ResourceNameRef(u"android", ResourceType::kId, u"hey:there"),
+            ConfigDescription{},
+            test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
+            &mDiagnostics));
 }
 
 TEST_F(ResourceTableTest, AddOneResource) {
     ResourceTable table;
 
-    EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/id"), {},
-                                  Source{ "test/path/file.xml", 23 },
-                                  util::make_unique<Id>(), &mDiagnostics));
+    EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/id"),
+                                  ConfigDescription{},
+                                  test::ValueBuilder<Id>()
+                                          .setSource("test/path/file.xml", 23u).build(),
+                                  &mDiagnostics));
 
     ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id"));
 }
@@ -71,23 +75,29 @@
 
     EXPECT_TRUE(table.addResource(
             test::parseNameOrDie(u"@android:attr/layout_width"),
-            config, Source{ "test/path/file.xml", 10 },
-            util::make_unique<Id>(), &mDiagnostics));
+            config,
+            test::ValueBuilder<Id>().setSource("test/path/file.xml", 10u).build(),
+            &mDiagnostics));
 
     EXPECT_TRUE(table.addResource(
             test::parseNameOrDie(u"@android:attr/id"),
-            config, Source{ "test/path/file.xml", 12 },
-            util::make_unique<Id>(), &mDiagnostics));
+            config,
+            test::ValueBuilder<Id>().setSource("test/path/file.xml", 12u).build(),
+            &mDiagnostics));
 
     EXPECT_TRUE(table.addResource(
             test::parseNameOrDie(u"@android:string/ok"),
-            config, Source{ "test/path/file.xml", 14 },
-            util::make_unique<Id>(), &mDiagnostics));
+            config,
+            test::ValueBuilder<Id>().setSource("test/path/file.xml", 14u).build(),
+            &mDiagnostics));
 
     EXPECT_TRUE(table.addResource(
             test::parseNameOrDie(u"@android:string/ok"),
-            languageConfig, Source{ "test/path/file.xml", 20 },
-            util::make_unique<BinaryPrimitive>(android::Res_value{}), &mDiagnostics));
+            languageConfig,
+            test::ValueBuilder<BinaryPrimitive>(android::Res_value{})
+                    .setSource("test/path/file.xml", 20u)
+                    .build(),
+            &mDiagnostics));
 
     ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/layout_width"));
     ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id"));
@@ -99,14 +109,14 @@
 TEST_F(ResourceTableTest, OverrideWeakResourceValue) {
     ResourceTable table;
 
-    ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), {}, {},
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{},
                                   util::make_unique<Attribute>(true), &mDiagnostics));
 
     Attribute* attr = test::getValue<Attribute>(&table, u"@android:attr/foo");
     ASSERT_NE(nullptr, attr);
     EXPECT_TRUE(attr->isWeak());
 
-    ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), {}, {},
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{},
                                   util::make_unique<Attribute>(false), &mDiagnostics));
 
     attr = test::getValue<Attribute>(&table, u"@android:attr/foo");
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index ecc5cd2..f312d75 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -15,11 +15,12 @@
  */
 
 #include "Resource.h"
-#include "flatten/ResourceTypeExtensions.h"
 #include "ResourceValues.h"
-#include "util/Util.h"
 #include "ValueVisitor.h"
 
+#include "util/Util.h"
+#include "flatten/ResourceTypeExtensions.h"
+
 #include <androidfw/ResourceTypes.h>
 #include <limits>
 
@@ -35,18 +36,10 @@
     visitor->visit(static_cast<Derived*>(this));
 }
 
-bool Value::isItem() const {
-    return false;
-}
-
 bool Value::isWeak() const {
     return false;
 }
 
-bool Item::isItem() const {
-    return true;
-}
-
 RawString::RawString(const StringPool::Ref& ref) : value(ref) {
 }
 
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 0dae091..2629153 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -41,17 +41,42 @@
 	virtual ~Value() = default;
 
     /**
-     * Whether or not this is an Item.
-     */
-    virtual bool isItem() const;
-
-    /**
      * Whether this value is weak and can be overridden without
      * warning or error. Default for base class is false.
      */
     virtual bool isWeak() const;
 
     /**
+     * Returns the source where this value was defined.
+     */
+    const Source& getSource() const {
+        return mSource;
+    }
+
+    void setSource(const Source& source) {
+        mSource = source;
+    }
+
+    void setSource(Source&& source) {
+        mSource = std::move(source);
+    }
+
+    /**
+     * Returns the comment that was associated with this resource.
+     */
+    StringPiece16 getComment() const {
+        return mComment;
+    }
+
+    void setComment(const StringPiece16& str) {
+        mComment = str.toString();
+    }
+
+    void setComment(std::u16string&& str) {
+        mComment = std::move(str);
+    }
+
+    /**
      * Calls the appropriate overload of ValueVisitor.
      */
     virtual void accept(RawValueVisitor* visitor) = 0;
@@ -65,6 +90,10 @@
      * Human readable printout of this value.
      */
     virtual void print(std::ostream* out) const = 0;
+
+private:
+    Source mSource;
+    std::u16string mComment;
 };
 
 /**
@@ -80,11 +109,6 @@
  */
 struct Item : public Value {
     /**
-     * An Item is, of course, an Item.
-     */
-    virtual bool isItem() const override;
-
-    /**
      * Clone the Item.
      */
     virtual Item* clone(StringPool* newPool) const override = 0;
diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h
index ee058aa..94042e3 100644
--- a/tools/aapt2/ValueVisitor.h
+++ b/tools/aapt2/ValueVisitor.h
@@ -115,6 +115,18 @@
 };
 
 /**
+ * Specialization that checks if the value is an Item.
+ */
+template <>
+struct DynCastVisitor<Item> : public RawValueVisitor {
+    Item* value = nullptr;
+
+    void visitItem(Item* item) override {
+        value = item;
+    }
+};
+
+/**
  * Returns a valid pointer to T if the Value is of subtype T.
  * Otherwise, returns nullptr.
  */
diff --git a/tools/aapt2/XmlPullParser.cpp b/tools/aapt2/XmlPullParser.cpp
index 1b9499d..cff935c 100644
--- a/tools/aapt2/XmlPullParser.cpp
+++ b/tools/aapt2/XmlPullParser.cpp
@@ -97,7 +97,7 @@
 }
 
 const std::u16string& XmlPullParser::getComment() const {
-    return mEventQueue.front().comment;
+    return mEventQueue.front().data1;
 }
 
 size_t XmlPullParser::getLineNumber() const {
diff --git a/tools/aapt2/XmlPullParser.h b/tools/aapt2/XmlPullParser.h
index f7d7a03..a0ce21d 100644
--- a/tools/aapt2/XmlPullParser.h
+++ b/tools/aapt2/XmlPullParser.h
@@ -158,7 +158,6 @@
         size_t depth;
         std::u16string data1;
         std::u16string data2;
-        std::u16string comment;
         std::vector<Attribute> attributes;
     };
 
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index 095552a..47fa2a6 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -292,7 +292,7 @@
     SymbolWriter* mSymbols;
     StringPool* mSourcePool;
 
-    template <typename T>
+    template <typename T, bool IsItem>
     T* writeEntry(FlatEntry* entry, BigBuffer* buffer) {
         static_assert(std::is_same<ResTable_entry, T>::value ||
                       std::is_same<ResTable_entry_ext, T>::value,
@@ -308,7 +308,7 @@
             outEntry->flags |= ResTable_entry::FLAG_WEAK;
         }
 
-        if (!entry->value->isItem()) {
+        if (!IsItem) {
             outEntry->flags |= ResTable_entry::FLAG_COMPLEX;
         }
 
@@ -329,8 +329,8 @@
     }
 
     bool flattenValue(FlatEntry* entry, BigBuffer* buffer) {
-        if (entry->value->isItem()) {
-            writeEntry<ResTable_entry>(entry, buffer);
+        if (Item* item = valueCast<Item>(entry->value)) {
+            writeEntry<ResTable_entry, true>(entry, buffer);
             if (Reference* ref = valueCast<Reference>(entry->value)) {
                 if (!ref->id) {
                     assert(ref->name && "reference must have at least a name");
@@ -339,12 +339,12 @@
                 }
             }
             Res_value* outValue = buffer->nextBlock<Res_value>();
-            bool result = static_cast<Item*>(entry->value)->flatten(outValue);
+            bool result = item->flatten(outValue);
             assert(result && "flatten failed");
             outValue->size = util::hostToDevice16(sizeof(*outValue));
         } else {
             const size_t beforeEntry = buffer->size();
-            ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext>(entry, buffer);
+            ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext, false>(entry, buffer);
             MapFlattenVisitor visitor(mSymbols, entry, buffer);
             entry->value->accept(&visitor);
             outEntry->count = util::hostToDevice32(visitor.mEntryCount);
@@ -551,17 +551,27 @@
             // configuration available. Here we reverse this to match the binary table.
             std::map<ConfigDescription, std::vector<FlatEntry>> configToEntryListMap;
             for (ResourceEntry* entry : sortedEntries) {
-                const size_t keyIndex = mKeyPool.makeRef(entry->name).getIndex();
+                const uint32_t keyIndex = (uint32_t) mKeyPool.makeRef(entry->name).getIndex();
 
                 // Group values by configuration.
                 for (auto& configValue : entry->values) {
-                   configToEntryListMap[configValue.config].push_back(FlatEntry{
-                            entry, configValue.value.get(), (uint32_t) keyIndex,
-                            (uint32_t)(mSourcePool->makeRef(util::utf8ToUtf16(
-                                    configValue.source.path)).getIndex()),
-                            (uint32_t)(configValue.source.line
-                                    ? configValue.source.line.value() : 0)
-                   });
+                    Value* value = configValue.value.get();
+
+                    const StringPool::Ref sourceRef = mSourcePool->makeRef(
+                            util::utf8ToUtf16(value->getSource().path));
+
+                    uint32_t lineNumber = 0;
+                    if (value->getSource().line) {
+                        lineNumber = value->getSource().line.value();
+                    }
+
+                    configToEntryListMap[configValue.config]
+                            .push_back(FlatEntry{
+                                    entry,
+                                    value,
+                                    keyIndex,
+                                    (uint32_t) sourceRef.getIndex(),
+                                    lineNumber });
                 }
             }
 
diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp
index 0ccafc2..11fcc5d 100644
--- a/tools/aapt2/link/AutoVersioner.cpp
+++ b/tools/aapt2/link/AutoVersioner.cpp
@@ -20,21 +20,18 @@
 #include "ValueVisitor.h"
 
 #include "link/Linkers.h"
+#include "util/Comparators.h"
 
 #include <algorithm>
 #include <cassert>
 
 namespace aapt {
 
-static bool cmpConfigValue(const ResourceConfigValue& lhs, const ConfigDescription& config) {
-    return lhs.config < config;
-}
-
 bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config,
                                      const int sdkVersionToGenerate) {
     assert(sdkVersionToGenerate > config.sdkVersion);
     const auto endIter = entry->values.end();
-    auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmpConfigValue);
+    auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmp::lessThan);
 
     // The source config came from this list, so it should be here.
     assert(iter != entry->values.end());
@@ -107,21 +104,16 @@
                             // We found attributes from a higher SDK level. Check that
                             // there is no other defined resource for the version we want to
                             // generate.
-                            if (shouldGenerateVersionedResource(entry.get(), configValue.config,
+                            if (shouldGenerateVersionedResource(entry.get(),
+                                                                configValue.config,
                                                                 minSdkStripped.value())) {
                                 // Let's create a new Style for this versioned resource.
                                 ConfigDescription newConfig(configValue.config);
                                 newConfig.sdkVersion = minSdkStripped.value();
 
-                                ResourceConfigValue newValue = {
-                                        newConfig,
-                                        configValue.source,
-                                        configValue.comment,
-                                        std::unique_ptr<Value>(configValue.value->clone(
-                                                &table->stringPool))
-                                };
-
-                                Style* newStyle = static_cast<Style*>(newValue.value.get());
+                                std::unique_ptr<Style> newStyle(style->clone(&table->stringPool));
+                                newStyle->setComment(style->getComment());
+                                newStyle->setSource(style->getSource());
 
                                 // Move the previously stripped attributes into this style.
                                 newStyle->entries.insert(newStyle->entries.end(),
@@ -130,9 +122,13 @@
 
                                 // Insert the new Resource into the correct place.
                                 auto iter = std::lower_bound(entry->values.begin(),
-                                                             entry->values.end(), newConfig,
-                                                             cmpConfigValue);
-                                entry->values.insert(iter, std::move(newValue));
+                                                             entry->values.end(),
+                                                             newConfig,
+                                                             cmp::lessThan);
+
+                                entry->values.insert(
+                                        iter,
+                                        ResourceConfigValue{ newConfig, std::move(newStyle) });
                             }
                         }
                     }
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index b84f2e0..ad701de 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -266,13 +266,14 @@
                 for (const auto& type : package->types) {
                     for (const auto& entry : type->entries) {
                         for (const auto& configValue : entry->values) {
-                            mContext.getDiagnostics()->error(DiagMessage(configValue.source)
-                                                             << "defined resource '"
-                                                             << ResourceNameRef(package->name,
-                                                                                type->type,
-                                                                                entry->name)
-                                                             << "' for external package '"
-                                                             << package->name << "'");
+                            mContext.getDiagnostics()->error(
+                                    DiagMessage(configValue.value->getSource())
+                                                << "defined resource '"
+                                                << ResourceNameRef(package->name,
+                                                                   type->type,
+                                                                   entry->name)
+                                                << "' for external package '"
+                                                << package->name << "'");
                             error = true;
                         }
                     }
@@ -472,10 +473,11 @@
 
                         Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(
                                 exportedSymbol.name);
-                        if (!mergedTable.addResource(
+                        std::unique_ptr<Id> id = util::make_unique<Id>();
+                        id->setSource(f.source.withLine(exportedSymbol.line));
+                        if (!mergedTable.addResourceAllowMangled(
                                 mangledName ? mangledName.value() : exportedSymbol.name,
-                                {}, {}, f.source.withLine(exportedSymbol.line),
-                                util::make_unique<Id>(), mContext.getDiagnostics())) {
+                                {}, std::move(id), mContext.getDiagnostics())) {
                             error = true;
                         }
                     }
diff --git a/tools/aapt2/link/PrivateAttributeMover_test.cpp b/tools/aapt2/link/PrivateAttributeMover_test.cpp
index a2f8d19..dbe0c92 100644
--- a/tools/aapt2/link/PrivateAttributeMover_test.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp
@@ -30,13 +30,9 @@
             .addSimple(u"@android:attr/privateA")
             .addSimple(u"@android:attr/publicB")
             .addSimple(u"@android:attr/privateB")
+            .setSymbolState(u"@android:attr/publicA", ResourceId(0x01010000), SymbolState::kPublic)
+            .setSymbolState(u"@android:attr/publicB", ResourceId(0x01010000), SymbolState::kPublic)
             .build();
-    ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:attr/publicA"),
-                                      ResourceId(0x01010000), {}, SymbolState::kPublic,
-                                      context->getDiagnostics()));
-    ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:attr/publicB"),
-                                      ResourceId(0x01010002), {}, SymbolState::kPublic,
-                                      context->getDiagnostics()));
 
     PrivateAttributeMover mover;
     ASSERT_TRUE(mover.consume(context.get(), table.get()));
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index b4fb996..8c924b5 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -206,13 +206,13 @@
 
                 if (!(typeMask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) {
                     // The actual type of this item is incompatible with the attribute.
-                    DiagMessage msg;
+                    DiagMessage msg(style->getSource());
                     buildAttributeMismatchMessage(&msg, s->attribute.get(), entry.value.get());
                     mContext->getDiagnostics()->error(msg);
                     mError = true;
                 }
             } else {
-                DiagMessage msg;
+                DiagMessage msg(style->getSource());
                 msg << "style attribute '";
                 if (entry.key.name) {
                     msg << entry.key.name.value().package << ":" << entry.key.name.value().entry;
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index db52546..636c2ba 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -19,6 +19,7 @@
 #include "ValueVisitor.h"
 
 #include "link/TableMerger.h"
+#include "util/Comparators.h"
 #include "util/Util.h"
 
 #include <cassert>
@@ -120,27 +121,24 @@
             }
 
             for (ResourceConfigValue& srcValue : srcEntry->values) {
-                auto cmp = [](const ResourceConfigValue& a,
-                              const ConfigDescription& b) -> bool {
-                    return a.config < b;
-                };
-
                 auto iter = std::lower_bound(dstEntry->values.begin(), dstEntry->values.end(),
-                                             srcValue.config, cmp);
+                                             srcValue.config, cmp::lessThan);
 
                 if (iter != dstEntry->values.end() && iter->config == srcValue.config) {
                     const int collisionResult = ResourceTable::resolveValueCollision(
                             iter->value.get(), srcValue.value.get());
                     if (collisionResult == 0) {
                         // Error!
-                        ResourceNameRef resourceName =
-                                { srcPackage->name, srcType->type, srcEntry->name };
-                        mContext->getDiagnostics()->error(DiagMessage(srcValue.source)
+                        ResourceNameRef resourceName(srcPackage->name,
+                                                     srcType->type,
+                                                     srcEntry->name);
+
+                        mContext->getDiagnostics()->error(DiagMessage(srcValue.value->getSource())
                                                           << "resource '" << resourceName
                                                           << "' has a conflicting value for "
                                                           << "configuration ("
                                                           << srcValue.config << ")");
-                        mContext->getDiagnostics()->note(DiagMessage(iter->source)
+                        mContext->getDiagnostics()->note(DiagMessage(iter->value->getSource())
                                                          << "originally defined here");
                         error = true;
                         continue;
@@ -150,16 +148,12 @@
                     }
 
                 } else {
-                    // Insert a new value.
-                    iter = dstEntry->values.insert(iter,
-                                                   ResourceConfigValue{ srcValue.config });
+                    // Insert a place holder value. We will fill it in below.
+                    iter = dstEntry->values.insert(iter, ResourceConfigValue{ srcValue.config });
                 }
 
-                iter->source = std::move(srcValue.source);
-                iter->comment = std::move(srcValue.comment);
                 if (manglePackage) {
-                    iter->value = cloneAndMangle(srcTable, srcPackage->name,
-                                                 srcValue.value.get());
+                    iter->value = cloneAndMangle(srcTable, srcPackage->name, srcValue.value.get());
                 } else {
                     iter->value = clone(srcValue.value.get());
                 }
@@ -179,7 +173,11 @@
             std::u16string mangledEntry = NameMangler::mangleEntry(package, entry.toString());
             std::u16string newPath = prefix.toString() + mangledEntry + suffix.toString();
             mFilesToMerge.push(FileToMerge{ table, *f->path, newPath });
-            return util::make_unique<FileReference>(mMasterTable->stringPool.makeRef(newPath));
+            std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
+                    mMasterTable->stringPool.makeRef(newPath));
+            fileRef->setComment(f->getComment());
+            fileRef->setSource(f->getSource());
+            return std::move(fileRef);
         }
     }
     return clone(value);
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index c96b080..7309396 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -16,9 +16,11 @@
 
 #include "ConfigDescription.h"
 #include "Resource.h"
-#include "util/Util.h"
+#include "ValueVisitor.h"
 
 #include "process/SymbolTable.h"
+#include "util/Comparators.h"
+#include "util/Util.h"
 
 #include <androidfw/AssetManager.h>
 #include <androidfw/ResourceTypes.h>
@@ -34,7 +36,7 @@
     if (!result) {
         if (name.type == ResourceType::kAttr) {
             // Recurse and try looking up a private attribute.
-            return findByName(ResourceName{ name.package, ResourceType::kAttrPrivate, name.entry });
+            return findByName(ResourceName(name.package, ResourceType::kAttrPrivate, name.entry));
         }
         return {};
     }
@@ -48,28 +50,26 @@
     }
 
     std::shared_ptr<Symbol> symbol = std::make_shared<Symbol>();
-    symbol->id = ResourceId{
-            sr.package->id.value(), sr.type->id.value(), sr.entry->id.value() };
+    symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(), sr.entry->id.value());
 
     if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) {
-        auto lt = [](ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
-            return lhs.config < rhs;
-        };
-
         const ConfigDescription kDefaultConfig;
         auto iter = std::lower_bound(sr.entry->values.begin(), sr.entry->values.end(),
-                                     kDefaultConfig, lt);
+                                     kDefaultConfig, cmp::lessThan);
 
         if (iter != sr.entry->values.end() && iter->config == kDefaultConfig) {
             // This resource has an Attribute.
-            symbol->attribute = util::make_unique<Attribute>(
-                    *static_cast<Attribute*>(iter->value.get()));
+            if (Attribute* attr = valueCast<Attribute>(iter->value.get())) {
+                symbol->attribute = std::unique_ptr<Attribute>(attr->clone(nullptr));
+            } else {
+                return {};
+            }
         }
     }
 
     if (name.type == ResourceType::kAttrPrivate) {
         // Masquerade this entry as kAttr.
-        mCache.put(ResourceName{ name.package, ResourceType::kAttr, name.entry }, symbol);
+        mCache.put(ResourceName(name.package, ResourceType::kAttr, name.entry), symbol);
     } else {
         mCache.put(name, symbol);
     }
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 0383c44..1b510e7 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -43,7 +43,7 @@
         return *this;
     }
 
-    ResourceTableBuilder& addSimple(const StringPiece16& name, ResourceId id = {}) {
+    ResourceTableBuilder& addSimple(const StringPiece16& name, const ResourceId id = {}) {
         return addValue(name, id, util::make_unique<Id>());
     }
 
@@ -51,7 +51,7 @@
         return addReference(name, {}, ref);
     }
 
-    ResourceTableBuilder& addReference(const StringPiece16& name, ResourceId id,
+    ResourceTableBuilder& addReference(const StringPiece16& name, const ResourceId id,
                                        const StringPiece16& ref) {
         return addValue(name, id, util::make_unique<Reference>(parseNameOrDie(ref)));
     }
@@ -60,7 +60,7 @@
         return addString(name, {}, str);
     }
 
-    ResourceTableBuilder& addString(const StringPiece16& name, ResourceId id,
+    ResourceTableBuilder& addString(const StringPiece16& name, const ResourceId id,
                                     const StringPiece16& str) {
         return addValue(name, id, util::make_unique<String>(mTable->stringPool.makeRef(str)));
     }
@@ -69,31 +69,43 @@
         return addFileReference(name, {}, path);
     }
 
-    ResourceTableBuilder& addFileReference(const StringPiece16& name, ResourceId id,
+    ResourceTableBuilder& addFileReference(const StringPiece16& name, const ResourceId id,
                                            const StringPiece16& path) {
         return addValue(name, id,
                         util::make_unique<FileReference>(mTable->stringPool.makeRef(path)));
     }
 
 
-    ResourceTableBuilder& addValue(const StringPiece16& name, std::unique_ptr<Value> value) {
+    ResourceTableBuilder& addValue(const StringPiece16& name,
+                                   std::unique_ptr<Value> value) {
         return addValue(name, {}, std::move(value));
     }
 
-    ResourceTableBuilder& addValue(const StringPiece16& name, ResourceId id,
+    ResourceTableBuilder& addValue(const StringPiece16& name, const ResourceId id,
                                        std::unique_ptr<Value> value) {
         return addValue(name, id, {}, std::move(value));
     }
 
-    ResourceTableBuilder& addValue(const StringPiece16& name, ResourceId id,
-                                   const ConfigDescription& config, std::unique_ptr<Value> value) {
+    ResourceTableBuilder& addValue(const StringPiece16& name, const ResourceId id,
+                                   const ConfigDescription& config,
+                                   std::unique_ptr<Value> value) {
         ResourceName resName = parseNameOrDie(name);
-        bool result = mTable->addResourceAllowMangled(resName, id, config, {}, std::move(value),
+        bool result = mTable->addResourceAllowMangled(resName, id, config, std::move(value),
                                                       &mDiagnostics);
         assert(result);
         return *this;
     }
 
+    ResourceTableBuilder& setSymbolState(const StringPiece16& name, ResourceId id,
+                                         SymbolState state) {
+        ResourceName resName = parseNameOrDie(name);
+        Symbol symbol;
+        symbol.state = state;
+        bool result = mTable->setSymbolStateAllowMangled(resName, id, symbol, &mDiagnostics);
+        assert(result);
+        return *this;
+    }
+
     std::unique_ptr<ResourceTable> build() {
         return std::move(mTable);
     }
@@ -106,6 +118,32 @@
     return reference;
 }
 
+template <typename T>
+class ValueBuilder {
+private:
+    std::unique_ptr<Value> mValue;
+
+public:
+    template <typename... Args>
+    ValueBuilder(Args&&... args) : mValue(new T{ std::forward<Args>(args)... }) {
+    }
+
+    template <typename... Args>
+    ValueBuilder& setSource(Args&&... args) {
+        mValue->setSource(Source{ std::forward<Args>(args)... });
+        return *this;
+    }
+
+    ValueBuilder& setComment(const StringPiece16& str) {
+        mValue->setComment(str);
+        return *this;
+    }
+
+    std::unique_ptr<Value> build() {
+        return std::move(mValue);
+    }
+};
+
 class AttributeBuilder {
 private:
     std::unique_ptr<Attribute> mAttr;
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index c7a715e..314c1e8 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -422,26 +422,24 @@
         const ResourceName name(package->name, *parsedType,
                                 util::getString(mKeyPool, entry->key.index).toString());
 
-        Source source;
+        Symbol symbol;
         if (mSourcePool.getError() == NO_ERROR) {
-            source.path = util::utf16ToUtf8(util::getString(
+            symbol.source.path = util::utf16ToUtf8(util::getString(
                     mSourcePool, util::deviceToHost32(entry->source.index)));
-            source.line = util::deviceToHost32(entry->sourceLine);
+            symbol.source.line = util::deviceToHost32(entry->sourceLine);
         }
 
-        SymbolState state = SymbolState::kUndefined;
         switch (util::deviceToHost16(entry->state)) {
         case Public_entry::kPrivate:
-            state = SymbolState::kPrivate;
+            symbol.state = SymbolState::kPrivate;
             break;
 
         case Public_entry::kPublic:
-            state = SymbolState::kPublic;
+            symbol.state = SymbolState::kPublic;
             break;
         }
 
-        if (!mTable->setSymbolStateAllowMangled(name, resId, source, state,
-                                                mContext->getDiagnostics())) {
+        if (!mTable->setSymbolStateAllowMangled(name, resId, symbol, mContext->getDiagnostics())) {
             return false;
         }
 
@@ -570,14 +568,17 @@
             source.line = util::deviceToHost32(sourceBlock->line);
         }
 
-        if (!mTable->addResourceAllowMangled(name, config, source, std::move(resourceValue),
+        resourceValue->setSource(source);
+        if (!mTable->addResourceAllowMangled(name, config, std::move(resourceValue),
                                              mContext->getDiagnostics())) {
             return false;
         }
 
         if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
-            if (!mTable->setSymbolStateAllowMangled(name, resId, mSource.withLine(0),
-                                                    SymbolState::kPublic,
+            Symbol symbol;
+            symbol.state = SymbolState::kPublic;
+            symbol.source = mSource.withLine(0);
+            if (!mTable->setSymbolStateAllowMangled(name, resId, symbol,
                                                     mContext->getDiagnostics())) {
                 return false;
             }
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h
index 4dbef5d..02c4081 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.h
+++ b/tools/aapt2/unflatten/BinaryResourceParser.h
@@ -66,26 +66,30 @@
     bool parseTypeSpec(const android::ResChunk_header* chunk);
     bool parseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
 
-    std::unique_ptr<Item> parseValue(const ResourceNameRef& name,
-            const ConfigDescription& config, const android::Res_value* value, uint16_t flags);
+    std::unique_ptr<Item> parseValue(const ResourceNameRef& name, const ConfigDescription& config,
+                                     const android::Res_value* value, uint16_t flags);
 
     std::unique_ptr<Value> parseMapEntry(const ResourceNameRef& name,
-            const ConfigDescription& config, const android::ResTable_map_entry* map);
+                                         const ConfigDescription& config,
+                                         const android::ResTable_map_entry* map);
 
-    std::unique_ptr<Style> parseStyle(const ResourceNameRef& name,
-            const ConfigDescription& config, const android::ResTable_map_entry* map);
+    std::unique_ptr<Style> parseStyle(const ResourceNameRef& name, const ConfigDescription& config,
+                                      const android::ResTable_map_entry* map);
 
     std::unique_ptr<Attribute> parseAttr(const ResourceNameRef& name,
-            const ConfigDescription& config, const android::ResTable_map_entry* map);
+                                         const ConfigDescription& config,
+                                         const android::ResTable_map_entry* map);
 
-    std::unique_ptr<Array> parseArray(const ResourceNameRef& name,
-            const ConfigDescription& config, const android::ResTable_map_entry* map);
+    std::unique_ptr<Array> parseArray(const ResourceNameRef& name, const ConfigDescription& config,
+                                      const android::ResTable_map_entry* map);
 
     std::unique_ptr<Plural> parsePlural(const ResourceNameRef& name,
-            const ConfigDescription& config, const android::ResTable_map_entry* map);
+                                        const ConfigDescription& config,
+                                        const android::ResTable_map_entry* map);
 
     std::unique_ptr<Styleable> parseStyleable(const ResourceNameRef& name,
-            const ConfigDescription& config, const android::ResTable_map_entry* map);
+                                              const ConfigDescription& config,
+                                              const android::ResTable_map_entry* map);
 
     IAaptContext* mContext;
     ResourceTable* mTable;
diff --git a/tools/aapt2/util/Comparators.h b/tools/aapt2/util/Comparators.h
new file mode 100644
index 0000000..652018e
--- /dev/null
+++ b/tools/aapt2/util/Comparators.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_UTIL_COMPARATORS_H
+#define AAPT_UTIL_COMPARATORS_H
+
+namespace aapt {
+namespace cmp {
+
+inline bool lessThan(const ResourceConfigValue& a, const ConfigDescription& b) {
+    return a.config < b;
+}
+
+} // namespace cmp
+} // namespace aapt
+
+#endif /* AAPT_UTIL_COMPARATORS_H */