Merge "Fix bug with cancelled drag" into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index 15fc149..9834c8c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4359,6 +4359,8 @@
     method public android.app.DownloadManager.Request setDestinationUri(android.net.Uri);
     method public android.app.DownloadManager.Request setMimeType(java.lang.String);
     method public android.app.DownloadManager.Request setNotificationVisibility(int);
+    method public android.app.DownloadManager.Request setRequiresCharging(boolean);
+    method public android.app.DownloadManager.Request setRequiresDeviceIdle(boolean);
     method public deprecated android.app.DownloadManager.Request setShowRunningNotification(boolean);
     method public android.app.DownloadManager.Request setTitle(java.lang.CharSequence);
     method public android.app.DownloadManager.Request setVisibleInDownloadsUi(boolean);
@@ -6335,6 +6337,7 @@
     field public static final long MAX_BACKOFF_DELAY_MILLIS = 18000000L; // 0x112a880L
     field public static final int NETWORK_TYPE_ANY = 1; // 0x1
     field public static final int NETWORK_TYPE_NONE = 0; // 0x0
+    field public static final int NETWORK_TYPE_NOT_ROAMING = 3; // 0x3
     field public static final int NETWORK_TYPE_UNMETERED = 2; // 0x2
   }
 
@@ -6382,6 +6385,7 @@
     method public abstract void cancel(int);
     method public abstract void cancelAll();
     method public abstract java.util.List<android.app.job.JobInfo> getAllPendingJobs();
+    method public abstract android.app.job.JobInfo getPendingJob(int);
     method public abstract int schedule(android.app.job.JobInfo);
     field public static final int RESULT_FAILURE = 0; // 0x0
     field public static final int RESULT_SUCCESS = 1; // 0x1
@@ -23757,8 +23761,10 @@
     method public boolean isConnected();
     method public boolean isConnectedOrConnecting();
     method public boolean isFailover();
+    method public boolean isMetered();
     method public boolean isRoaming();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.NetworkInfo> CREATOR;
   }
 
   public static final class NetworkInfo.DetailedState extends java.lang.Enum {
diff --git a/api/system-current.txt b/api/system-current.txt
index c489435..2755bc8 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4502,6 +4502,8 @@
     method public android.app.DownloadManager.Request setDestinationUri(android.net.Uri);
     method public android.app.DownloadManager.Request setMimeType(java.lang.String);
     method public android.app.DownloadManager.Request setNotificationVisibility(int);
+    method public android.app.DownloadManager.Request setRequiresCharging(boolean);
+    method public android.app.DownloadManager.Request setRequiresDeviceIdle(boolean);
     method public deprecated android.app.DownloadManager.Request setShowRunningNotification(boolean);
     method public android.app.DownloadManager.Request setTitle(java.lang.CharSequence);
     method public android.app.DownloadManager.Request setVisibleInDownloadsUi(boolean);
@@ -5085,6 +5087,7 @@
     field public static final java.lang.String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
     field public static final java.lang.String EXTRA_SHOW_WHEN = "android.showWhen";
     field public static final java.lang.String EXTRA_SMALL_ICON = "android.icon";
+    field public static final java.lang.String EXTRA_SUBSTITUTE_APP_NAME = "android.substName";
     field public static final java.lang.String EXTRA_SUB_TEXT = "android.subText";
     field public static final java.lang.String EXTRA_SUMMARY_TEXT = "android.summaryText";
     field public static final java.lang.String EXTRA_TEMPLATE = "android.template";
@@ -6613,6 +6616,7 @@
     field public static final long MAX_BACKOFF_DELAY_MILLIS = 18000000L; // 0x112a880L
     field public static final int NETWORK_TYPE_ANY = 1; // 0x1
     field public static final int NETWORK_TYPE_NONE = 0; // 0x0
+    field public static final int NETWORK_TYPE_NOT_ROAMING = 3; // 0x3
     field public static final int NETWORK_TYPE_UNMETERED = 2; // 0x2
   }
 
@@ -6660,6 +6664,7 @@
     method public abstract void cancel(int);
     method public abstract void cancelAll();
     method public abstract java.util.List<android.app.job.JobInfo> getAllPendingJobs();
+    method public abstract android.app.job.JobInfo getPendingJob(int);
     method public abstract int schedule(android.app.job.JobInfo);
     field public static final int RESULT_FAILURE = 0; // 0x0
     field public static final int RESULT_SUCCESS = 1; // 0x1
@@ -25583,8 +25588,10 @@
     method public boolean isConnected();
     method public boolean isConnectedOrConnecting();
     method public boolean isFailover();
+    method public boolean isMetered();
     method public boolean isRoaming();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.NetworkInfo> CREATOR;
   }
 
   public static final class NetworkInfo.DetailedState extends java.lang.Enum {
diff --git a/api/test-current.txt b/api/test-current.txt
index f6340b2..5dc6d51 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4359,6 +4359,8 @@
     method public android.app.DownloadManager.Request setDestinationUri(android.net.Uri);
     method public android.app.DownloadManager.Request setMimeType(java.lang.String);
     method public android.app.DownloadManager.Request setNotificationVisibility(int);
+    method public android.app.DownloadManager.Request setRequiresCharging(boolean);
+    method public android.app.DownloadManager.Request setRequiresDeviceIdle(boolean);
     method public deprecated android.app.DownloadManager.Request setShowRunningNotification(boolean);
     method public android.app.DownloadManager.Request setTitle(java.lang.CharSequence);
     method public android.app.DownloadManager.Request setVisibleInDownloadsUi(boolean);
@@ -6339,6 +6341,7 @@
     field public static final long MAX_BACKOFF_DELAY_MILLIS = 18000000L; // 0x112a880L
     field public static final int NETWORK_TYPE_ANY = 1; // 0x1
     field public static final int NETWORK_TYPE_NONE = 0; // 0x0
+    field public static final int NETWORK_TYPE_NOT_ROAMING = 3; // 0x3
     field public static final int NETWORK_TYPE_UNMETERED = 2; // 0x2
   }
 
@@ -6386,6 +6389,7 @@
     method public abstract void cancel(int);
     method public abstract void cancelAll();
     method public abstract java.util.List<android.app.job.JobInfo> getAllPendingJobs();
+    method public abstract android.app.job.JobInfo getPendingJob(int);
     method public abstract int schedule(android.app.job.JobInfo);
     field public static final int RESULT_FAILURE = 0; // 0x0
     field public static final int RESULT_SUCCESS = 1; // 0x1
@@ -23825,8 +23829,10 @@
     method public boolean isConnected();
     method public boolean isConnectedOrConnecting();
     method public boolean isFailover();
+    method public boolean isMetered();
     method public boolean isRoaming();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.NetworkInfo> CREATOR;
   }
 
   public static final class NetworkInfo.DetailedState extends java.lang.Enum {
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index e681d47..29bc4d8 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -370,6 +370,7 @@
          * {@link ConnectivityManager#TYPE_BLUETOOTH}.
          * @hide
          */
+        @Deprecated
         public static final int NETWORK_BLUETOOTH = 1 << 2;
 
         private Uri mUri;
@@ -381,6 +382,7 @@
         private int mAllowedNetworkTypes = ~0; // default to all network types allowed
         private boolean mRoamingAllowed = true;
         private boolean mMeteredAllowed = true;
+        private int mFlags = 0;
         private boolean mIsVisibleInDownloadsUi = true;
         private boolean mScannable = false;
         private boolean mUseSystemCache = false;
@@ -669,6 +671,10 @@
          * By default, all network types are allowed. Consider using
          * {@link #setAllowedOverMetered(boolean)} instead, since it's more
          * flexible.
+         * <p>
+         * As of {@link android.os.Build.VERSION_CODES#N}, setting only the
+         * {@link #NETWORK_WIFI} flag here is equivalent to calling
+         * {@link #setAllowedOverMetered(boolean)} with {@code false}.
          *
          * @param flags any combination of the NETWORK_* bit flags.
          * @return this object
@@ -701,6 +707,42 @@
         }
 
         /**
+         * Specify that to run this download, the device needs to be plugged in.
+         * This defaults to false.
+         *
+         * @param requiresCharging Whether or not the device is plugged in.
+         * @see android.app.job.JobInfo.Builder#setRequiresCharging(boolean)
+         */
+        public Request setRequiresCharging(boolean requiresCharging) {
+            if (requiresCharging) {
+                mFlags |= Downloads.Impl.FLAG_REQUIRES_CHARGING;
+            } else {
+                mFlags &= ~Downloads.Impl.FLAG_REQUIRES_CHARGING;
+            }
+            return this;
+        }
+
+        /**
+         * Specify that to run, the download needs the device to be in idle
+         * mode. This defaults to false.
+         * <p>
+         * Idle mode is a loose definition provided by the system, which means
+         * that the device is not in use, and has not been in use for some time.
+         *
+         * @param requiresDeviceIdle Whether or not the device need be within an
+         *            idle maintenance window.
+         * @see android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)
+         */
+        public Request setRequiresDeviceIdle(boolean requiresDeviceIdle) {
+            if (requiresDeviceIdle) {
+                mFlags |= Downloads.Impl.FLAG_REQUIRES_DEVICE_IDLE;
+            } else {
+                mFlags &= ~Downloads.Impl.FLAG_REQUIRES_DEVICE_IDLE;
+            }
+            return this;
+        }
+
+        /**
          * Set whether this download should be displayed in the system's Downloads UI. True by
          * default.
          * @param isVisible whether to display this download in the Downloads UI
@@ -746,6 +788,7 @@
             values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes);
             values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed);
             values.put(Downloads.Impl.COLUMN_ALLOW_METERED, mMeteredAllowed);
+            values.put(Downloads.Impl.COLUMN_FLAGS, mFlags);
             values.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, mIsVisibleInDownloadsUi);
 
             return values;
@@ -983,16 +1026,7 @@
             // called with nothing to remove!
             throw new IllegalArgumentException("input param 'ids' can't be null");
         }
-        ContentValues values = new ContentValues();
-        values.put(Downloads.Impl.COLUMN_DELETED, 1);
-        // if only one id is passed in, then include it in the uri itself.
-        // this will eliminate a full database scan in the download service.
-        if (ids.length == 1) {
-            return mResolver.update(ContentUris.withAppendedId(mBaseUri, ids[0]), values,
-                    null, null);
-        } 
-        return mResolver.update(mBaseUri, values, getWhereClauseForIds(ids),
-                getWhereArgsForIds(ids));
+        return mResolver.delete(mBaseUri, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
     }
 
     /**
@@ -1121,6 +1155,20 @@
     }
 
     /**
+     * Force the given downloads to proceed even if their size is larger than
+     * {@link #getMaxBytesOverMobile(Context)}.
+     *
+     * @hide
+     */
+    public void forceDownload(long... ids) {
+        ContentValues values = new ContentValues();
+        values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
+        values.put(Downloads.Impl.COLUMN_CONTROL, Downloads.Impl.CONTROL_RUN);
+        values.put(Downloads.Impl.COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT, 1);
+        mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
+    }
+
+    /**
      * Returns maximum size, in bytes, of downloads that may go over a mobile connection; or null if
      * there's no limit
      *
diff --git a/core/java/android/app/JobSchedulerImpl.java b/core/java/android/app/JobSchedulerImpl.java
index b3a486f..e30b96f 100644
--- a/core/java/android/app/JobSchedulerImpl.java
+++ b/core/java/android/app/JobSchedulerImpl.java
@@ -78,4 +78,13 @@
             return null;
         }
     }
+
+    @Override
+    public JobInfo getPendingJob(int jobId) {
+        try {
+            return mBinder.getPendingJob(jobId);
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
 }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 5becd07..d7705b9 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -976,10 +976,8 @@
      */
     public static final String EXTRA_CONTAINS_CUSTOM_VIEW = "android.contains.customView";
 
-    /**
-     * @SystemApi
-     * @hide
-     */
+    /** @hide */
+    @SystemApi
     public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName";
 
     private Icon mSmallIcon;
diff --git a/core/java/android/app/job/IJobScheduler.aidl b/core/java/android/app/job/IJobScheduler.aidl
index 3379f2e..b6eec27 100644
--- a/core/java/android/app/job/IJobScheduler.aidl
+++ b/core/java/android/app/job/IJobScheduler.aidl
@@ -28,4 +28,5 @@
     void cancel(int jobId);
     void cancelAll();
     List<JobInfo> getAllPendingJobs();
+    JobInfo getPendingJob(int jobId);
 }
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index 602d950..ecfc527 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -44,6 +44,8 @@
     public static final int NETWORK_TYPE_ANY = 1;
     /** This job requires network connectivity that is unmetered. */
     public static final int NETWORK_TYPE_UNMETERED = 2;
+    /** This job requires network connectivity that is not roaming. */
+    public static final int NETWORK_TYPE_NOT_ROAMING = 3;
 
     /**
      * Amount of backoff a job has initially by default, in milliseconds.
@@ -240,8 +242,9 @@
 
     /**
      * One of {@link android.app.job.JobInfo#NETWORK_TYPE_ANY},
-     * {@link android.app.job.JobInfo#NETWORK_TYPE_NONE}, or
-     * {@link android.app.job.JobInfo#NETWORK_TYPE_UNMETERED}.
+     * {@link android.app.job.JobInfo#NETWORK_TYPE_NONE},
+     * {@link android.app.job.JobInfo#NETWORK_TYPE_UNMETERED}, or
+     * {@link android.app.job.JobInfo#NETWORK_TYPE_NOT_ROAMING}.
      */
     public int getNetworkType() {
         return networkType;
diff --git a/core/java/android/app/job/JobScheduler.java b/core/java/android/app/job/JobScheduler.java
index d1e563f..9618cd10 100644
--- a/core/java/android/app/job/JobScheduler.java
+++ b/core/java/android/app/job/JobScheduler.java
@@ -16,6 +16,9 @@
 
 package android.app.job;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import java.util.List;
 
 /**
@@ -78,7 +81,6 @@
      * Cancel a job that is pending in the JobScheduler.
      * @param jobId unique identifier for this job. Obtain this value from the jobs returned by
      * {@link #getAllPendingJobs()}.
-     * @return
      */
     public abstract void cancel(int jobId);
 
@@ -88,8 +90,18 @@
     public abstract void cancelAll();
 
     /**
-     * @return a list of all the jobs registered by this package that have not yet been executed.
+     * Retrieve all jobs for this package that are pending in the JobScheduler.
+     *
+     * @return a list of all the jobs registered by this package that have not
+     *         yet been executed.
      */
-    public abstract List<JobInfo> getAllPendingJobs();
+    public abstract @NonNull List<JobInfo> getAllPendingJobs();
 
+    /**
+     * Retrieve a specific job for this package that is pending in the
+     * JobScheduler.
+     *
+     * @return job registered by this package that has not yet been executed.
+     */
+    public abstract @Nullable JobInfo getPendingJob(int jobId);
 }
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index af7a465..b6c5c6f 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -119,12 +119,9 @@
     private String mReason;
     private String mExtraInfo;
     private boolean mIsFailover;
-    private boolean mIsRoaming;
-
-    /**
-     * Indicates whether network connectivity is possible:
-     */
     private boolean mIsAvailable;
+    private boolean mIsRoaming;
+    private boolean mIsMetered;
 
     /**
      * @hide
@@ -139,8 +136,6 @@
         mSubtypeName = subtypeName;
         setDetailedState(DetailedState.IDLE, null, null);
         mState = State.UNKNOWN;
-        mIsAvailable = false; // until we're told otherwise, assume unavailable
-        mIsRoaming = false;
     }
 
     /** {@hide} */
@@ -156,8 +151,9 @@
                 mReason = source.mReason;
                 mExtraInfo = source.mExtraInfo;
                 mIsFailover = source.mIsFailover;
-                mIsRoaming = source.mIsRoaming;
                 mIsAvailable = source.mIsAvailable;
+                mIsRoaming = source.mIsRoaming;
+                mIsMetered = source.mIsMetered;
             }
         }
     }
@@ -330,6 +326,30 @@
     }
 
     /**
+     * Returns if this network is metered. A network is classified as metered
+     * when the user is sensitive to heavy data usage on that connection due to
+     * monetary costs, data limitations or battery/performance issues. You
+     * should check this before doing large data transfers, and warn the user or
+     * delay the operation until another network is available.
+     *
+     * @return {@code true} if large transfers should be avoided, otherwise
+     *         {@code false}.
+     */
+    public boolean isMetered() {
+        synchronized (this) {
+            return mIsMetered;
+        }
+    }
+
+    /** {@hide} */
+    @VisibleForTesting
+    public void setMetered(boolean isMetered) {
+        synchronized (this) {
+            mIsMetered = isMetered;
+        }
+    }
+
+    /**
      * Reports the current coarse-grained state of the network.
      * @return the coarse-grained state
      */
@@ -409,26 +429,21 @@
             append("], state: ").append(mState).append("/").append(mDetailedState).
             append(", reason: ").append(mReason == null ? "(unspecified)" : mReason).
             append(", extra: ").append(mExtraInfo == null ? "(none)" : mExtraInfo).
-            append(", roaming: ").append(mIsRoaming).
             append(", failover: ").append(mIsFailover).
-            append(", isAvailable: ").append(mIsAvailable).
+            append(", available: ").append(mIsAvailable).
+            append(", roaming: ").append(mIsRoaming).
+            append(", metered: ").append(mIsMetered).
             append("]");
             return builder.toString();
         }
     }
 
-    /**
-     * Implement the Parcelable interface
-     * @hide
-     */
+    @Override
     public int describeContents() {
         return 0;
     }
 
-    /**
-     * Implement the Parcelable interface.
-     * @hide
-     */
+    @Override
     public void writeToParcel(Parcel dest, int flags) {
         synchronized (this) {
             dest.writeInt(mNetworkType);
@@ -440,35 +455,34 @@
             dest.writeInt(mIsFailover ? 1 : 0);
             dest.writeInt(mIsAvailable ? 1 : 0);
             dest.writeInt(mIsRoaming ? 1 : 0);
+            dest.writeInt(mIsMetered ? 1 : 0);
             dest.writeString(mReason);
             dest.writeString(mExtraInfo);
         }
     }
 
-    /**
-     * Implement the Parcelable interface.
-     * @hide
-     */
-    public static final Creator<NetworkInfo> CREATOR =
-        new Creator<NetworkInfo>() {
-            public NetworkInfo createFromParcel(Parcel in) {
-                int netType = in.readInt();
-                int subtype = in.readInt();
-                String typeName = in.readString();
-                String subtypeName = in.readString();
-                NetworkInfo netInfo = new NetworkInfo(netType, subtype, typeName, subtypeName);
-                netInfo.mState = State.valueOf(in.readString());
-                netInfo.mDetailedState = DetailedState.valueOf(in.readString());
-                netInfo.mIsFailover = in.readInt() != 0;
-                netInfo.mIsAvailable = in.readInt() != 0;
-                netInfo.mIsRoaming = in.readInt() != 0;
-                netInfo.mReason = in.readString();
-                netInfo.mExtraInfo = in.readString();
-                return netInfo;
-            }
+    public static final Creator<NetworkInfo> CREATOR = new Creator<NetworkInfo>() {
+        @Override
+        public NetworkInfo createFromParcel(Parcel in) {
+            int netType = in.readInt();
+            int subtype = in.readInt();
+            String typeName = in.readString();
+            String subtypeName = in.readString();
+            NetworkInfo netInfo = new NetworkInfo(netType, subtype, typeName, subtypeName);
+            netInfo.mState = State.valueOf(in.readString());
+            netInfo.mDetailedState = DetailedState.valueOf(in.readString());
+            netInfo.mIsFailover = in.readInt() != 0;
+            netInfo.mIsAvailable = in.readInt() != 0;
+            netInfo.mIsRoaming = in.readInt() != 0;
+            netInfo.mIsMetered = in.readInt() != 0;
+            netInfo.mReason = in.readString();
+            netInfo.mExtraInfo = in.readString();
+            return netInfo;
+        }
 
-            public NetworkInfo[] newArray(int size) {
-                return new NetworkInfo[size];
-            }
-        };
+        @Override
+        public NetworkInfo[] newArray(int size) {
+            return new NetworkInfo[size];
+        }
+    };
 }
diff --git a/core/java/android/net/NetworkState.java b/core/java/android/net/NetworkState.java
index 933287f..95e3802 100644
--- a/core/java/android/net/NetworkState.java
+++ b/core/java/android/net/NetworkState.java
@@ -25,6 +25,7 @@
  * @hide
  */
 public class NetworkState implements Parcelable {
+    public static final NetworkState EMPTY = new NetworkState(null, null, null, null, null, null);
 
     public final NetworkInfo networkInfo;
     public final LinkProperties linkProperties;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 7f94d0e..b9a3cff 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -3090,7 +3090,7 @@
                     || uidWifiRunningTime != 0) {
                 dumpLine(pw, uid, category, WIFI_DATA, fullWifiLockOnTime, wifiScanTime,
                         uidWifiRunningTime, wifiScanCount,
-                        /* legacy fields follow, keep at 0 */ 0, 0, 0, 0);
+                        /* legacy fields follow, keep at 0 */ 0, 0, 0);
             }
 
             dumpControllerActivityLine(pw, uid, category, WIFI_CONTROLLER_DATA,
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index 8472f78..b826584 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -412,6 +412,11 @@
 
         public static final String COLUMN_ALLOW_WRITE = "allow_write";
 
+        public static final int FLAG_REQUIRES_CHARGING = 1 << 0;
+        public static final int FLAG_REQUIRES_DEVICE_IDLE = 1 << 1;
+
+        public static final String COLUMN_FLAGS = "flags";
+
         /**
          * default value for {@link #COLUMN_LAST_UPDATESRC}.
          * This value is used when this column's value is not relevant.
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index 3ec7656..0d51b5b 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -87,7 +87,7 @@
     void timeShiftEnablePositionTracking(in IBinder sessionToken, boolean enable, int userId);
 
     // For the recording session
-    void startRecording(in IBinder sessionToken, in Uri programHint, int userId);
+    void startRecording(in IBinder sessionToken, in Uri programUri, int userId);
     void stopRecording(in IBinder sessionToken, int userId);
 
     // For TV input hardware binding
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index b1ce8d4..356ec3c 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -56,6 +56,6 @@
     void timeShiftEnablePositionTracking(boolean enable);
 
     // For the recording session
-    void startRecording(in Uri programHint);
+    void startRecording(in Uri programUri);
     void stopRecording();
 }
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 56103ad..07cfbda 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -352,8 +352,8 @@
     }
 
     @Override
-    public void startRecording(@Nullable Uri programHint) {
-        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_START_RECORDING, programHint));
+    public void startRecording(@Nullable Uri programUri) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_START_RECORDING, programUri));
     }
 
     @Override
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 5c9d2b2..1d894e1 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -2093,16 +2093,16 @@
         /**
          * Starts TV program recording in the current recording session.
          *
-         * @param programHint The URI for the TV program to record as a hint, built by
+         * @param programUri The URI for the TV program to record as a hint, built by
          *            {@link TvContract#buildProgramUri(long)}. Can be {@code null}.
          */
-        void startRecording(@Nullable Uri programHint) {
+        void startRecording(@Nullable Uri programUri) {
             if (mToken == null) {
                 Log.w(TAG, "The session has been already released");
                 return;
             }
             try {
-                mService.startRecording(mToken, programHint, mUserId);
+                mService.startRecording(mToken, programUri, mUserId);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 7ae8da0..488b284 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -1695,8 +1695,8 @@
          *
          * <p>The application may supply the URI for a TV program for filling in program specific
          * data fields in the {@link android.media.tv.TvContract.RecordedPrograms} table.
-         * A non-null {@code programHint} implies the started recording should be of that specific
-         * program, whereas null {@code programHint} does not impose such a requirement and the
+         * A non-null {@code programUri} implies the started recording should be of that specific
+         * program, whereas null {@code programUri} does not impose such a requirement and the
          * recording can span across multiple TV programs. In either case, the application must call
          * {@link TvRecordingClient#stopRecording()} to stop the recording.
          *
@@ -1761,8 +1761,8 @@
          * Calls {@link #onStartRecording(Uri)}.
          *
          */
-        void startRecording(@Nullable  Uri programHint) {
-            onStartRecording(programHint);
+        void startRecording(@Nullable  Uri programUri) {
+            onStartRecording(programUri);
         }
 
         /**
diff --git a/media/java/android/media/tv/TvRecordingClient.java b/media/java/android/media/tv/TvRecordingClient.java
index 2613376..5aadeb6 100644
--- a/media/java/android/media/tv/TvRecordingClient.java
+++ b/media/java/android/media/tv/TvRecordingClient.java
@@ -157,7 +157,7 @@
      *
      * <p>The application may supply the URI for a TV program for filling in program specific data
      * fields in the {@link android.media.tv.TvContract.RecordedPrograms} table.
-     * A non-null {@code programHint} implies the started recording should be of that specific
+     * A non-null {@code programUri} implies the started recording should be of that specific
      * program, whereas null {@code programUri} does not impose such a requirement and the
      * recording can span across multiple TV programs. In either case, the application must call
      * {@link TvRecordingClient#stopRecording()} to stop the recording.
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 70c8957..31058a9 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -162,6 +162,9 @@
     <!-- It's like, reality, but, you know, virtual -->
     <uses-permission android:name="android.permission.ACCESS_VR_MANAGER" />
 
+    <!-- the ability to rename notifications posted by other apps -->
+    <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
+
     <application
         android:name=".SystemUIApplication"
         android:persistent="true"
diff --git a/packages/SystemUI/res/layout/notification_settings_icon_row.xml b/packages/SystemUI/res/layout/notification_settings_icon_row.xml
index f47083a..da3461b90 100644
--- a/packages/SystemUI/res/layout/notification_settings_icon_row.xml
+++ b/packages/SystemUI/res/layout/notification_settings_icon_row.xml
@@ -24,12 +24,9 @@
 
     <com.android.systemui.statusbar.AlphaOptimizedImageView
         android:id="@+id/gear_icon"
-        android:layout_width="@dimen/notification_gear_width"
-        android:layout_height="@dimen/notification_gear_height"
-        android:paddingTop="@dimen/notification_gear_top_padding"
-        android:paddingStart="@dimen/notification_gear_padding"
-        android:paddingEnd="@dimen/notification_gear_padding"
-        android:paddingBottom="@dimen/notification_gear_padding"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="@dimen/notification_gear_padding"
         android:src="@drawable/ic_settings"
         android:tint="@color/notification_gear_color"
         android:alpha="0"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 5043610..b8f576b 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -81,13 +81,7 @@
     <!-- Width of the space containing the gear icon behind a notification -->
     <dimen name="notification_gear_width">64dp</dimen>
 
-    <!-- Height of the space containing the gear icon behind a notification -->
-    <dimen name="notification_gear_height">74dp</dimen>
-
-    <!-- The space above the gear icon displayed behind a notification -->
-    <dimen name="notification_gear_top_padding">30dp</dimen>
-
-    <!-- The space on either side and below the gear icon displayed behind a notification  -->
+    <!-- The space around the gear icon displayed behind a notification  -->
     <dimen name="notification_gear_padding">20dp</dimen>
 
     <!-- size at which Notification icons will be drawn in the status bar -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 81fa520..2758551 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -182,7 +182,7 @@
     <!-- Notification title displayed when a screenshot is saved to the Gallery. [CHAR LIMIT=50] -->
     <string name="screenshot_saved_title">Screenshot captured.</string>
     <!-- Notification text displayed when a screenshot is saved to the Gallery. [CHAR LIMIT=100] -->
-    <string name="screenshot_saved_text">Touch to view your screenshot.</string>
+    <string name="screenshot_saved_text">Tap to view your screenshot.</string>
     <!-- Notification title displayed when we fail to take a screenshot. [CHAR LIMIT=50] -->
     <string name="screenshot_failed_title">Couldn\'t capture screenshot.</string>
     <!-- Notification text displayed when we fail to save a screenshot for unknown reasons. [CHAR LIMIT=100] -->
@@ -819,7 +819,7 @@
     <string name="speed_bump_explanation">Less urgent notifications below</string>
 
     <!-- Shows to explain the double tap interaction with notifications: After tapping a notification on Keyguard, this will explain users to tap again to launch a notification. [CHAR LIMIT=60] -->
-    <string name="notification_tap_again">Touch again to open</string>
+    <string name="notification_tap_again">Tap again to open</string>
 
     <!-- Shows when people have pressed the unlock icon to explain how to unlock. [CHAR LIMIT=60] -->
     <string name="keyguard_unlock">Swipe up to unlock</string>
@@ -1054,7 +1054,7 @@
     <!-- Screen pinning dialog title. -->
     <string name="screen_pinning_title">Screen is pinned</string>
     <!-- Screen pinning dialog description. -->
-    <string name="screen_pinning_description">This keeps it in view until you unpin. Touch and hold Back to unpin.</string>
+    <string name="screen_pinning_description">This keeps it in view until you unpin. Touch &amp; hold Back to unpin.</string>
     <!-- Screen pinning positive response. -->
     <string name="screen_pinning_positive">Got it</string>
     <!-- Screen pinning negative response. -->
@@ -1082,7 +1082,7 @@
     <string name="volumeui_notification_title"><xliff:g id="app_name" example="Volume Prototype 1">%1$s</xliff:g> is the volume dialog</string>
 
     <!-- VolumeUI restoration notification: text -->
-    <string name="volumeui_notification_text">Touch to restore the original.</string>
+    <string name="volumeui_notification_text">Tap to restore the original.</string>
 
     <!-- Toast shown when user unlocks screen and managed profile activity is in the foreground -->
     <string name="managed_profile_foreground_toast">You\'re using your work profile</string>
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 9a6fa9c..5c50b5c 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -493,7 +493,15 @@
         updateSwipeProgressFromOffset(view, canAnimViewBeDismissed);
     }
 
-    public void snapChildIfNeeded(final View view, boolean animate) {
+    /**
+     * Called when a view is updated to be non-dismissable, if the view was being dismissed before
+     * the update this will handle snapping it back into place.
+     *
+     * @param view the view to snap if necessary.
+     * @param animate whether to animate the snap or not.
+     * @param targetLeft the target to snap to.
+     */
+    public void snapChildIfNeeded(final View view, boolean animate, float targetLeft) {
         if ((mDragging && mCurrView == view) || mSnappingChild) {
             return;
         }
@@ -507,7 +515,7 @@
         }
         if (needToSnap) {
             if (animate) {
-                snapChild(view, 0 /* targetLeft */, 0.0f /* velocity */);
+                snapChild(view, targetLeft, 0.0f /* velocity */);
             } else {
                 snapChildInstantly(view);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 6f26222..a0aeb2f 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -247,8 +247,8 @@
                     }
                 }
             } catch (PackageManager.NameNotFoundException e) {
-                Log.w(TAG, "Failed to swap drawable; "
-                        + component.flattenToShortString() + " not found", e);
+                Log.v(TAG, "Assistant component "
+                        + component.flattenToShortString() + " not found");
             } catch (Resources.NotFoundException nfe) {
                 Log.w(TAG, "Failed to swap drawable from "
                         + component.flattenToShortString(), nfe);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index eb08947..e6cbbea 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -43,6 +43,7 @@
 import android.media.MediaActionSound;
 import android.net.Uri;
 import android.os.AsyncTask;
+import android.os.Bundle;
 import android.os.Environment;
 import android.os.Process;
 import android.provider.MediaStore;
@@ -164,6 +165,11 @@
         c.drawColor(overlayColor);
         c.setBitmap(null);
 
+        // swap "System UI" out for "Android System"
+        final Bundle extras = new Bundle();
+        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
+                context.getString(com.android.internal.R.string.android_system_label));
+
         // Show the intermediate notification
         mTickerAddSpace = !mTickerAddSpace;
         mNotificationManager = nManager;
@@ -180,6 +186,8 @@
                 .setSmallIcon(R.drawable.stat_notify_image)
                 .setCategory(Notification.CATEGORY_PROGRESS)
                 .setWhen(now)
+                .setShowWhen(true)
+                .addExtras(extras)
                 .setColor(r.getColor(
                         com.android.internal.R.color.system_notification_accent_color));
 
@@ -190,6 +198,8 @@
             .setContentText(r.getString(R.string.screenshot_saving_text))
             .setSmallIcon(R.drawable.stat_notify_image)
             .setWhen(now)
+            .setShowWhen(true)
+            .addExtras(extras)
             .setColor(r.getColor(com.android.internal.R.color.system_notification_accent_color))
             .setStyle(mNotificationStyle)
             .setPublicVersion(mPublicNotificationBuilder.build());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 2806729..caa1585 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -263,6 +263,9 @@
         int headsUpheight = headsUpCustom && beforeN ? mMaxHeadsUpHeightLegacy
                 : mMaxHeadsUpHeight;
         layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight);
+        if (mSettingsIconRow != null) {
+            mSettingsIconRow.updateVerticalLocation();
+        }
     }
 
     public StatusBarNotification getStatusBarNotification() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java
index 9ed5022..060d8f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java
@@ -55,8 +55,11 @@
     private boolean mOnLeft = true;
     private boolean mDismissing = false;
     private boolean mSnapping = false;
+    private boolean mIconPlaced = false;
+
     private int[] mGearLocation = new int[2];
     private int[] mParentLocation = new int[2];
+    private int mVertSpaceForGear;
 
     public NotificationSettingsIconRow(Context context) {
         this(context, null);
@@ -83,16 +86,18 @@
         setOnClickListener(this);
         mHorizSpaceForGear =
                 getResources().getDimensionPixelOffset(R.dimen.notification_gear_width);
+        mVertSpaceForGear = getResources().getDimensionPixelOffset(R.dimen.notification_min_height);
         resetState();
     }
 
     public void resetState() {
         setGearAlpha(0f);
+        mIconPlaced = false;
         mSettingsFadedIn = false;
         mAnimating = false;
         mSnapping = false;
         mDismissing = false;
-        setIconLocation(true /* on left */, true /* force */);
+        setIconLocation(true /* on left */);
         if (mListener != null) {
             mListener.onSettingsIconRowReset(mParent);
         }
@@ -104,7 +109,7 @@
 
     public void setNotificationRowParent(ExpandableNotificationRow parent) {
         mParent = parent;
-        setIconLocation(mOnLeft, true /* force */);
+        setIconLocation(mOnLeft);
     }
 
     public void setAppName(String appName) {
@@ -184,7 +189,7 @@
         if (isIconLocationChange(transX)) {
             setGearAlpha(0f);
         }
-        setIconLocation(transX > 0 /* fromLeft */, false /* force */);
+        setIconLocation(transX > 0 /* fromLeft */);
         mFadeAnimator = ValueAnimator.ofFloat(mGearIcon.getAlpha(), 1);
         mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
             @Override
@@ -221,22 +226,39 @@
         mFadeAnimator.start();
     }
 
-    @Override
-    public void onRtlPropertiesChanged(int layoutDirection) {
-        setIconLocation(mOnLeft, true /* force */);
+    public void updateVerticalLocation() {
+        if (mParent == null) {
+            return;
+        }
+        int parentHeight = mParent.getCollapsedHeight();
+        if (parentHeight < mVertSpaceForGear) {
+            mGearIcon.setTranslationY((parentHeight / 2) - (mGearIcon.getHeight() / 2));
+        } else {
+            mGearIcon.setTranslationY((mVertSpaceForGear - mGearIcon.getHeight()) / 2);
+        }
     }
 
-    public void setIconLocation(boolean onLeft, boolean force) {
-        if ((!force && onLeft == mOnLeft) || mSnapping || mParent == null) {
+    @Override
+    public void onRtlPropertiesChanged(int layoutDirection) {
+        setIconLocation(mOnLeft);
+    }
+
+    public void setIconLocation(boolean onLeft) {
+        updateVerticalLocation();
+        if ((mIconPlaced && onLeft == mOnLeft) || mSnapping || mParent == null
+                || mGearIcon.getWidth() == 0) {
             // Do nothing
             return;
         }
         final boolean isRtl = mParent.isLayoutRtl();
+
         // TODO No need to cast to float here once b/28050538 is fixed.
         final float left = (float) (isRtl ? -(mParent.getWidth() - mHorizSpaceForGear) : 0);
         final float right = (float) (isRtl ? 0 : (mParent.getWidth() - mHorizSpaceForGear));
-        setTranslationX(onLeft ? left : right);
+        final float centerX = ((mHorizSpaceForGear - mGearIcon.getWidth()) / 2);
+        setTranslationX(onLeft ? left + centerX : right + centerX);
         mOnLeft = onLeft;
+        mIconPlaced = true;
     }
 
     public boolean isIconLocationChange(float translation) {
@@ -264,9 +286,8 @@
                 mParent.getLocationOnScreen(mParentLocation);
 
                 final int centerX = (int) (mHorizSpaceForGear / 2);
-                // Top / bottom padding are not equal, need to subtract them to get center of gear.
-                final int centerY = (int) (mGearIcon.getHeight() - mGearIcon.getPaddingTop()
-                        - mGearIcon.getPaddingBottom()) / 2 + mGearIcon.getPaddingTop();
+                final int centerY =
+                        (int) (mGearIcon.getTranslationY() * 2 + mGearIcon.getHeight())/ 2;
                 final int x = mGearLocation[0] - mParentLocation[0] + centerX;
                 final int y = mGearLocation[1] - mParentLocation[1] + centerY;
                 mListener.onGearTouched(mParent, x, y);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java
index 2294931..d90a21d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridView.java
@@ -109,7 +109,7 @@
 
     private void showOfflineAuthUi() {
         // TODO: Show keyguard UI in-place.
-        mStatusBar.executeRunnableDismissingKeyguard(null, null, true, true);
+        mStatusBar.executeRunnableDismissingKeyguard(null, null, true, true, true);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 83a15ad..8d74536 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -490,7 +490,7 @@
             AsyncTask.execute(runnable);
         } else {
             mPhoneStatusBar.executeRunnableDismissingKeyguard(runnable, null /* cancelAction */,
-                    false /* dismissShade */, false /* afterKeyguardGone */);
+                    false /* dismissShade */, false /* afterKeyguardGone */, true /* deferred */);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 8617104..7e2fa2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -1199,7 +1199,7 @@
         }
         if (mQsFullyExpanded && mFalsingManager.shouldEnforceBouncer()) {
             mStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
-                    false /* dismissShade */, true /* afterKeyguardGone */);
+                    false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */);
         }
         if (DEBUG) {
             invalidate();
@@ -1794,7 +1794,8 @@
                     public void run() {
                         mKeyguardBottomArea.launchLeftAffordance();
                     }
-                }, null, true /* dismissShade */, false /* afterKeyguardGone */);
+                }, null, true /* dismissShade */, false /* afterKeyguardGone */,
+                        true /* deferred */);
             }
             else {
                 mKeyguardBottomArea.launchLeftAffordance();
@@ -1813,7 +1814,8 @@
                     public void run() {
                         mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource);
                     }
-                }, null, true /* dismissShade */, false /* afterKeyguardGone */);
+                }, null, true /* dismissShade */, false /* afterKeyguardGone */,
+                    true /* deferred */);
             }
             else {
                 mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index c36c482..436dc9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -3177,13 +3177,14 @@
             }
         };
         executeRunnableDismissingKeyguard(runnable, cancelRunnable, dismissShade,
-                afterKeyguardGone);
+                afterKeyguardGone, true /* deferred */);
     }
 
     public void executeRunnableDismissingKeyguard(final Runnable runnable,
             final Runnable cancelAction,
             final boolean dismissShade,
-            final boolean afterKeyguardGone) {
+            final boolean afterKeyguardGone,
+            final boolean deferred) {
         final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
         dismissKeyguardThenExecute(new OnDismissAction() {
             @Override
@@ -3206,7 +3207,7 @@
                     animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
                             true /* delayed*/);
                 }
-                return true;
+                return deferred;
             }
         }, cancelAction, afterKeyguardGone);
     }
@@ -3525,7 +3526,7 @@
             @Override
             public void run() {
                 mLeaveOpenOnKeyguardHide = true;
-                executeRunnableDismissingKeyguard(runnable, null, false, false);
+                executeRunnableDismissingKeyguard(runnable, null, false, false, false);
             }
         });
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index c962606..3e0f930 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -701,7 +701,7 @@
         mFalsingManager.onNotificationDismissed();
         if (mFalsingManager.shouldEnforceBouncer()) {
             mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
-                    false /* dismissShade */, true /* afterKeyguardGone */);
+                    false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */);
         }
     }
 
@@ -990,9 +990,11 @@
                 true /* isDismissAll */);
     }
 
-    public void snapViewIfNeeded(View child) {
+    public void snapViewIfNeeded(ExpandableNotificationRow child) {
         boolean animate = mIsExpanded || isPinnedHeadsUp(child);
-        mSwipeHelper.snapChildIfNeeded(child, animate);
+        // If the child is showing the gear to go to settings, snap to that
+        float targetLeft = child.getSettingsRow().isVisible() ? child.getTranslation() : 0;
+        mSwipeHelper.snapChildIfNeeded(child, animate, targetLeft);
     }
 
     @Override
@@ -3524,8 +3526,7 @@
                     } else {
                         // Check scheduled, reset alpha and update location; check will fade it in
                         mCurrIconRow.setGearAlpha(0f);
-                        mCurrIconRow.setIconLocation(translation > 0 /* onLeft */,
-                                false /* force */);
+                        mCurrIconRow.setIconLocation(translation > 0 /* onLeft */);
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index b069361..f06583b 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -821,37 +821,25 @@
     }
 
     private NetworkState getFilteredNetworkState(int networkType, int uid) {
-        NetworkInfo info = null;
-        LinkProperties lp = null;
-        NetworkCapabilities nc = null;
-        Network network = null;
-        String subscriberId = null;
-
         if (mLegacyTypeTracker.isTypeSupported(networkType)) {
-            NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
+            final NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
+            final NetworkState state;
             if (nai != null) {
-                synchronized (nai) {
-                    info = new NetworkInfo(nai.networkInfo);
-                    lp = new LinkProperties(nai.linkProperties);
-                    nc = new NetworkCapabilities(nai.networkCapabilities);
-                    // Network objects are outwardly immutable so there is no point to duplicating.
-                    // Duplicating also precludes sharing socket factories and connection pools.
-                    network = nai.network;
-                    subscriberId = (nai.networkMisc != null) ? nai.networkMisc.subscriberId : null;
-                }
-                info.setType(networkType);
+                state = nai.getNetworkState();
+                state.networkInfo.setType(networkType);
             } else {
-                info = new NetworkInfo(networkType, 0, getNetworkTypeName(networkType), "");
+                final NetworkInfo info = new NetworkInfo(networkType, 0,
+                        getNetworkTypeName(networkType), "");
                 info.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null);
                 info.setIsAvailable(true);
-                lp = new LinkProperties();
-                nc = new NetworkCapabilities();
-                network = null;
+                state = new NetworkState(info, new LinkProperties(), new NetworkCapabilities(),
+                        null, null, null);
             }
-            info = getFilteredNetworkInfo(info, lp, uid);
+            filterNetworkStateForUid(state, uid);
+            return state;
+        } else {
+            return NetworkState.EMPTY;
         }
-
-        return new NetworkState(info, lp, nc, network, subscriberId, null);
     }
 
     private NetworkAgentInfo getNetworkAgentInfoForNetwork(Network network) {
@@ -861,7 +849,7 @@
         synchronized (mNetworkForNetId) {
             return mNetworkForNetId.get(network.netId);
         }
-    };
+    }
 
     private Network[] getVpnUnderlyingNetworks(int uid) {
         if (!mLockdownEnabled) {
@@ -877,12 +865,6 @@
     }
 
     private NetworkState getUnfilteredActiveNetworkState(int uid) {
-        NetworkInfo info = null;
-        LinkProperties lp = null;
-        NetworkCapabilities nc = null;
-        Network network = null;
-        String subscriberId = null;
-
         NetworkAgentInfo nai = getDefaultNetwork();
 
         final Network[] networks = getVpnUnderlyingNetworks(uid);
@@ -900,18 +882,10 @@
         }
 
         if (nai != null) {
-            synchronized (nai) {
-                info = new NetworkInfo(nai.networkInfo);
-                lp = new LinkProperties(nai.linkProperties);
-                nc = new NetworkCapabilities(nai.networkCapabilities);
-                // Network objects are outwardly immutable so there is no point to duplicating.
-                // Duplicating also precludes sharing socket factories and connection pools.
-                network = nai.network;
-                subscriberId = (nai.networkMisc != null) ? nai.networkMisc.subscriberId : null;
-            }
+            return nai.getNetworkState();
+        } else {
+            return NetworkState.EMPTY;
         }
-
-        return new NetworkState(info, lp, nc, network, subscriberId, null);
     }
 
     /**
@@ -952,21 +926,29 @@
     }
 
     /**
-     * Return a filtered {@link NetworkInfo}, potentially marked
-     * {@link DetailedState#BLOCKED} based on
-     * {@link #isNetworkWithLinkPropertiesBlocked}.
+     * Apply any relevant filters to {@link NetworkState} for the given UID. For
+     * example, this may mark the network as {@link DetailedState#BLOCKED} based
+     * on {@link #isNetworkWithLinkPropertiesBlocked}, or
+     * {@link NetworkInfo#isMetered()} based on network policies.
      */
-    private NetworkInfo getFilteredNetworkInfo(NetworkInfo info, LinkProperties lp, int uid) {
-        if (info != null && isNetworkWithLinkPropertiesBlocked(lp, uid)) {
-            // network is blocked; clone and override state
-            info = new NetworkInfo(info);
-            info.setDetailedState(DetailedState.BLOCKED, null, null);
+    private void filterNetworkStateForUid(NetworkState state, int uid) {
+        if (state == null || state.networkInfo == null || state.linkProperties == null) return;
+
+        if (isNetworkWithLinkPropertiesBlocked(state.linkProperties, uid)) {
+            state.networkInfo.setDetailedState(DetailedState.BLOCKED, null, null);
         }
-        if (info != null && mLockdownTracker != null) {
-            info = mLockdownTracker.augmentNetworkInfo(info);
-            if (VDBG) log("returning Locked NetworkInfo");
+        if (mLockdownTracker != null) {
+            mLockdownTracker.augmentNetworkInfo(state.networkInfo);
         }
-        return info;
+
+        // TODO: apply metered state closer to NetworkAgentInfo
+        final long token = Binder.clearCallingIdentity();
+        try {
+            state.networkInfo.setMetered(mPolicyManager.isNetworkMetered(state));
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     /**
@@ -980,10 +962,10 @@
     public NetworkInfo getActiveNetworkInfo() {
         enforceAccessPermission();
         final int uid = Binder.getCallingUid();
-        NetworkState state = getUnfilteredActiveNetworkState(uid);
-        NetworkInfo ni = getFilteredNetworkInfo(state.networkInfo, state.linkProperties, uid);
-        maybeLogBlockedNetworkInfo(ni, uid);
-        return ni;
+        final NetworkState state = getUnfilteredActiveNetworkState(uid);
+        filterNetworkStateForUid(state, uid);
+        maybeLogBlockedNetworkInfo(state.networkInfo, uid);
+        return state.networkInfo;
     }
 
     @Override
@@ -1027,8 +1009,9 @@
     @Override
     public NetworkInfo getActiveNetworkInfoForUid(int uid) {
         enforceConnectivityInternalPermission();
-        NetworkState state = getUnfilteredActiveNetworkState(uid);
-        return getFilteredNetworkInfo(state.networkInfo, state.linkProperties, uid);
+        final NetworkState state = getUnfilteredActiveNetworkState(uid);
+        filterNetworkStateForUid(state, uid);
+        return state.networkInfo;
     }
 
     @Override
@@ -1039,12 +1022,13 @@
             // A VPN is active, so we may need to return one of its underlying networks. This
             // information is not available in LegacyTypeTracker, so we have to get it from
             // getUnfilteredActiveNetworkState.
-            NetworkState state = getUnfilteredActiveNetworkState(uid);
+            final NetworkState state = getUnfilteredActiveNetworkState(uid);
             if (state.networkInfo != null && state.networkInfo.getType() == networkType) {
-                return getFilteredNetworkInfo(state.networkInfo, state.linkProperties, uid);
+                filterNetworkStateForUid(state, uid);
+                return state.networkInfo;
             }
         }
-        NetworkState state = getFilteredNetworkState(networkType, uid);
+        final NetworkState state = getFilteredNetworkState(networkType, uid);
         return state.networkInfo;
     }
 
@@ -1052,15 +1036,14 @@
     public NetworkInfo getNetworkInfoForNetwork(Network network) {
         enforceAccessPermission();
         final int uid = Binder.getCallingUid();
-        NetworkInfo info = null;
-        NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+        final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
         if (nai != null) {
-            synchronized (nai) {
-                info = new NetworkInfo(nai.networkInfo);
-                info = getFilteredNetworkInfo(info, nai.linkProperties, uid);
-            }
+            final NetworkState state = nai.getNetworkState();
+            filterNetworkStateForUid(state, uid);
+            return state.networkInfo;
+        } else {
+            return null;
         }
-        return info;
     }
 
     @Override
@@ -1222,12 +1205,7 @@
         for (Network network : getAllNetworks()) {
             final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
             if (nai != null) {
-                synchronized (nai) {
-                    final String subscriberId = (nai.networkMisc != null)
-                            ? nai.networkMisc.subscriberId : null;
-                    result.add(new NetworkState(nai.networkInfo, nai.linkProperties,
-                            nai.networkCapabilities, network, subscriberId, null));
-                }
+                result.add(nai.getNetworkState());
             }
         }
         return result.toArray(new NetworkState[result.size()]);
@@ -1255,24 +1233,9 @@
     @Override
     public boolean isActiveNetworkMetered() {
         enforceAccessPermission();
-        final int uid = Binder.getCallingUid();
-        final long token = Binder.clearCallingIdentity();
-        try {
-            return isActiveNetworkMeteredUnchecked(uid);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
 
-    private boolean isActiveNetworkMeteredUnchecked(int uid) {
-        final NetworkState state = getUnfilteredActiveNetworkState(uid);
-        if (state.networkInfo != null) {
-            try {
-                return mPolicyManager.isNetworkMetered(state);
-            } catch (RemoteException e) {
-            }
-        }
-        return false;
+        final NetworkInfo info = getActiveNetworkInfo();
+        return (info != null) ? info.isMetered() : false;
     }
 
     private INetworkManagementEventObserver mDataActivityObserver = new BaseNetworkObserver() {
@@ -1490,7 +1453,8 @@
 
     private Intent makeGeneralIntent(NetworkInfo info, String bcastType) {
         if (mLockdownTracker != null) {
-            info = mLockdownTracker.augmentNetworkInfo(info);
+            info = new NetworkInfo(info);
+            mLockdownTracker.augmentNetworkInfo(info);
         }
 
         Intent intent = new Intent(bcastType);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1e74f81..9d7ddc7 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -11983,8 +11983,12 @@
         }
         if (receiver != null) {
             // Caller wants result sent back to them.
+            Bundle sendBundle = new Bundle();
+            // At least return the receiver extras
+            sendBundle.putBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS,
+                    pae.receiverExtras);
             try {
-                pae.receiver.send(0, null);
+                pae.receiver.send(0, sendBundle);
             } catch (RemoteException e) {
             }
         }
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index c5d38cb..3201060 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -25,6 +25,7 @@
 import android.net.NetworkInfo;
 import android.net.NetworkMisc;
 import android.net.NetworkRequest;
+import android.net.NetworkState;
 import android.os.Handler;
 import android.os.Messenger;
 import android.util.SparseArray;
@@ -247,6 +248,17 @@
         currentScore = newScore;
     }
 
+    public NetworkState getNetworkState() {
+        synchronized (this) {
+            // Network objects are outwardly immutable so there is no point to duplicating.
+            // Duplicating also precludes sharing socket factories and connection pools.
+            final String subscriberId = (networkMisc != null) ? networkMisc.subscriberId : null;
+            return new NetworkState(new NetworkInfo(networkInfo),
+                    new LinkProperties(linkProperties),
+                    new NetworkCapabilities(networkCapabilities), network, subscriberId, null);
+        }
+    }
+
     public String toString() {
         return "NetworkAgentInfo{ ni{" + networkInfo + "}  " +
                 "network{" + network + "}  nethandle{" + network.getNetworkHandle() + "}  " +
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 7df8ffd..075a88f 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -40,6 +40,7 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Handler;
@@ -336,6 +337,19 @@
         }
     }
 
+    public JobInfo getPendingJob(int uid, int jobId) {
+        synchronized (mLock) {
+            List<JobStatus> jobs = mJobs.getJobsByUid(uid);
+            for (int i = jobs.size() - 1; i >= 0; i--) {
+                JobStatus job = jobs.get(i);
+                if (job.getJobId() == jobId) {
+                    return job.getJob();
+                }
+            }
+            return null;
+        }
+    }
+
     void cancelJobsForUser(int userHandle) {
         List<JobStatus> jobsForUser;
         synchronized (mLock) {
@@ -912,7 +926,8 @@
                     if (job.hasIdleConstraint()) {
                         idleCount++;
                     }
-                    if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()) {
+                    if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()
+                            || job.hasNotRoamingConstraint()) {
                         connectivityCount++;
                     }
                     if (job.hasChargingConstraint()) {
@@ -1346,6 +1361,18 @@
         }
 
         @Override
+        public JobInfo getPendingJob(int jobId) throws RemoteException {
+            final int uid = Binder.getCallingUid();
+
+            long ident = Binder.clearCallingIdentity();
+            try {
+                return JobSchedulerService.this.getPendingJob(uid, jobId);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override
         public void cancelAll() throws RemoteException {
             final int uid = Binder.getCallingUid();
 
@@ -1378,7 +1405,7 @@
 
             long identityToken = Binder.clearCallingIdentity();
             try {
-                JobSchedulerService.this.dumpInternal(pw);
+                JobSchedulerService.this.dumpInternal(pw, args);
             } finally {
                 Binder.restoreCallingIdentity(identityToken);
             }
@@ -1450,7 +1477,17 @@
         return s.toString();
     }
 
-    void dumpInternal(final PrintWriter pw) {
+    void dumpInternal(final PrintWriter pw, String[] args) {
+        int filterUid = -1;
+        if (!ArrayUtils.isEmpty(args)) {
+            try {
+                filterUid = getContext().getPackageManager().getPackageUid(args[0],
+                        PackageManager.MATCH_UNINSTALLED_PACKAGES);
+            } catch (NameNotFoundException ignored) {
+            }
+        }
+
+        final int filterUidFinal = filterUid;
         final long now = SystemClock.elapsedRealtime();
         synchronized (mLock) {
             pw.println("Started users: " + Arrays.toString(mStartedUsers));
@@ -1463,6 +1500,13 @@
                     public void process(JobStatus job) {
                         pw.print("  Job #"); pw.print(index++); pw.print(": ");
                         pw.println(job.toShortString());
+
+                        // Skip printing details if the caller requested a filter
+                        if (filterUidFinal != -1 && job.getUid() != filterUidFinal
+                                && job.getSourceUid() != filterUidFinal) {
+                            return;
+                        }
+
                         job.dump(pw, "    ", true);
                         pw.print("    Ready: ");
                         pw.print(mHandler.isReadyToBeExecutedLocked(job));
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 35628a2..5ad988a 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -365,11 +365,14 @@
          */
         private void writeConstraintsToXml(XmlSerializer out, JobStatus jobStatus) throws IOException {
             out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS);
+            if (jobStatus.hasConnectivityConstraint()) {
+                out.attribute(null, "connectivity", Boolean.toString(true));
+            }
             if (jobStatus.hasUnmeteredConstraint()) {
                 out.attribute(null, "unmetered", Boolean.toString(true));
             }
-            if (jobStatus.hasConnectivityConstraint()) {
-                out.attribute(null, "connectivity", Boolean.toString(true));
+            if (jobStatus.hasNotRoamingConstraint()) {
+                out.attribute(null, "not-roaming", Boolean.toString(true));
             }
             if (jobStatus.hasIdleConstraint()) {
                 out.attribute(null, "idle", Boolean.toString(true));
@@ -693,13 +696,17 @@
         }
 
         private void buildConstraintsFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser) {
-            String val = parser.getAttributeValue(null, "unmetered");
+            String val = parser.getAttributeValue(null, "connectivity");
+            if (val != null) {
+                jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+            }
+            val = parser.getAttributeValue(null, "unmetered");
             if (val != null) {
                 jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
             }
-            val = parser.getAttributeValue(null, "connectivity");
+            val = parser.getAttributeValue(null, "not-roaming");
             if (val != null) {
-                jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+                jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING);
             }
             val = parser.getAttributeValue(null, "idle");
             if (val != null) {
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index be9d800..88cf322 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -16,44 +16,44 @@
 
 package com.android.server.job.controllers;
 
-
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.net.ConnectivityManager;
+import android.net.INetworkPolicyListener;
 import android.net.NetworkInfo;
-import android.os.ServiceManager;
+import android.net.NetworkPolicyManager;
 import android.os.UserHandle;
 import android.util.Slog;
 
-import com.android.server.ConnectivityService;
+import com.android.internal.annotations.GuardedBy;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateChangedListener;
 
 import java.io.PrintWriter;
-import java.util.LinkedList;
-import java.util.List;
+import java.util.ArrayList;
 
 /**
  * Handles changes in connectivity.
- * We are only interested in metered vs. unmetered networks, and we're interested in them on a
- * per-user basis.
+ * <p>
+ * Each app can have a different default networks or different connectivity
+ * status due to user-requested network policies, so we need to check
+ * constraints on a per-UID basis.
  */
 public class ConnectivityController extends StateController implements
         ConnectivityManager.OnNetworkActiveListener {
     private static final String TAG = "JobScheduler.Conn";
 
-    private final List<JobStatus> mTrackedJobs = new LinkedList<JobStatus>();
-    private final BroadcastReceiver mConnectivityChangedReceiver =
-            new ConnectivityChangedReceiver();
+    private final ConnectivityManager mConnManager;
+    private final NetworkPolicyManager mNetPolicyManager;
+
+    @GuardedBy("mLock")
+    private final ArrayList<JobStatus> mTrackedJobs = new ArrayList<JobStatus>();
+
     /** Singleton. */
     private static ConnectivityController mSingleton;
     private static Object sCreationLock = new Object();
-    /** Track whether the latest active network is metered. */
-    private boolean mNetworkUnmetered;
-    /** Track whether the latest active network is connected. */
-    private boolean mNetworkConnected;
 
     public static ConnectivityController get(JobSchedulerService jms) {
         synchronized (sCreationLock) {
@@ -67,51 +67,62 @@
     private ConnectivityController(StateChangedListener stateChangedListener, Context context,
             Object lock) {
         super(stateChangedListener, context, lock);
-        // Register connectivity changed BR.
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+
+        mConnManager = mContext.getSystemService(ConnectivityManager.class);
+        mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);
+
+        final IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
         mContext.registerReceiverAsUser(
-                mConnectivityChangedReceiver, UserHandle.ALL, intentFilter, null, null);
-        ConnectivityService cs =
-                (ConnectivityService)ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
-        if (cs != null) {
-            if (cs.getActiveNetworkInfo() != null) {
-                mNetworkConnected = cs.getActiveNetworkInfo().isConnected();
-                mNetworkUnmetered = mNetworkConnected && !cs.isActiveNetworkMetered();
-            } else {
-                mNetworkConnected = mNetworkUnmetered = false;
-            }
-        }
+                mConnectivityReceiver, UserHandle.SYSTEM, intentFilter, null, null);
+
+        mNetPolicyManager.registerListener(mNetPolicyListener);
     }
 
     @Override
     public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
-        if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()) {
-            jobStatus.setConnectivityConstraintSatisfied(mNetworkConnected);
-            jobStatus.setUnmeteredConstraintSatisfied(mNetworkUnmetered);
+        if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()
+                || jobStatus.hasNotRoamingConstraint()) {
+            updateConstraintsSatisfied(jobStatus);
             mTrackedJobs.add(jobStatus);
         }
     }
 
     @Override
-    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate) {
-        if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()) {
+    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
+            boolean forUpdate) {
+        if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()
+                || jobStatus.hasNotRoamingConstraint()) {
             mTrackedJobs.remove(jobStatus);
         }
     }
 
+    private boolean updateConstraintsSatisfied(JobStatus jobStatus) {
+        final NetworkInfo info = mConnManager.getActiveNetworkInfoForUid(jobStatus.getSourceUid());
+        final boolean connected = (info != null) && info.isConnected();
+        final boolean unmetered = connected && !info.isMetered();
+        final boolean notRoaming = connected && !info.isRoaming();
+
+        boolean changed = false;
+        changed |= jobStatus.setConnectivityConstraintSatisfied(connected);
+        changed |= jobStatus.setUnmeteredConstraintSatisfied(unmetered);
+        changed |= jobStatus.setNotRoamingConstraintSatisfied(notRoaming);
+        return changed;
+    }
+
     /**
-     * @param userId Id of the user for whom we are updating the connectivity state.
+     * Update all jobs tracked by this controller.
+     *
+     * @param uid only update jobs belonging to this UID, or {@code -1} to
+     *            update all tracked jobs.
      */
-    private void updateTrackedJobs(int userId) {
+    private void updateTrackedJobs(int uid) {
         synchronized (mLock) {
             boolean changed = false;
-            for (JobStatus js : mTrackedJobs) {
-                if (js.getUserId() != userId) {
-                    continue;
+            for (int i = 0; i < mTrackedJobs.size(); i++) {
+                final JobStatus js = mTrackedJobs.get(i);
+                if (uid == -1 || uid == js.getSourceUid()) {
+                    changed |= updateConstraintsSatisfied(js);
                 }
-                changed |= js.setConnectivityConstraintSatisfied(mNetworkConnected);
-                changed |= js.setUnmeteredConstraintSatisfied(mNetworkUnmetered);
             }
             if (changed) {
                 mStateChangedListener.onControllerStateChanged();
@@ -122,9 +133,11 @@
     /**
      * We know the network has just come up. We want to run any jobs that are ready.
      */
+    @Override
     public synchronized void onNetworkActive() {
         synchronized (mLock) {
-            for (JobStatus js : mTrackedJobs) {
+            for (int i = 0; i < mTrackedJobs.size(); i++) {
+                final JobStatus js = mTrackedJobs.get(i);
                 if (js.isReady()) {
                     if (DEBUG) {
                         Slog.d(TAG, "Running " + js + " due to network activity.");
@@ -135,61 +148,44 @@
         }
     }
 
-    class ConnectivityChangedReceiver extends BroadcastReceiver {
-        /**
-         * We'll receive connectivity changes for each user here, which we process independently.
-         * We are only interested in the active network here. We're only interested in the active
-         * network, b/c the end result of this will be for apps to try to hit the network.
-         * @param context The Context in which the receiver is running.
-         * @param intent The Intent being received.
-         */
-        // TODO: Test whether this will be called twice for each user.
+    private BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (DEBUG) {
-                Slog.d(TAG, "Received connectivity event: " + intent.getAction() + " u"
-                        + context.getUserId());
-            }
-            final String action = intent.getAction();
-            if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
-                final int networkType =
-                        intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE,
-                                ConnectivityManager.TYPE_NONE);
-                // Connectivity manager for THIS context - important!
-                final ConnectivityManager connManager = (ConnectivityManager)
-                        context.getSystemService(Context.CONNECTIVITY_SERVICE);
-                final NetworkInfo activeNetwork = connManager.getActiveNetworkInfo();
-                final int userid = context.getUserId();
-                // This broadcast gets sent a lot, only update if the active network has changed.
-                if (activeNetwork == null) {
-                    mNetworkUnmetered = false;
-                    mNetworkConnected = false;
-                    updateTrackedJobs(userid);
-                } else if (activeNetwork.getType() == networkType) {
-                    mNetworkUnmetered = false;
-                    mNetworkConnected = !intent.getBooleanExtra(
-                            ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
-                    if (mNetworkConnected) {  // No point making the call if we know there's no conn.
-                        mNetworkUnmetered = !connManager.isActiveNetworkMetered();
-                    }
-                    updateTrackedJobs(userid);
-                }
-            } else {
-                if (DEBUG) {
-                    Slog.d(TAG, "Unrecognised action in intent: " + action);
-                }
-            }
+            updateTrackedJobs(-1);
+        }
+    };
+
+    private INetworkPolicyListener mNetPolicyListener = new INetworkPolicyListener.Stub() {
+        @Override
+        public void onUidRulesChanged(int uid, int uidRules) {
+            updateTrackedJobs(uid);
+        }
+
+        @Override
+        public void onMeteredIfacesChanged(String[] meteredIfaces) {
+            updateTrackedJobs(-1);
+        }
+
+        @Override
+        public void onRestrictBackgroundChanged(boolean restrictBackground) {
+            updateTrackedJobs(-1);
+        }
+
+        @Override
+        public void onRestrictBackgroundWhitelistChanged(int uid, boolean whitelisted) {
+            updateTrackedJobs(uid);
         }
     };
 
     @Override
     public void dumpControllerStateLocked(PrintWriter pw) {
         pw.println("Conn.");
-        pw.println("connected: " + mNetworkConnected + " unmetered: " + mNetworkUnmetered);
-        for (JobStatus js: mTrackedJobs) {
+        for (int i = 0; i < mTrackedJobs.size(); i++) {
+            final JobStatus js = mTrackedJobs.get(i);
             pw.println(String.valueOf(js.getJobId() + "," + js.getUid())
                     + ": C=" + js.hasConnectivityConstraint()
-                    + ", UM=" + js.hasUnmeteredConstraint());
+                    + ", UM=" + js.hasUnmeteredConstraint()
+                    + ", NR=" + js.hasNotRoamingConstraint());
         }
     }
 }
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index dd70758..9ab4386 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -29,7 +29,6 @@
 import android.util.TimeUtils;
 
 import java.io.PrintWriter;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Uniquely identifies a job internally.
@@ -55,6 +54,7 @@
     static final int CONSTRAINT_APP_NOT_IDLE = 1<<6;
     static final int CONSTRAINT_CONTENT_TRIGGER = 1<<7;
     static final int CONSTRAINT_DEVICE_NOT_DOZING = 1<<8;
+    static final int CONSTRAINT_NOT_ROAMING = 1<<9;
 
     // Soft override: ignore constraints like time that don't affect API availability
     public static final int OVERRIDE_SOFT = 1;
@@ -176,6 +176,9 @@
         if (job.getNetworkType() == JobInfo.NETWORK_TYPE_UNMETERED) {
             requiredConstraints |= CONSTRAINT_UNMETERED;
         }
+        if (job.getNetworkType() == JobInfo.NETWORK_TYPE_NOT_ROAMING) {
+            requiredConstraints |= CONSTRAINT_NOT_ROAMING;
+        }
         if (job.isRequireCharging()) {
             requiredConstraints |= CONSTRAINT_CHARGING;
         }
@@ -312,6 +315,10 @@
         return (requiredConstraints&CONSTRAINT_UNMETERED) != 0;
     }
 
+    public boolean hasNotRoamingConstraint() {
+        return (requiredConstraints&CONSTRAINT_NOT_ROAMING) != 0;
+    }
+
     public boolean hasChargingConstraint() {
         return (requiredConstraints&CONSTRAINT_CHARGING) != 0;
     }
@@ -376,12 +383,16 @@
         return setConstraintSatisfied(CONSTRAINT_IDLE, state);
     }
 
+    boolean setConnectivityConstraintSatisfied(boolean state) {
+        return setConstraintSatisfied(CONSTRAINT_CONNECTIVITY, state);
+    }
+
     boolean setUnmeteredConstraintSatisfied(boolean state) {
         return setConstraintSatisfied(CONSTRAINT_UNMETERED, state);
     }
 
-    boolean setConnectivityConstraintSatisfied(boolean state) {
-        return setConstraintSatisfied(CONSTRAINT_CONNECTIVITY, state);
+    boolean setNotRoamingConstraintSatisfied(boolean state) {
+        return setConstraintSatisfied(CONSTRAINT_NOT_ROAMING, state);
     }
 
     boolean setAppNotIdleConstraintSatisfied(boolean state) {
@@ -425,7 +436,7 @@
 
     static final int CONSTRAINTS_OF_INTEREST =
             CONSTRAINT_CHARGING | CONSTRAINT_TIMING_DELAY |
-            CONSTRAINT_CONNECTIVITY | CONSTRAINT_UNMETERED |
+            CONSTRAINT_CONNECTIVITY | CONSTRAINT_UNMETERED | CONSTRAINT_NOT_ROAMING |
             CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER;
 
     // Soft override covers all non-"functional" constraints
@@ -517,11 +528,14 @@
         if ((constraints&CONSTRAINT_IDLE) != 0) {
             pw.print(" IDLE");
         }
+        if ((constraints&CONSTRAINT_CONNECTIVITY) != 0) {
+            pw.print(" CONNECTIVITY");
+        }
         if ((constraints&CONSTRAINT_UNMETERED) != 0) {
             pw.print(" UNMETERED");
         }
-        if ((constraints&CONSTRAINT_CONNECTIVITY) != 0) {
-            pw.print(" CONNECTIVITY");
+        if ((constraints&CONSTRAINT_NOT_ROAMING) != 0) {
+            pw.print(" NOT_ROAMING");
         }
         if ((constraints&CONSTRAINT_APP_NOT_IDLE) != 0) {
             pw.print(" APP_NOT_IDLE");
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index fc412e3..4a8539a 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -209,7 +209,9 @@
                 throw new RuntimeException("Problem setting firewall rules", e);
             }
 
-            mConnService.sendConnectedBroadcast(augmentNetworkInfo(egressInfo));
+            final NetworkInfo clone = new NetworkInfo(egressInfo);
+            augmentNetworkInfo(clone);
+            mConnService.sendConnectedBroadcast(clone);
         }
     }
 
@@ -320,13 +322,11 @@
         }
     }
 
-    public NetworkInfo augmentNetworkInfo(NetworkInfo info) {
+    public void augmentNetworkInfo(NetworkInfo info) {
         if (info.isConnected()) {
             final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
-            info = new NetworkInfo(info);
             info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null);
         }
-        return info;
     }
 
     private void showNotification(int titleRes, int iconRes) {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 433c056..43f47fa 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -2148,6 +2148,10 @@
 
     @Override
     public boolean isNetworkMetered(NetworkState state) {
+        if (state.networkInfo == null) {
+            return false;
+        }
+
         final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state);
 
         // roaming networks are always considered metered
@@ -2163,10 +2167,6 @@
         if (policy != null) {
             return policy.metered;
         } else {
-            if (state.networkInfo == null) {
-                return false;
-            }
-
             final int type = state.networkInfo.getType();
             if (isNetworkTypeMobile(type) || type == TYPE_WIMAX) {
                 return true;
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index e13ee13..b389cf5 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -1557,7 +1557,7 @@
         }
 
         @Override
-        public void startRecording(IBinder sessionToken, @Nullable Uri programHint, int userId) {
+        public void startRecording(IBinder sessionToken, @Nullable Uri programUri, int userId) {
             final int callingUid = Binder.getCallingUid();
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
                     userId, "startRecording");
@@ -1566,7 +1566,7 @@
                 synchronized (mLock) {
                     try {
                         getSessionLocked(sessionToken, callingUid, resolvedUserId).startRecording(
-                                programHint);
+                                programUri);
                     } catch (RemoteException | SessionNotFoundException e) {
                         Slog.e(TAG, "error in startRecording", e);
                     }