Merge "Call AgingHelper.onNotificationRemoved in Assistant"
diff --git a/Android.bp b/Android.bp
index 980aa04..7e97e66 100644
--- a/Android.bp
+++ b/Android.bp
@@ -502,6 +502,7 @@
         "media/java/android/media/session/IOnMediaKeyListener.aidl",
         "media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl",
         "media/java/android/media/session/ISession.aidl",
+        "media/java/android/media/session/ISession2TokensListener.aidl",
         "media/java/android/media/session/ISessionCallback.aidl",
         "media/java/android/media/session/ISessionController.aidl",
         "media/java/android/media/session/ISessionControllerCallback.aidl",
diff --git a/api/current.txt b/api/current.txt
index 100052e..80fea7b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -26099,6 +26099,25 @@
     method public android.os.Bundle getResultData();
   }
 
+  public final class Session2CommandGroup implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.util.Set<android.media.Session2Command> getCommands();
+    method public boolean hasCommand(android.media.Session2Command);
+    method public boolean hasCommand(int);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.media.Session2CommandGroup> CREATOR;
+  }
+
+  public static final class Session2CommandGroup.Builder {
+    ctor public Session2CommandGroup.Builder();
+    ctor public Session2CommandGroup.Builder(android.media.Session2CommandGroup);
+    method public android.media.Session2CommandGroup.Builder addCommand(android.media.Session2Command);
+    method public android.media.Session2CommandGroup.Builder addCommand(int);
+    method public android.media.Session2CommandGroup build();
+    method public android.media.Session2CommandGroup.Builder removeCommand(android.media.Session2Command);
+    method public android.media.Session2CommandGroup.Builder removeCommand(int);
+  }
+
   public class SoundPool {
     ctor public deprecated SoundPool(int, int, int);
     method public final void autoPause();
@@ -29922,12 +29941,16 @@
     method public android.net.NetworkSpecifier build();
     method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setDiscoverySession(android.net.wifi.aware.DiscoverySession);
     method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setPeerHandle(android.net.wifi.aware.PeerHandle);
+    method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setPort(int);
     method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setPskPassphrase(java.lang.String);
+    method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setTransportProtocol(int);
   }
 
   public final class WifiAwareNetworkInfo implements android.os.Parcelable android.net.TransportInfo {
     method public int describeContents();
     method public java.net.Inet6Address getPeerIpv6Addr();
+    method public int getPort();
+    method public int getTransportProtocol();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.net.wifi.aware.WifiAwareNetworkInfo> CREATOR;
   }
@@ -44313,6 +44336,7 @@
     method public int describeContents();
     method public int getCdmaDbm();
     method public int getCdmaEcio();
+    method public java.util.List<android.telephony.CellSignalStrength> getCellSignalStrengths();
     method public int getEvdoDbm();
     method public int getEvdoEcio();
     method public int getEvdoSnr();
@@ -44755,6 +44779,9 @@
   public static abstract class TelephonyManager.CellInfoCallback {
     ctor public TelephonyManager.CellInfoCallback();
     method public abstract void onCellInfo(java.util.List<android.telephony.CellInfo>);
+    method public void onError(int, java.lang.Throwable);
+    field public static final int ERROR_MODEM_ERROR = 2; // 0x2
+    field public static final int ERROR_TIMEOUT = 1; // 0x1
   }
 
   public static abstract class TelephonyManager.UssdResponseCallback {
diff --git a/api/system-current.txt b/api/system-current.txt
index a4b3978..2428b90 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -199,6 +199,7 @@
   }
 
   public static final class R.attr {
+    field public static final int inheritShowWhenLocked = 16844194; // 0x10105a2
     field public static final int isVrOnly = 16844152; // 0x1010578
     field public static final int requiredSystemPropertyName = 16844133; // 0x1010565
     field public static final int requiredSystemPropertyValue = 16844134; // 0x1010566
@@ -258,6 +259,7 @@
     method public boolean convertToTranslucent(android.app.Activity.TranslucentConversionListener, android.app.ActivityOptions);
     method public deprecated boolean isBackgroundVisibleBehind();
     method public deprecated void onBackgroundVisibleBehindChanged(boolean);
+    method public void setInheritShowWhenLocked(boolean);
   }
 
   public static abstract interface Activity.TranslucentConversionListener {
@@ -1299,6 +1301,26 @@
 
 }
 
+package android.content.om {
+
+  public final class OverlayInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public boolean isEnabled();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.content.om.OverlayInfo> CREATOR;
+    field public final java.lang.String category;
+    field public final java.lang.String packageName;
+    field public final java.lang.String targetPackageName;
+    field public final int userId;
+  }
+
+  public class OverlayManager {
+    method public java.util.List<android.content.om.OverlayInfo> getOverlayInfosForTarget(java.lang.String, int);
+    method public boolean setEnabledExclusiveInCategory(java.lang.String, int);
+  }
+
+}
+
 package android.content.pm {
 
   public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
@@ -4858,6 +4880,14 @@
     field public static final android.os.Parcelable.Creator<android.permission.RuntimePermissionPresentationInfo> CREATOR;
   }
 
+  public final class RuntimePermissionUsageInfo implements android.os.Parcelable {
+    ctor public RuntimePermissionUsageInfo(java.lang.CharSequence, int);
+    method public int describeContents();
+    method public java.lang.CharSequence getName();
+    method public int getAppAccessCount();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.permission.RuntimePermissionUsageInfo> CREATOR;
+  }
 }
 
 package android.permissionpresenterservice {
@@ -4979,6 +5009,7 @@
     method public static void removeOnPropertyChangedListener(android.provider.DeviceConfig.OnPropertyChangedListener);
     method public static void resetToDefaults(int, java.lang.String);
     method public static boolean setProperty(java.lang.String, java.lang.String, java.lang.String, boolean);
+    field public static final java.lang.String NAMESPACE_INPUT_NATIVE_BOOT = "input_native_boot";
   }
 
   public static abstract interface DeviceConfig.OnPropertyChangedListener {
@@ -5662,7 +5693,7 @@
     ctor public NotificationAssistantService();
     method public final void adjustNotification(android.service.notification.Adjustment);
     method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
-    method public void onActionClicked(java.lang.String, android.app.Notification.Action, int);
+    method public void onActionInvoked(java.lang.String, android.app.Notification.Action, int);
     method public final android.os.IBinder onBind(android.content.Intent);
     method public void onNotificationDirectReplied(java.lang.String);
     method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
diff --git a/api/test-current.txt b/api/test-current.txt
index 575875d..6d40e69 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -28,6 +28,7 @@
 
   public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
     method public void onMovedToDisplay(int, android.content.res.Configuration);
+    method public void setInheritShowWhenLocked(boolean);
   }
 
   public class ActivityManager {
@@ -219,6 +220,11 @@
     method public abstract void onOpActiveChanged(int, int, java.lang.String, boolean);
   }
 
+  public final class NotificationChannel implements android.os.Parcelable {
+    method public boolean isImportanceLockedByOEM();
+    method public void setImportanceLockedByOEM(boolean);
+  }
+
   public final class NotificationChannelGroup implements android.os.Parcelable {
     method public int getUserLockedFields();
     method public void lockFields(int);
@@ -1246,6 +1252,71 @@
 
 }
 
+package android.service.autofill.augmented {
+
+  public abstract class AugmentedAutofillService extends android.app.Service {
+    ctor public AugmentedAutofillService();
+    method protected final void dump(java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
+    method protected void dump(java.io.PrintWriter, java.lang.String[]);
+    method public void onFillRequest(android.service.autofill.augmented.FillRequest, android.os.CancellationSignal, android.service.autofill.augmented.FillController, android.service.autofill.augmented.FillCallback);
+    field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.augmented.AugmentedAutofillService";
+  }
+
+  public final class FillCallback {
+    method public void onSuccess(android.service.autofill.augmented.FillResponse);
+  }
+
+  public final class FillController {
+    method public void autofill(java.util.List<android.util.Pair<android.view.autofill.AutofillId, android.view.autofill.AutofillValue>>);
+  }
+
+  public final class FillRequest {
+    method public android.content.ComponentName getActivityComponent();
+    method public android.view.autofill.AutofillId getFocusedId();
+    method public android.view.autofill.AutofillValue getFocusedValue();
+    method public android.service.autofill.augmented.PresentationParams getPresentationParams();
+    method public int getTaskId();
+  }
+
+  public final class FillResponse implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.service.autofill.augmented.FillResponse> CREATOR;
+  }
+
+  public static final class FillResponse.Builder {
+    ctor public FillResponse.Builder();
+    method public android.service.autofill.augmented.FillResponse build();
+    method public android.service.autofill.augmented.FillResponse.Builder setFillWindow(android.service.autofill.augmented.FillWindow);
+    method public android.service.autofill.augmented.FillResponse.Builder setIgnoredIds(java.util.List<android.view.autofill.AutofillId>);
+  }
+
+  public final class FillWindow implements java.lang.AutoCloseable {
+    ctor public FillWindow();
+    method public void destroy();
+    method public boolean update(android.service.autofill.augmented.PresentationParams.Area, android.view.View, long);
+    field public static final long FLAG_METADATA_ADDRESS = 1L; // 0x1L
+  }
+
+  public abstract class PresentationParams {
+    method public int getFlags();
+    method public android.service.autofill.augmented.PresentationParams.Area getFullArea();
+    method public android.service.autofill.augmented.PresentationParams.Area getSuggestionArea();
+    field public static final int FLAG_HINT_GRAVITY_BOTTOM = 2; // 0x2
+    field public static final int FLAG_HINT_GRAVITY_LEFT = 4; // 0x4
+    field public static final int FLAG_HINT_GRAVITY_RIGHT = 8; // 0x8
+    field public static final int FLAG_HINT_GRAVITY_TOP = 1; // 0x1
+    field public static final int FLAG_HOST_IME = 16; // 0x10
+    field public static final int FLAG_HOST_SYSTEM = 32; // 0x20
+  }
+
+  public static abstract class PresentationParams.Area {
+    method public android.graphics.Rect getBounds();
+    method public android.service.autofill.augmented.PresentationParams.Area getSubArea(android.graphics.Rect);
+  }
+
+}
+
 package android.service.notification {
 
   public final class Adjustment implements android.os.Parcelable {
@@ -1275,7 +1346,7 @@
     ctor public NotificationAssistantService();
     method public final void adjustNotification(android.service.notification.Adjustment);
     method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
-    method public void onActionClicked(java.lang.String, android.app.Notification.Action, int);
+    method public void onActionInvoked(java.lang.String, android.app.Notification.Action, int);
     method public final android.os.IBinder onBind(android.content.Intent);
     method public void onNotificationDirectReplied(java.lang.String);
     method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
@@ -1847,6 +1918,10 @@
     ctor public AutofillId(android.view.autofill.AutofillId, int);
   }
 
+  public final class AutofillManager {
+    field public static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 120000; // 0x1d4c0
+  }
+
 }
 
 package android.view.inputmethod {
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index f347867..860e40d 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -25,7 +25,9 @@
 import "frameworks/base/core/proto/android/app/settings_enums.proto";
 import "frameworks/base/core/proto/android/app/job/enums.proto";
 import "frameworks/base/core/proto/android/bluetooth/enums.proto";
+import "frameworks/base/core/proto/android/net/networkcapabilities.proto";
 import "frameworks/base/core/proto/android/os/enums.proto";
+import "frameworks/base/core/proto/android/server/connectivity/data_stall_event.proto";
 import "frameworks/base/core/proto/android/server/enums.proto";
 import "frameworks/base/core/proto/android/server/location/enums.proto";
 import "frameworks/base/core/proto/android/service/procstats_enum.proto";
@@ -172,11 +174,13 @@
         WifiEnabledStateChanged wifi_enabled_state_changed = 113;
         WifiRunningStateChanged wifi_running_state_changed = 114;
         AppCompacted app_compacted = 115;
-        NetworkDnsEventReported network_dns_event_Reported = 116;
+        NetworkDnsEventReported network_dns_event_reported = 116;
         DocsUIPickerLaunchedFromReported docs_ui_picker_launched_from_reported = 117;
         DocsUIPickResultReported docs_ui_pick_result_reported = 118;
         DocsUISearchModeReported docs_ui_search_mode_reported = 119;
         DocsUISearchTypeReported docs_ui_search_type_reported = 120;
+        DataStallEvent data_stall_event = 121;
+        RescuePartyResetReported rescue_party_reset_reported = 122;
     }
 
     // Pulled events will start at field 10000.
@@ -3798,7 +3802,7 @@
     //bionic/libc/include/netdb.h
     //system/netd/resolv/include/netd_resolv/resolv.h
     enum ReturnCode {
-        EAI_NOERR = 0;
+        EAI_NO_ERR = 0;
         EAI_ADDRFAMILY = 1;
         EAI_AGAIN = 2;
         EAI_BADFLAGS = 3;
@@ -3821,3 +3825,37 @@
     // The latency period(in microseconds) it took for this DNS lookup to complete.
     optional int32 latency_micros = 3;
 }
+
+/**
+ * Logs when a data stall event occurs.
+ *
+ * Log from:
+ *     frameworks/base/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+ */
+message DataStallEvent {
+    // Data stall evaluation type.
+    // See frameworks/base/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+    // Refer to the definition of DATA_STALL_EVALUATION_TYPE_*.
+    optional int32 evaluation_type = 1;
+    // See definition in data_stall_event.proto.
+    optional com.android.server.connectivity.ProbeResult validation_result = 2;
+    // See definition in data_stall_event.proto.
+    optional android.net.NetworkCapabilitiesProto.Transport network_type = 3;
+    // See definition in data_stall_event.proto.
+    optional com.android.server.connectivity.WifiData wifi_info = 4 [(log_mode) = MODE_BYTES];
+    // See definition in data_stall_event.proto.
+    optional com.android.server.connectivity.CellularData cell_info = 5 [(log_mode) = MODE_BYTES];
+    // See definition in data_stall_event.proto.
+    optional com.android.server.connectivity.DnsEvent dns_event = 6 [(log_mode) = MODE_BYTES];
+}
+
+/*
+ * Logs when RescueParty resets some set of experiment flags.
+ *
+ * Logged from:
+ *     frameworks/base/services/core/java/com/android/server/RescueParty.java
+ */
+message RescuePartyResetReported {
+    // The rescue level of this reset. A value of 0 indicates missing or unknown level information.
+    optional int32 rescue_level = 1;
+}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index c89848e..98c5a0fb 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1023,7 +1023,9 @@
      *
      * @return The content capture manager
      */
-    @NonNull private ContentCaptureManager getContentCaptureManager() {
+    @Nullable private ContentCaptureManager getContentCaptureManager() {
+        // ContextCapture disabled for system apps
+        if (getApplicationInfo().isSystemApp()) return null;
         if (mContentCaptureManager == null) {
             mContentCaptureManager = getSystemService(ContentCaptureManager.class);
         }
@@ -8289,6 +8291,33 @@
     }
 
     /**
+     * Specifies whether this {@link Activity} should be shown on top of the lock screen whenever
+     * the lockscreen is up and this activity has another activity behind it with the showWhenLock
+     * attribute set. That is, this activity is only visible on the lock screen if there is another
+     * activity with the showWhenLock attribute visible at the same time on the lock screen. A use
+     * case for this is permission dialogs, that should only be visible on the lock screen if their
+     * requesting activity is also visible. This value can be set as a manifest attribute using
+     * android.R.attr#inheritShowWhenLocked.
+     *
+     * @param inheritShowWhenLocked {@code true} to show the {@link Activity} on top of the lock
+     *                              screen when this activity has another activity behind it with
+     *                              the showWhenLock attribute set; {@code false} otherwise.
+     * @see #setShowWhenLocked(boolean)
+     * See android.R.attr#inheritShowWhenLocked
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public void setInheritShowWhenLocked(boolean inheritShowWhenLocked) {
+        try {
+            ActivityTaskManager.getService().setInheritShowWhenLocked(
+                    mToken, inheritShowWhenLocked);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to call setInheritShowWhenLocked", e);
+        }
+    }
+
+    /**
      * Specifies whether the screen should be turned on when the {@link Activity} is resumed.
      * Normally an activity will be transitioned to the stopped state if it is started while the
      * screen if off, but with this flag set the activity will cause the screen to turn on if the
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index dd87dc3..cc6c999 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -419,6 +419,7 @@
     void updateLockTaskFeatures(int userId, int flags);
 
     void setShowWhenLocked(in IBinder token, boolean showWhenLocked);
+    void setInheritShowWhenLocked(in IBinder token, boolean setInheritShownWhenLocked);
     void setTurnScreenOn(in IBinder token, boolean turnScreenOn);
 
     /**
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 41ceaaf..950e9aa 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -19,6 +19,7 @@
 
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.app.NotificationManager.Importance;
 import android.content.ContentResolver;
@@ -168,6 +169,7 @@
     // If this is a blockable system notification channel.
     private boolean mBlockableSystem = false;
     private boolean mAllowAppOverlay = DEFAULT_ALLOW_APP_OVERLAY;
+    private boolean mImportanceLockedByOEM;
 
     /**
      * Creates a notification channel.
@@ -230,6 +232,7 @@
         mLightColor = in.readInt();
         mBlockableSystem = in.readBoolean();
         mAllowAppOverlay = in.readBoolean();
+        mImportanceLockedByOEM = in.readBoolean();
     }
 
     @Override
@@ -283,6 +286,7 @@
         dest.writeInt(mLightColor);
         dest.writeBoolean(mBlockableSystem);
         dest.writeBoolean(mAllowAppOverlay);
+        dest.writeBoolean(mImportanceLockedByOEM);
     }
 
     /**
@@ -649,6 +653,22 @@
     }
 
     /**
+     * @hide
+     */
+    @TestApi
+    public void setImportanceLockedByOEM(boolean locked) {
+        mImportanceLockedByOEM = locked;
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    public boolean isImportanceLockedByOEM() {
+        return mImportanceLockedByOEM;
+    }
+
+    /**
      * Returns whether the user has chosen the importance of this channel, either to affirm the
      * initial selection from the app, or changed it to be higher or lower.
      * @see #getImportance()
@@ -952,25 +972,26 @@
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         NotificationChannel that = (NotificationChannel) o;
-        return getImportance() == that.getImportance() &&
-                mBypassDnd == that.mBypassDnd &&
-                getLockscreenVisibility() == that.getLockscreenVisibility() &&
-                mLights == that.mLights &&
-                getLightColor() == that.getLightColor() &&
-                getUserLockedFields() == that.getUserLockedFields() &&
-                isFgServiceShown() == that.isFgServiceShown() &&
-                mVibrationEnabled == that.mVibrationEnabled &&
-                mShowBadge == that.mShowBadge &&
-                isDeleted() == that.isDeleted() &&
-                isBlockableSystem() == that.isBlockableSystem() &&
-                mAllowAppOverlay == that.mAllowAppOverlay &&
-                Objects.equals(getId(), that.getId()) &&
-                Objects.equals(getName(), that.getName()) &&
-                Objects.equals(mDesc, that.mDesc) &&
-                Objects.equals(getSound(), that.getSound()) &&
-                Arrays.equals(mVibration, that.mVibration) &&
-                Objects.equals(getGroup(), that.getGroup()) &&
-                Objects.equals(getAudioAttributes(), that.getAudioAttributes());
+        return getImportance() == that.getImportance()
+                && mBypassDnd == that.mBypassDnd
+                && getLockscreenVisibility() == that.getLockscreenVisibility()
+                && mLights == that.mLights
+                && getLightColor() == that.getLightColor()
+                && getUserLockedFields() == that.getUserLockedFields()
+                && isFgServiceShown() == that.isFgServiceShown()
+                && mVibrationEnabled == that.mVibrationEnabled
+                && mShowBadge == that.mShowBadge
+                && isDeleted() == that.isDeleted()
+                && isBlockableSystem() == that.isBlockableSystem()
+                && mAllowAppOverlay == that.mAllowAppOverlay
+                && Objects.equals(getId(), that.getId())
+                && Objects.equals(getName(), that.getName())
+                && Objects.equals(mDesc, that.mDesc)
+                && Objects.equals(getSound(), that.getSound())
+                && Arrays.equals(mVibration, that.mVibration)
+                && Objects.equals(getGroup(), that.getGroup())
+                && Objects.equals(getAudioAttributes(), that.getAudioAttributes())
+                && mImportanceLockedByOEM == that.mImportanceLockedByOEM;
     }
 
     @Override
@@ -979,7 +1000,8 @@
                 getLockscreenVisibility(), getSound(), mLights, getLightColor(),
                 getUserLockedFields(),
                 isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(),
-                getAudioAttributes(), isBlockableSystem(), mAllowAppOverlay);
+                getAudioAttributes(), isBlockableSystem(), mAllowAppOverlay,
+                mImportanceLockedByOEM);
         result = 31 * result + Arrays.hashCode(mVibration);
         return result;
     }
@@ -1007,6 +1029,7 @@
                 + ", mAudioAttributes=" + mAudioAttributes
                 + ", mBlockableSystem=" + mBlockableSystem
                 + ", mAllowAppOverlay=" + mAllowAppOverlay
+                + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
                 + '}';
         pw.println(prefix + output);
     }
@@ -1033,6 +1056,7 @@
                 + ", mAudioAttributes=" + mAudioAttributes
                 + ", mBlockableSystem=" + mBlockableSystem
                 + ", mAllowAppOverlay=" + mAllowAppOverlay
+                + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
                 + '}';
     }
 
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index d827f6c..3adafd72 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -45,6 +45,8 @@
 import android.content.Context;
 import android.content.IRestrictionsManager;
 import android.content.RestrictionsManager;
+import android.content.om.IOverlayManager;
+import android.content.om.OverlayManager;
 import android.content.pm.CrossProfileApps;
 import android.content.pm.ICrossProfileApps;
 import android.content.pm.IShortcutService;
@@ -1053,6 +1055,14 @@
                 return new ShortcutManager(ctx, IShortcutService.Stub.asInterface(b));
             }});
 
+        registerService(Context.OVERLAY_SERVICE, OverlayManager.class,
+                new CachedServiceFetcher<OverlayManager>() {
+            @Override
+            public OverlayManager createService(ContextImpl ctx) throws ServiceNotFoundException {
+                IBinder b = ServiceManager.getServiceOrThrow(Context.OVERLAY_SERVICE);
+                return new OverlayManager(ctx, IOverlayManager.Stub.asInterface(b));
+            }});
+
         registerService(Context.NETWORK_WATCHLIST_SERVICE, NetworkWatchlistManager.class,
                 new CachedServiceFetcher<NetworkWatchlistManager>() {
                     @Override
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index dd55003..1989f06 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -18,8 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
-import android.os.Build;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -32,8 +31,10 @@
  *
  * @hide
  */
+@SystemApi
 public final class OverlayInfo implements Parcelable {
 
+    /** @hide */
     @IntDef(prefix = "STATE_", value = {
             STATE_UNKNOWN,
             STATE_MISSING_TARGET,
@@ -44,6 +45,7 @@
             STATE_TARGET_UPGRADING,
             STATE_OVERLAY_UPGRADING,
     })
+    /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     public @interface State {}
 
@@ -52,17 +54,23 @@
      * objects exposed outside the {@link
      * com.android.server.om.OverlayManagerService} should never have this
      * state.
+     *
+     * @hide
      */
     public static final int STATE_UNKNOWN = -1;
 
     /**
      * The target package of the overlay is not installed. The overlay cannot be enabled.
+     *
+     * @hide
      */
     public static final int STATE_MISSING_TARGET = 0;
 
     /**
      * Creation of idmap file failed (e.g. no matching resources). The overlay
      * cannot be enabled.
+     *
+     * @hide
      */
     public static final int STATE_NO_IDMAP = 1;
 
@@ -70,6 +78,7 @@
      * The overlay is currently disabled. It can be enabled.
      *
      * @see IOverlayManager#setEnabled
+     * @hide
      */
     public static final int STATE_DISABLED = 2;
 
@@ -77,18 +86,21 @@
      * The overlay is currently enabled. It can be disabled.
      *
      * @see IOverlayManager#setEnabled
+     * @hide
      */
     public static final int STATE_ENABLED = 3;
 
     /**
      * The target package is currently being upgraded; the state will change
      * once the package installation has finished.
+     * @hide
      */
     public static final int STATE_TARGET_UPGRADING = 4;
 
     /**
      * The overlay package is currently being upgraded; the state will change
      * once the package installation has finished.
+     * @hide
      */
     public static final int STATE_OVERLAY_UPGRADING = 5;
 
@@ -96,6 +108,7 @@
      * The overlay package is currently enabled because it is marked as
      * 'static'. It cannot be disabled but will change state if for instance
      * its target is uninstalled.
+     * @hide
      */
     public static final int STATE_ENABLED_STATIC = 6;
 
@@ -103,40 +116,52 @@
      * Overlay category: theme.
      * <p>
      * Change how Android (including the status bar, dialogs, ...) looks.
+     *
+     * @hide
      */
     public static final String CATEGORY_THEME = "android.theme";
 
     /**
      * Package name of the overlay package
+     *
+     * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
     public final String packageName;
 
     /**
      * Package name of the target package
+     *
+     * @hide
      */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    @SystemApi
     public final String targetPackageName;
 
     /**
      * Category of the overlay package
+     *
+     * @hide
      */
+    @SystemApi
     public final String category;
 
     /**
      * Full path to the base APK for this overlay package
+     * @hide
      */
     public final String baseCodePath;
 
     /**
      * The state of this OverlayInfo as defined by the STATE_* constants in this class.
+     * @hide
      */
-    @UnsupportedAppUsage
     public final @State int state;
 
     /**
      * User handle for which this overlay applies
+     * @hide
      */
+    @SystemApi
     public final int userId;
 
     /**
@@ -161,12 +186,15 @@
      *
      * @param source the source OverlayInfo to base the new instance on
      * @param state the new state for the source OverlayInfo
+     *
+     * @hide
      */
     public OverlayInfo(@NonNull OverlayInfo source, @State int state) {
         this(source.packageName, source.targetPackageName, source.category, source.baseCodePath,
                 state, source.userId, source.priority, source.isStatic);
     }
 
+    /** @hide */
     public OverlayInfo(@NonNull String packageName, @NonNull String targetPackageName,
             @NonNull String category, @NonNull String baseCodePath, int state, int userId,
             int priority, boolean isStatic) {
@@ -181,6 +209,7 @@
         ensureValidState();
     }
 
+    /** @hide */
     public OverlayInfo(Parcel source) {
         packageName = source.readString();
         targetPackageName = source.readString();
@@ -255,8 +284,9 @@
      * Disabled overlay packages are installed but are currently not in use.
      *
      * @return true if the overlay is enabled, else false.
+     * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
     public boolean isEnabled() {
         switch (state) {
             case STATE_ENABLED:
@@ -272,6 +302,7 @@
      * debugging purposes.
      *
      * @return a human readable String representing the state.
+     * @hide
      */
     public static String stateToString(@State int state) {
         switch (state) {
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
new file mode 100644
index 0000000..7a2220bf
--- /dev/null
+++ b/core/java/android/content/om/OverlayManager.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2018 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.content.om;
+
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import java.util.List;
+
+/**
+ * Updates OverlayManager state; gets information about installed overlay packages.
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.OVERLAY_SERVICE)
+public class OverlayManager {
+
+    private final IOverlayManager mService;
+    private final Context mContext;
+
+    /**
+     * Creates a new instance.
+     *
+     * @param context The current context in which to operate.
+     * @param service The backing system service.
+     *
+     * @hide
+     */
+    public OverlayManager(Context context, IOverlayManager service) {
+        mContext = context;
+        mService = service;
+    }
+
+    /** @hide */
+    public OverlayManager(Context context) {
+        this(context, IOverlayManager.Stub.asInterface(
+            ServiceManager.getService(Context.OVERLAY_SERVICE)));
+    }
+    /**
+     * Request that an overlay package is enabled and any other overlay packages with the same
+     * target package and category are disabled.
+     *
+     * @param packageName the name of the overlay package to enable.
+     * @param userId The user for which to change the overlay.
+     * @return true if the system successfully registered the request, false otherwise.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean setEnabledExclusiveInCategory(@Nullable final String packageName,
+            int userId) {
+        try {
+            return mService.setEnabledExclusiveInCategory(packageName, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns information about all overlays for the given target package for
+     * the specified user. The returned list is ordered according to the
+     * overlay priority with the highest priority at the end of the list.
+     *
+     * @param targetPackageName The name of the target package.
+     * @param userId The user to get the OverlayInfos for.
+     * @return A list of OverlayInfo objects; if no overlays exist for the
+     *         requested package, an empty list is returned.
+     *
+     * @hide
+     */
+    @SystemApi
+    public List<OverlayInfo> getOverlayInfosForTarget(@Nullable final String targetPackageName,
+            int userId) {
+        try {
+            return mService.getOverlayInfosForTarget(targetPackageName, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 0edb36c..3cfbe0c 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -505,6 +505,22 @@
      */
     public int flags;
 
+    /**
+     * Bit in {@link #privateFlags} indicating if the activity should be shown when locked in case
+     * an activity behind this can also be shown when locked.
+     * See android.R.attr#inheritShowWhenLocked
+     * @hide
+     */
+    public static final int FLAG_INHERIT_SHOW_WHEN_LOCKED = 0x1;
+
+    /**
+     * Options that have been set in the activity declaration in the manifest.
+     * These include:
+     * {@link #FLAG_INHERIT_SHOW_WHEN_LOCKED}.
+     * @hide
+     */
+    public int privateFlags;
+
     /** @hide */
     @IntDef(prefix = { "SCREEN_ORIENTATION_" }, value = {
             SCREEN_ORIENTATION_UNSET,
@@ -975,6 +991,7 @@
         taskAffinity = orig.taskAffinity;
         targetActivity = orig.targetActivity;
         flags = orig.flags;
+        privateFlags = orig.privateFlags;
         screenOrientation = orig.screenOrientation;
         configChanges = orig.configChanges;
         softInputMode = orig.softInputMode;
@@ -1122,9 +1139,10 @@
                     + " targetActivity=" + targetActivity
                     + " persistableMode=" + persistableModeToString());
         }
-        if (launchMode != 0 || flags != 0 || theme != 0) {
+        if (launchMode != 0 || flags != 0 || privateFlags != 0 || theme != 0) {
             pw.println(prefix + "launchMode=" + launchMode
                     + " flags=0x" + Integer.toHexString(flags)
+                    + " privateFlags=0x" + Integer.toHexString(privateFlags)
                     + " theme=0x" + Integer.toHexString(theme));
         }
         if (screenOrientation != SCREEN_ORIENTATION_UNSPECIFIED
@@ -1178,6 +1196,7 @@
         dest.writeString(targetActivity);
         dest.writeString(launchToken);
         dest.writeInt(flags);
+        dest.writeInt(privateFlags);
         dest.writeInt(screenOrientation);
         dest.writeInt(configChanges);
         dest.writeInt(softInputMode);
@@ -1307,6 +1326,7 @@
         targetActivity = source.readString();
         launchToken = source.readString();
         flags = source.readInt();
+        privateFlags = source.readInt();
         screenOrientation = source.readInt();
         configChanges = source.readInt();
         softInputMode = source.readInt();
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index c984651..0fc50c6 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -4646,6 +4646,9 @@
                 a.info.flags |= ActivityInfo.FLAG_TURN_SCREEN_ON;
             }
 
+            if (sa.getBoolean(R.styleable.AndroidManifestActivity_inheritShowWhenLocked, false)) {
+                a.info.privateFlags |= ActivityInfo.FLAG_INHERIT_SHOW_WHEN_LOCKED;
+            }
         } else {
             a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
             a.info.configChanges = 0;
@@ -5025,6 +5028,7 @@
         info.targetActivity = targetActivity;
         info.configChanges = target.info.configChanges;
         info.flags = target.info.flags;
+        info.privateFlags = target.info.privateFlags;
         info.icon = target.info.icon;
         info.logo = target.info.logo;
         info.banner = target.info.banner;
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index b520d2c..f5d288e 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -16,6 +16,8 @@
 
 package android.hardware.hdmi;
 
+import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH;
+
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
@@ -27,6 +29,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.util.ArrayMap;
 import android.util.Log;
 
@@ -264,6 +267,10 @@
     private final boolean mHasTvDevice;
     // True if we have a logical device of type audio system hosted in the system.
     private final boolean mHasAudioSystemDevice;
+    // True if we have a logical device of type audio system hosted in the system.
+    private final boolean mHasSwitchDevice;
+    // True if it's a switch device.
+    private final boolean mIsSwitchDevice;
 
     /**
      * {@hide} - hide this constructor because it has a parameter of type IHdmiControlService,
@@ -283,6 +290,9 @@
         mHasTvDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_TV);
         mHasPlaybackDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_PLAYBACK);
         mHasAudioSystemDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mHasSwitchDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH);
+        mIsSwitchDevice = SystemProperties.getBoolean(
+            PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false);
     }
 
     private static boolean hasDeviceType(int[] types, int type) {
@@ -319,6 +329,9 @@
                 return mHasPlaybackDevice ? new HdmiPlaybackClient(mService) : null;
             case HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM:
                 return mHasAudioSystemDevice ? new HdmiAudioSystemClient(mService) : null;
+            case HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH:
+                return (mHasSwitchDevice || mIsSwitchDevice)
+                    ? new HdmiSwitchClient(mService) : null;
             default:
                 return null;
         }
@@ -373,6 +386,24 @@
     }
 
     /**
+     * Gets an object that represents an HDMI-CEC logical device of type switch on the system.
+     *
+     * <p>Used to send HDMI control messages to other devices like TV through HDMI bus. It is also
+     * possible to communicate with other logical devices hosted in the same system if the system is
+     * configured to host more than one type of HDMI-CEC logical devices.
+     *
+     * @return {@link HdmiSwitchClient} instance. {@code null} on failure.
+     *
+     * TODO(b/110094868): unhide for Q
+     * @hide
+     */
+    @Nullable
+    @SuppressLint("Doclava125")
+    public HdmiSwitchClient getSwitchClient() {
+        return (HdmiSwitchClient) getClient(HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH);
+    }
+
+    /**
      * Controls standby mode of the system. It will also try to turn on/off the connected devices if
      * necessary.
      *
diff --git a/core/java/android/hardware/hdmi/HdmiSwitchClient.java b/core/java/android/hardware/hdmi/HdmiSwitchClient.java
new file mode 100644
index 0000000..1ac2973
--- /dev/null
+++ b/core/java/android/hardware/hdmi/HdmiSwitchClient.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2018 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.hardware.hdmi;
+
+import android.annotation.NonNull;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * HdmiSwitchClient represents HDMI-CEC logical device of type Switch in the Android system which
+ * acts as switch.
+ *
+ * <p>HdmiSwitchClient has a CEC device type of HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH,
+ * but it is used by all Android TV devices that have multiple HDMI inputs,
+ * even if it is not a "pure" swicth and has another device type like TV or Player.
+ *
+ * @hide
+ * TODO(b/110094868): unhide and add @SystemApi for Q
+ */
+public class HdmiSwitchClient extends HdmiClient {
+
+    private static final String TAG = "HdmiSwitchClient";
+
+    /* package */ HdmiSwitchClient(IHdmiControlService service) {
+        super(service);
+    }
+
+    private static IHdmiControlCallback getCallbackWrapper(final SelectCallback callback) {
+        return new IHdmiControlCallback.Stub() {
+            @Override
+            public void onComplete(int result) {
+                callback.onComplete(result);
+            }
+        };
+    }
+
+    /** @hide */
+    // TODO(b/110094868): unhide for Q
+    @Override
+    public int getDeviceType() {
+        return HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH;
+    }
+
+    /**
+     * Selects a CEC logical device to be a new active source.
+     *
+     * @param logicalAddress logical address of the device to select
+     * @param callback callback to get the result with
+     * @throws {@link IllegalArgumentException} if the {@code callback} is null
+     *
+     * @hide
+     * TODO(b/110094868): unhide and add @SystemApi for Q
+     */
+    public void deviceSelect(int logicalAddress, @NonNull SelectCallback callback) {
+        if (callback == null) {
+            throw new IllegalArgumentException("callback must not be null.");
+        }
+        try {
+            mService.deviceSelect(logicalAddress, getCallbackWrapper(callback));
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to select device: ", e);
+        }
+    }
+
+    /**
+     * Selects a HDMI port to be a new route path.
+     *
+     * @param portId HDMI port to select
+     * @param callback callback to get the result with
+     * @throws {@link IllegalArgumentException} if the {@code callback} is null
+     *
+     * @hide
+     * TODO(b/110094868): unhide and add @SystemApi for Q
+     */
+    public void portSelect(int portId, @NonNull SelectCallback callback) {
+        if (callback == null) {
+            throw new IllegalArgumentException("Callback must not be null");
+        }
+        try {
+            mService.portSelect(portId, getCallbackWrapper(callback));
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to select port: ", e);
+        }
+    }
+
+    /**
+     * Returns all the CEC devices connected to the device.
+     *
+     * <p>This only applies to device with multiple HDMI inputs
+     *
+     * @return list of {@link HdmiDeviceInfo} for connected CEC devices. Empty list is returned if
+     * there is none.
+     *
+     * @hide
+     * TODO(b/110094868): unhide and add @SystemApi for Q
+     */
+    public List<HdmiDeviceInfo> getDeviceList() {
+        try {
+            return mService.getDeviceList();
+        } catch (RemoteException e) {
+            Log.e("TAG", "Failed to call getDeviceList():", e);
+            return Collections.<HdmiDeviceInfo>emptyList();
+        }
+    }
+
+    /**
+     * Callback interface used to get the result of {@link #deviceSelect} or {@link #portSelect}.
+     *
+     * @hide
+     * TODO(b/110094868): unhide and add @SystemApi for Q
+     */
+    public interface SelectCallback {
+
+        /**
+         * Called when the operation is finished.
+         *
+         * @param result the result value of {@link #deviceSelect} or {@link #portSelect}.
+         */
+        void onComplete(int result);
+    }
+}
diff --git a/core/java/android/os/ParcelableException.aidl b/core/java/android/os/ParcelableException.aidl
new file mode 100644
index 0000000..d214922
--- /dev/null
+++ b/core/java/android/os/ParcelableException.aidl
@@ -0,0 +1,18 @@
+/* Copyright 2018, 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.os;
+
+parcelable ParcelableException;
diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl
index 0e18b44..7a7bd83 100644
--- a/core/java/android/permission/IPermissionController.aidl
+++ b/core/java/android/permission/IPermissionController.aidl
@@ -31,4 +31,5 @@
     void revokeRuntimePermission(String packageName, String permissionName);
     void countPermissionApps(in List<String> permissionNames, boolean countOnlyGranted,
             boolean countSystem, in RemoteCallback callback);
+    void getPermissionUsages(boolean countSystem, long numMillis, in RemoteCallback callback);
 }
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index d7332ae..0865b62 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -18,6 +18,7 @@
 
 import static android.permission.PermissionControllerService.SERVICE_INTERFACE;
 
+import static com.android.internal.util.Preconditions.checkArgumentNonnegative;
 import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
 import static com.android.internal.util.Preconditions.checkNotNull;
 
@@ -146,6 +147,20 @@
         void onCountPermissionApps(int numApps);
     }
 
+    /**
+     * Callback for delivering the result of {@link #getPermissionUsages}.
+     *
+     * @hide
+     */
+    public interface OnPermissionUsageResultCallback {
+        /**
+         * The result for {@link #getPermissionUsages}.
+         *
+         * @param users The users list.
+         */
+        void onPermissionUsageResult(@NonNull List<RuntimePermissionUsageInfo> users);
+    }
+
     private final @NonNull Context mContext;
 
     /**
@@ -264,6 +279,28 @@
     }
 
     /**
+     * Count how many apps have used permissions.
+     *
+     * @param countSystem Also count system apps
+     * @param numMillis The number of milliseconds in the past to check for uses
+     * @param executor Executor on which to invoke the callback
+     * @param callback Callback to receive the result
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
+    public void getPermissionUsages(boolean countSystem, long numMillis,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnPermissionUsageResultCallback callback) {
+        checkArgumentNonnegative(numMillis);
+        checkNotNull(executor);
+        checkNotNull(callback);
+
+        sRemoteService.scheduleRequest(new PendingGetPermissionUsagesRequest(sRemoteService,
+                countSystem, numMillis, executor, callback));
+    }
+
+    /**
      * A connection to the remote service
      */
     static final class RemoteService extends
@@ -326,6 +363,7 @@
         private final boolean mDoDryRun;
         private final int mReason;
         private final @NonNull String mCallingPackage;
+        private final @NonNull Executor mExecutor;
         private final @NonNull OnRevokeRuntimePermissionsCallback mCallback;
 
         private final @NonNull RemoteCallback mRemoteCallback;
@@ -341,6 +379,7 @@
             mDoDryRun = doDryRun;
             mReason = reason;
             mCallingPackage = callingPackage;
+            mExecutor = executor;
             mCallback = callback;
 
             mRemoteCallback = new RemoteCallback(result -> executor.execute(() -> {
@@ -375,7 +414,13 @@
 
         @Override
         protected void onTimeout(RemoteService remoteService) {
-            mCallback.onRevokeRuntimePermissions(Collections.emptyMap());
+            long token = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(
+                        () -> mCallback.onRevokeRuntimePermissions(Collections.emptyMap()));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         @Override
@@ -521,4 +566,61 @@
             }
         }
     }
+
+    /**
+     * Request for {@link #getPermissionUsages}
+     */
+    private static final class PendingGetPermissionUsagesRequest extends
+            AbstractRemoteService.PendingRequest<RemoteService, IPermissionController> {
+        private final @NonNull OnPermissionUsageResultCallback mCallback;
+        private final boolean mCountSystem;
+        private final long mNumMillis;
+
+        private final @NonNull RemoteCallback mRemoteCallback;
+
+        private PendingGetPermissionUsagesRequest(@NonNull RemoteService service,
+                boolean countSystem, long numMillis, @NonNull @CallbackExecutor Executor executor,
+                @NonNull OnPermissionUsageResultCallback callback) {
+            super(service);
+
+            mCountSystem = countSystem;
+            mNumMillis = numMillis;
+            mCallback = callback;
+
+            mRemoteCallback = new RemoteCallback(result -> executor.execute(() -> {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    final List<RuntimePermissionUsageInfo> reportedUsers;
+                    List<RuntimePermissionUsageInfo> users = null;
+                    if (result != null) {
+                        users = result.getParcelableArrayList(KEY_RESULT);
+                    } else {
+                        users = Collections.emptyList();
+                    }
+                    reportedUsers = users;
+
+                    callback.onPermissionUsageResult(reportedUsers);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+
+                    finish();
+                }
+            }), null);
+        }
+
+        @Override
+        protected void onTimeout(RemoteService remoteService) {
+            mCallback.onPermissionUsageResult(Collections.emptyList());
+        }
+
+        @Override
+        public void run() {
+            try {
+                getService().getServiceInterface().getPermissionUsages(mCountSystem, mNumMillis,
+                        mRemoteCallback);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error counting permission users", e);
+            }
+        }
+    }
 }
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index f621737..75d61e6 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -17,6 +17,7 @@
 package android.permission;
 
 import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.internal.util.Preconditions.checkArgumentNonnegative;
 import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
 import static com.android.internal.util.Preconditions.checkNotNull;
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
@@ -112,6 +113,17 @@
     public abstract int onCountPermissionApps(@NonNull List<String> permissionNames,
             boolean countOnlyGranted, boolean countSystem);
 
+    /**
+     * Count how many apps have used permissions.
+     *
+     * @param countSystem Also count system apps
+     * @param numMillis The number of milliseconds in the past to check for uses
+     *
+     * @return descriptions of the users of permissions
+     */
+    public abstract @NonNull List<RuntimePermissionUsageInfo>
+            onPermissionUsageResult(boolean countSystem, long numMillis);
+
     @Override
     public final IBinder onBind(Intent intent) {
         return new IPermissionController.Stub() {
@@ -187,6 +199,20 @@
                                 PermissionControllerService.this, permissionNames, countOnlyGranted,
                                 countSystem, callback));
             }
+
+            @Override
+            public void getPermissionUsages(boolean countSystem, long numMillis,
+                    RemoteCallback callback) {
+                checkArgumentNonnegative(numMillis);
+                checkNotNull(callback, "callback");
+
+                enforceCallingPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS, null);
+
+                mHandler.sendMessage(
+                        obtainMessage(PermissionControllerService::getPermissionUsages,
+                                PermissionControllerService.this, countSystem, numMillis,
+                                callback));
+            }
         };
     }
 
@@ -230,4 +256,17 @@
         result.putInt(PermissionControllerManager.KEY_RESULT, numApps);
         callback.sendResult(result);
     }
+
+    private void getPermissionUsages(boolean countSystem, long numMillis,
+            @NonNull RemoteCallback callback) {
+        List<RuntimePermissionUsageInfo> users =
+                onPermissionUsageResult(countSystem, numMillis);
+        if (users != null && !users.isEmpty()) {
+            Bundle result = new Bundle();
+            result.putParcelableList(PermissionControllerManager.KEY_RESULT, users);
+            callback.sendResult(result);
+        } else {
+            callback.sendResult(null);
+        }
+    }
 }
diff --git a/core/java/android/permission/RuntimePermissionUsageInfo.aidl b/core/java/android/permission/RuntimePermissionUsageInfo.aidl
new file mode 100644
index 0000000..88820dd
--- /dev/null
+++ b/core/java/android/permission/RuntimePermissionUsageInfo.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2019, 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.permission;
+
+parcelable RuntimePermissionUsageInfo;
\ No newline at end of file
diff --git a/core/java/android/permission/RuntimePermissionUsageInfo.java b/core/java/android/permission/RuntimePermissionUsageInfo.java
new file mode 100644
index 0000000..af1a1be
--- /dev/null
+++ b/core/java/android/permission/RuntimePermissionUsageInfo.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2019 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.permission;
+
+import static com.android.internal.util.Preconditions.checkArgumentNonnegative;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class contains information about how a runtime permission
+ * is used. A single runtime permission presented to the user may
+ * correspond to multiple platform defined permissions, e.g. the
+ * location permission may control both the coarse and fine platform
+ * permissions.
+ *
+ * @hide
+ */
+@SystemApi
+public final class RuntimePermissionUsageInfo implements Parcelable {
+    private final @NonNull CharSequence mName;
+    private final int mNumUsers;
+
+    /**
+     * Creates a new instance.
+     *
+     * @param name The permission group name.
+     * @param numUsers The number of apps that have used this permission.
+     */
+    public RuntimePermissionUsageInfo(@NonNull CharSequence name, int numUsers) {
+        checkNotNull(name);
+        checkArgumentNonnegative(numUsers);
+
+        mName = name;
+        mNumUsers = numUsers;
+    }
+
+    private RuntimePermissionUsageInfo(Parcel parcel) {
+        this(parcel.readCharSequence(), parcel.readInt());
+    }
+
+    /**
+     * @return The number of apps that accessed this permission
+     */
+    public int getAppAccessCount() {
+        return mNumUsers;
+    }
+
+    /**
+     * Gets the permission group name.
+     *
+     * @return The name.
+     */
+    public @NonNull CharSequence getName() {
+        return mName;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeCharSequence(mName);
+        parcel.writeInt(mNumUsers);
+    }
+
+    public static final Creator<RuntimePermissionUsageInfo> CREATOR =
+            new Creator<RuntimePermissionUsageInfo>() {
+        public RuntimePermissionUsageInfo createFromParcel(Parcel source) {
+            return new RuntimePermissionUsageInfo(source);
+        }
+
+        public RuntimePermissionUsageInfo[] newArray(int size) {
+            return new RuntimePermissionUsageInfo[size];
+        }
+    };
+}
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 4e207ed..c795895 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -48,6 +48,15 @@
      */
     public static final Uri CONTENT_URI = Uri.parse("content://" + Settings.AUTHORITY + "/config");
 
+    /**
+     * Namespace for all input-related features that are used at the native level.
+     * These features are applied at reboot.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String NAMESPACE_INPUT_NATIVE_BOOT = "input_native_boot";
+
     private static final Object sLock = new Object();
     @GuardedBy("sLock")
     private static Map<OnPropertyChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 574ccaf..ef117ea 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1190,7 +1190,8 @@
     /**
      * Activity Action: Show Do Not Disturb access settings.
      * <p>
-     * Users can grant and deny access to Do Not Disturb configuration from here.
+     * Users can grant and deny access to Do Not Disturb configuration from here. Managed
+     * profiles cannot grant Do Not Disturb access.
      * See {@link android.app.NotificationManager#isNotificationPolicyAccessGranted()} for more
      * details.
      * <p>
@@ -13772,6 +13773,7 @@
          * requires_targeting_p            (boolean)
          * max_squeeze_remeasure_attempts  (int)
          * edit_choices_before_sending     (boolean)
+         * show_in_heads_up                (boolean)
          * </pre>
          * @see com.android.systemui.statusbar.policy.SmartReplyConstants
          * @hide
diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
index a9f4034..0116961 100644
--- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
+++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.app.Service;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -58,6 +59,9 @@
  * @hide
  */
 @SystemApi
+@TestApi
+// TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+// in the same package as the test, and that module is compiled with SDK=test_current
 public abstract class AugmentedAutofillService extends Service {
 
     private static final String TAG = AugmentedAutofillService.class.getSimpleName();
@@ -268,7 +272,6 @@
          *
          * <p>Used to make sure the SmartSuggestionsParams is updated when a new fields is focused.
          */
-        // TODO(b/111330312): might not be needed when using IME
         @GuardedBy("mLock")
         private AutofillId mLastShownId;
 
diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java
index 620ec59..bfb4aad 100644
--- a/core/java/android/service/autofill/augmented/FillCallback.java
+++ b/core/java/android/service/autofill/augmented/FillCallback.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy;
 import android.util.Log;
 
@@ -29,6 +30,9 @@
  * @hide
  */
 @SystemApi
+@TestApi
+//TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+//in the same package as the test, and that module is compiled with SDK=test_current
 public final class FillCallback {
 
     private static final String TAG = FillCallback.class.getSimpleName();
diff --git a/core/java/android/service/autofill/augmented/FillController.java b/core/java/android/service/autofill/augmented/FillController.java
index e65cf47..d7bc893 100644
--- a/core/java/android/service/autofill/augmented/FillController.java
+++ b/core/java/android/service/autofill/augmented/FillController.java
@@ -19,6 +19,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.RemoteException;
 import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy;
 import android.util.Log;
@@ -36,6 +37,9 @@
  * @hide
  */
 @SystemApi
+@TestApi
+//TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+//in the same package as the test, and that module is compiled with SDK=test_current
 public final class FillController {
     private static final String TAG = "FillController";
 
diff --git a/core/java/android/service/autofill/augmented/FillRequest.java b/core/java/android/service/autofill/augmented/FillRequest.java
index 57d2cc8..dad5067 100644
--- a/core/java/android/service/autofill/augmented/FillRequest.java
+++ b/core/java/android/service/autofill/augmented/FillRequest.java
@@ -18,6 +18,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.content.ComponentName;
 import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy;
 import android.view.autofill.AutofillId;
@@ -29,6 +30,9 @@
  */
 @SystemApi
 // TODO(b/111330312): pass a requestId and/or sessionId
+@TestApi
+// TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+// in the same package as the test, and that module is compiled with SDK=test_current
 public final class FillRequest {
 
     final AutofillProxy mProxy;
diff --git a/core/java/android/service/autofill/augmented/FillResponse.java b/core/java/android/service/autofill/augmented/FillResponse.java
index 1ecfab4..5285132 100644
--- a/core/java/android/service/autofill/augmented/FillResponse.java
+++ b/core/java/android/service/autofill/augmented/FillResponse.java
@@ -18,6 +18,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.view.autofill.AutofillId;
@@ -30,6 +31,9 @@
  * @hide
  */
 @SystemApi
+@TestApi
+//TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+//in the same package as the test, and that module is compiled with SDK=test_current
 public final class FillResponse implements Parcelable {
 
     private final FillWindow mFillWindow;
@@ -50,6 +54,9 @@
      * @hide
      */
     @SystemApi
+    @TestApi
+    //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+    //in the same package as the test, and that module is compiled with SDK=test_current
     public static final class Builder {
 
         private FillWindow mFillWindow;
diff --git a/core/java/android/service/autofill/augmented/FillWindow.java b/core/java/android/service/autofill/augmented/FillWindow.java
index bad7ddd..33b88e42 100644
--- a/core/java/android/service/autofill/augmented/FillWindow.java
+++ b/core/java/android/service/autofill/augmented/FillWindow.java
@@ -20,6 +20,7 @@
 import android.annotation.LongDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.app.Dialog;
 import android.graphics.Rect;
 import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy;
@@ -61,6 +62,9 @@
  * @hide
  */
 @SystemApi
+@TestApi
+//TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+//in the same package as the test, and that module is compiled with SDK=test_current
 public final class FillWindow implements AutoCloseable {
     private static final String TAG = "FillWindow";
 
diff --git a/core/java/android/service/autofill/augmented/PresentationParams.java b/core/java/android/service/autofill/augmented/PresentationParams.java
index 0124ecc..b60064e 100644
--- a/core/java/android/service/autofill/augmented/PresentationParams.java
+++ b/core/java/android/service/autofill/augmented/PresentationParams.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.graphics.Rect;
 import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy;
 import android.util.DebugUtils;
@@ -48,6 +49,9 @@
  * @hide
  */
 @SystemApi
+@TestApi
+//TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+//in the same package as the test, and that module is compiled with SDK=test_current
 public abstract class PresentationParams {
 
     /**
@@ -147,8 +151,11 @@
      * Area associated with a {@link PresentationParams Smart Suggestions} provider.
      *
      * @hide
-     * */
+     */
     @SystemApi
+    @TestApi
+    //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+    //in the same package as the test, and that module is compiled with SDK=test_current
     public abstract static class Area {
 
         /** @hide */
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index fd2b0fae..63fd563 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -203,7 +203,7 @@
      * @param action the action that is just clicked
      * @param source the source that provided the action, e.g. SOURCE_FROM_APP
      */
-    public void onActionClicked(@NonNull String key, @NonNull Notification.Action action,
+    public void onActionInvoked(@NonNull String key, @NonNull Notification.Action action,
             @Source int source) {
     }
 
@@ -338,7 +338,7 @@
             args.arg1 = key;
             args.arg2 = action;
             args.argi2 = source;
-            mHandler.obtainMessage(MyHandler.MSG_ON_ACTION_CLICKED, args).sendToTarget();
+            mHandler.obtainMessage(MyHandler.MSG_ON_ACTION_INVOKED, args).sendToTarget();
         }
     }
 
@@ -349,7 +349,7 @@
         public static final int MSG_ON_NOTIFICATION_EXPANSION_CHANGED = 4;
         public static final int MSG_ON_NOTIFICATION_DIRECT_REPLY_SENT = 5;
         public static final int MSG_ON_SUGGESTED_REPLY_SENT = 6;
-        public static final int MSG_ON_ACTION_CLICKED = 7;
+        public static final int MSG_ON_ACTION_INVOKED = 7;
 
         public MyHandler(Looper looper) {
             super(looper, null, false);
@@ -419,13 +419,13 @@
                     onSuggestedReplySent(key, reply, source);
                     break;
                 }
-                case MSG_ON_ACTION_CLICKED: {
+                case MSG_ON_ACTION_INVOKED: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     String key = (String) args.arg1;
                     Notification.Action action = (Notification.Action) args.arg2;
                     int source = args.argi2;
                     args.recycle();
-                    onActionClicked(key, action, source);
+                    onActionInvoked(key, action, source);
                     break;
                 }
             }
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index ad10cc9..3a644d4 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -22,16 +22,21 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.metrics.LogMaker;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
 
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
 /**
  * Class encapsulating a Notification. Sent by the NotificationManagerService to clients including
  * the status bar and any {@link android.service.notification.NotificationListenerService}s.
  */
 public class StatusBarNotification implements Parcelable {
+    static final int MAX_LOG_TAG_LENGTH = 36;
+
     @UnsupportedAppUsage
     private final String pkg;
     @UnsupportedAppUsage
@@ -56,6 +61,9 @@
 
     private Context mContext; // used for inflation & icon expansion
 
+    // Contains the basic logging data of the notification.
+    private LogMaker mLogMaker;
+
     /** @hide */
     public StatusBarNotification(String pkg, String opPkg, int id,
             String tag, int uid, int initialPid, Notification notification, UserHandle user,
@@ -381,4 +389,51 @@
         }
         return mContext;
     }
+
+    /**
+     * Returns a LogMaker that contains all basic information of the notification.
+     * @hide
+     */
+    public LogMaker getLogMaker() {
+        if (mLogMaker == null) {
+            // Initialize fields that only change on update (so a new record).
+            mLogMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN)
+                .setPackageName(getPackageName())
+                .addTaggedData(MetricsEvent.NOTIFICATION_ID, getId())
+                .addTaggedData(MetricsEvent.NOTIFICATION_TAG, getTag())
+                .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID, getChannelIdLogTag());
+        }
+        // Reset fields that can change between updates, or are used by multiple logs.
+        return mLogMaker
+            .clearCategory()
+            .clearType()
+            .clearSubtype()
+            .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID, getGroupLogTag())
+            .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_SUMMARY,
+                getNotification().isGroupSummary() ? 1 : 0);
+    }
+
+    private String getGroupLogTag() {
+        return shortenTag(getGroup());
+    }
+
+    private String getChannelIdLogTag() {
+        if (notification.getChannelId() == null) {
+            return null;
+        }
+        return shortenTag(notification.getChannelId());
+    }
+
+    // Make logTag with max size MAX_LOG_TAG_LENGTH.
+    // For shorter or equal tags, returns the tag.
+    // For longer tags, truncate the tag and append a hash of the full tag to
+    // fill the maximum size.
+    private String shortenTag(String logTag) {
+        if (logTag == null || logTag.length() <= MAX_LOG_TAG_LENGTH) {
+            return logTag;
+        }
+        String hash = Integer.toHexString(logTag.hashCode());
+        return logTag.substring(0, MAX_LOG_TAG_LENGTH - hash.length() - 1) + "-"
+            + hash;
+    }
 }
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 96ef8ba..ccd0fc1 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -22,6 +22,7 @@
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.graphics.FrameInfo;
+import android.graphics.Insets;
 import android.hardware.display.DisplayManagerGlobal;
 import android.os.Build;
 import android.os.Handler;
@@ -199,7 +200,7 @@
      * @hide
      */
     private static final String[] CALLBACK_TRACE_TITLES = {
-            "input", "animation", "traversal", "commit"
+            "input", "animation", "insets_animation", "traversal", "commit"
     };
 
     /**
@@ -209,18 +210,33 @@
     public static final int CALLBACK_INPUT = 0;
 
     /**
-     * Callback type: Animation callback.  Runs before traversals.
+     * Callback type: Animation callback.  Runs before {@link #CALLBACK_INSETS_ANIMATION}.
      * @hide
      */
     @TestApi
     public static final int CALLBACK_ANIMATION = 1;
 
     /**
+     * Callback type: Animation callback to handle inset updates. This is separate from
+     * {@link #CALLBACK_ANIMATION} as we need to "gather" all inset animation updates via
+     * {@link WindowInsetsAnimationController#changeInsets} for multiple ongoing animations but then
+     * update the whole view system with a single callback to {@link View#dispatchWindowInsetsAnimationProgress}
+     * that contains all the combined updated insets.
+     * <p>
+     * Both input and animation may change insets, so we need to run this after these callbacks, but
+     * before traversals.
+     * <p>
+     * Runs before traversals.
+     * @hide
+     */
+    public static final int CALLBACK_INSETS_ANIMATION = 2;
+
+    /**
      * Callback type: Traversal callback.  Handles layout and draw.  Runs
      * after all other asynchronous messages have been handled.
      * @hide
      */
-    public static final int CALLBACK_TRAVERSAL = 2;
+    public static final int CALLBACK_TRAVERSAL = 3;
 
     /**
      * Callback type: Commit callback.  Handles post-draw operations for the frame.
@@ -232,7 +248,7 @@
      * to the view hierarchy state) actually took effect.
      * @hide
      */
-    public static final int CALLBACK_COMMIT = 3;
+    public static final int CALLBACK_COMMIT = 4;
 
     private static final int CALLBACK_LAST = CALLBACK_COMMIT;
 
@@ -704,6 +720,7 @@
 
             mFrameInfo.markAnimationsStart();
             doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
+            doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
 
             mFrameInfo.markPerformTraversalsStart();
             doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 7b9f78e..ce71b07 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -45,7 +45,9 @@
  * @hide
  */
 @VisibleForTesting
-public class InsetsAnimationControlImpl implements WindowInsetsAnimationController {
+public class InsetsAnimationControlImpl implements WindowInsetsAnimationController  {
+
+    private final Rect mTmpFrame = new Rect();
 
     private final WindowInsetsAnimationControlListener mListener;
     private final SparseArray<InsetsSourceConsumer> mConsumers;
@@ -61,19 +63,23 @@
     private final InsetsState mInitialInsetsState;
     private final @InsetType int mTypes;
     private final Supplier<SyncRtSurfaceTransactionApplier> mTransactionApplierSupplier;
-
+    private final InsetsController mController;
+    private final WindowInsetsAnimationListener.InsetsAnimation mAnimation;
     private Insets mCurrentInsets;
+    private Insets mPendingInsets;
 
     @VisibleForTesting
     public InsetsAnimationControlImpl(SparseArray<InsetsSourceConsumer> consumers, Rect frame,
             InsetsState state, WindowInsetsAnimationControlListener listener,
             @InsetType int types,
-            Supplier<SyncRtSurfaceTransactionApplier> transactionApplierSupplier) {
+            Supplier<SyncRtSurfaceTransactionApplier> transactionApplierSupplier,
+            InsetsController controller) {
         mConsumers = consumers;
         mListener = listener;
         mTypes = types;
         mTransactionApplierSupplier = transactionApplierSupplier;
-        mInitialInsetsState = new InsetsState(state);
+        mController = controller;
+        mInitialInsetsState = new InsetsState(state, true /* copySources */);
         mCurrentInsets = getInsetsFromState(mInitialInsetsState, frame, null /* typeSideMap */);
         mHiddenInsets = calculateInsets(mInitialInsetsState, frame, consumers, false /* shown */,
                 null /* typeSideMap */);
@@ -83,6 +89,10 @@
 
         // TODO: Check for controllability first and wait for IME if needed.
         listener.onReady(this, types);
+
+        mAnimation = new WindowInsetsAnimationListener.InsetsAnimation(mTypes, mHiddenInsets,
+                mShownInsets);
+        mController.dispatchAnimationStarted(mAnimation);
     }
 
     @Override
@@ -108,29 +118,35 @@
 
     @Override
     public void changeInsets(Insets insets) {
-        insets = sanitize(insets);
-        final Insets offset = Insets.subtract(mShownInsets, insets);
+        mPendingInsets = sanitize(insets);
+        mController.scheduleApplyChangeInsets();
+    }
+
+    void applyChangeInsets(InsetsState state) {
+        final Insets offset = Insets.subtract(mShownInsets, mPendingInsets);
         ArrayList<SurfaceParams> params = new ArrayList<>();
         if (offset.left != 0) {
-            updateLeashesForSide(INSET_SIDE_LEFT, offset.left, params);
+            updateLeashesForSide(INSET_SIDE_LEFT, offset.left, params, state);
         }
         if (offset.top != 0) {
-            updateLeashesForSide(INSET_SIDE_TOP, offset.top, params);
+            updateLeashesForSide(INSET_SIDE_TOP, offset.top, params, state);
         }
         if (offset.right != 0) {
-            updateLeashesForSide(INSET_SIDE_RIGHT, offset.right, params);
+            updateLeashesForSide(INSET_SIDE_RIGHT, offset.right, params, state);
         }
         if (offset.bottom != 0) {
-            updateLeashesForSide(INSET_SIDE_BOTTOM, offset.bottom, params);
+            updateLeashesForSide(INSET_SIDE_BOTTOM, offset.bottom, params, state);
         }
         SyncRtSurfaceTransactionApplier applier = mTransactionApplierSupplier.get();
         applier.scheduleApply(params.toArray(new SurfaceParams[params.size()]));
-        mCurrentInsets = insets;
+        mCurrentInsets = mPendingInsets;
     }
 
     @Override
     public void finish(int shownTypes) {
         // TODO
+
+        mController.dispatchAnimationFinished(mAnimation);
     }
 
     private Insets calculateInsets(InsetsState state, Rect frame,
@@ -146,7 +162,7 @@
             @Nullable @InsetSide SparseIntArray typeSideMap) {
         return state.calculateInsets(frame, false /* isScreenRound */,
                 false /* alwaysConsumerNavBar */, null /* displayCutout */, typeSideMap)
-                .getSystemWindowInsets();
+                .getInsets(mTypes);
     }
 
     private Insets sanitize(Insets insets) {
@@ -154,7 +170,7 @@
     }
 
     private void updateLeashesForSide(@InsetSide int side, int inset,
-            ArrayList<SurfaceParams> surfaceParams) {
+            ArrayList<SurfaceParams> surfaceParams, InsetsState state) {
         ArraySet<InsetsSourceConsumer> items = mSideSourceMap.get(side);
         // TODO: Implement behavior when inset spans over multiple types
         for (int i = items.size() - 1; i >= 0; i--) {
@@ -162,24 +178,32 @@
             final InsetsSource source = mInitialInsetsState.getSource(consumer.getType());
             final SurfaceControl leash = consumer.getControl().getLeash();
             mTmpMatrix.setTranslate(source.getFrame().left, source.getFrame().top);
-            addTranslationToMatrix(side, inset, mTmpMatrix);
+
+            mTmpFrame.set(source.getFrame());
+            addTranslationToMatrix(side, inset, mTmpMatrix, mTmpFrame);
+
+            state.getSource(source.getType()).setFrame(mTmpFrame);
             surfaceParams.add(new SurfaceParams(leash, 1f, mTmpMatrix, null, 0, 0f));
         }
     }
 
-    private void addTranslationToMatrix(@InsetSide int side, int inset, Matrix m) {
+    private void addTranslationToMatrix(@InsetSide int side, int inset, Matrix m, Rect frame) {
         switch (side) {
             case INSET_SIDE_LEFT:
                 m.postTranslate(-inset, 0);
+                frame.offset(-inset, 0);
                 break;
             case INSET_SIDE_TOP:
                 m.postTranslate(0, -inset);
+                frame.offset(0, -inset);
                 break;
             case INSET_SIDE_RIGHT:
                 m.postTranslate(inset, 0);
+                frame.offset(inset, 0);
                 break;
             case INSET_SIDE_BOTTOM:
                 m.postTranslate(0, inset);
+                frame.offset(0, inset);
                 break;
         }
     }
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 01af37e..c2ade76 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.util.ArraySet;
@@ -49,9 +50,29 @@
 
     private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>();
     private final ArrayList<InsetsAnimationControlImpl> mAnimationControls = new ArrayList<>();
+    private WindowInsets mLastInsets;
+
+    private boolean mAnimCallbackScheduled;
+
+    private final Runnable mAnimCallback;
 
     public InsetsController(ViewRootImpl viewRoot) {
         mViewRoot = viewRoot;
+        mAnimCallback = () -> {
+            mAnimCallbackScheduled = false;
+            if (mAnimationControls.isEmpty()) {
+                return;
+            }
+
+            InsetsState state = new InsetsState(mState, true /* copySources */);
+            for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
+                mAnimationControls.get(i).applyChangeInsets(state);
+            }
+            WindowInsets insets = state.calculateInsets(mFrame, mLastInsets.isRound(),
+                    mLastInsets.shouldAlwaysConsumeNavBar(), mLastInsets.getDisplayCutout(),
+                    null /* typeSideMap */);
+            mViewRoot.mView.dispatchWindowInsetsAnimationProgress(insets);
+        };
     }
 
     void onFrameChanged(Rect frame) {
@@ -82,8 +103,9 @@
     @VisibleForTesting
     public WindowInsets calculateInsets(boolean isScreenRound,
             boolean alwaysConsumeNavBar, DisplayCutout cutout) {
-        return mState.calculateInsets(mFrame, isScreenRound, alwaysConsumeNavBar, cutout,
+        mLastInsets = mState.calculateInsets(mFrame, isScreenRound, alwaysConsumeNavBar, cutout,
                 null /* typeSideMap */);
+        return mLastInsets;
     }
 
     /**
@@ -148,7 +170,7 @@
         }
         final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(consumers,
                 mFrame, mState, listener, types,
-                () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView));
+                () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this);
         mAnimationControls.add(controller);
     }
 
@@ -200,4 +222,20 @@
         pw.println(prefix); pw.println("InsetsController:");
         mState.dump(prefix + "  ", pw);
     }
+
+    void dispatchAnimationStarted(WindowInsetsAnimationListener.InsetsAnimation animation) {
+        mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation);
+    }
+
+    void dispatchAnimationFinished(WindowInsetsAnimationListener.InsetsAnimation animation) {
+        mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation);
+    }
+
+    void scheduleApplyChangeInsets() {
+        if (!mAnimCallbackScheduled) {
+            mViewRoot.mChoreographer.postCallback(Choreographer.CALLBACK_INSETS_ANIMATION,
+                    mAnimCallback, null /* token*/);
+            mAnimCallbackScheduled = true;
+        }
+    }
 }
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 0931914..cf8c0707 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -107,6 +107,10 @@
         set(copy);
     }
 
+    public InsetsState(InsetsState copy, boolean copySources) {
+        set(copy, copySources);
+    }
+
     /**
      * Calculates {@link WindowInsets} based on the current source configuration.
      *
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index cd0e579..abefd55 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -96,6 +96,7 @@
 import android.view.AccessibilityIterators.TextSegmentIterator;
 import android.view.AccessibilityIterators.WordTextSegmentIterator;
 import android.view.ContextMenu.ContextMenuInfo;
+import android.view.WindowInsetsAnimationListener.InsetsAnimation;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityEventSource;
 import android.view.accessibility.AccessibilityManager;
@@ -4547,6 +4548,8 @@
         OnCapturedPointerListener mOnCapturedPointerListener;
 
         private ArrayList<OnUnhandledKeyEventListener> mUnhandledKeyListeners;
+
+        private WindowInsetsAnimationListener mWindowInsetsAnimationListener;
     }
 
     @UnsupportedAppUsage
@@ -10488,6 +10491,37 @@
     }
 
     /**
+     * Sets a {@link WindowInsetsAnimationListener} to be notified about animations of windows that
+     * cause insets.
+     *
+     * @param listener The listener to set.
+     * @hide pending unhide
+     */
+    public void setWindowInsetsAnimationListener(WindowInsetsAnimationListener listener) {
+        getListenerInfo().mWindowInsetsAnimationListener = listener;
+    }
+
+    void dispatchWindowInsetsAnimationStarted(InsetsAnimation animation) {
+        if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationListener != null) {
+            mListenerInfo.mWindowInsetsAnimationListener.onStarted(animation);
+        }
+    }
+
+    WindowInsets dispatchWindowInsetsAnimationProgress(WindowInsets insets) {
+        if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationListener != null) {
+            return mListenerInfo.mWindowInsetsAnimationListener.onProgress(insets);
+        } else {
+            return insets;
+        }
+    }
+
+    void dispatchWindowInsetsAnimationFinished(InsetsAnimation animation) {
+        if (mListenerInfo != null && mListenerInfo.mOnApplyWindowInsetsListener != null) {
+            mListenerInfo.mWindowInsetsAnimationListener.onFinished(animation);
+        }
+    }
+
+    /**
      * Compute the view's coordinate within the surface.
      *
      * <p>Computes the coordinates of this view in its surface. The argument
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 9d11397..0986cfa 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -51,6 +51,7 @@
 import android.util.Pools.SynchronizedPool;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
+import android.view.WindowInsetsAnimationListener.InsetsAnimation;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -7139,6 +7140,34 @@
         return insets;
     }
 
+    @Override
+    void dispatchWindowInsetsAnimationStarted(InsetsAnimation animation) {
+        super.dispatchWindowInsetsAnimationStarted(animation);
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            getChildAt(i).dispatchWindowInsetsAnimationStarted(animation);
+        }
+    }
+
+    @Override
+    WindowInsets dispatchWindowInsetsAnimationProgress(WindowInsets insets) {
+        insets = super.dispatchWindowInsetsAnimationProgress(insets);
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            getChildAt(i).dispatchWindowInsetsAnimationProgress(insets);
+        }
+        return insets;
+    }
+
+    @Override
+    void dispatchWindowInsetsAnimationFinished(InsetsAnimation animation) {
+        super.dispatchWindowInsetsAnimationFinished(animation);
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            getChildAt(i).dispatchWindowInsetsAnimationFinished(animation);
+        }
+    }
+
     /**
      * Returns the animation listener to which layout animation events are
      * sent.
diff --git a/core/java/android/view/WindowInsetsAnimationController.java b/core/java/android/view/WindowInsetsAnimationController.java
index 9de517d..cf4415d 100644
--- a/core/java/android/view/WindowInsetsAnimationController.java
+++ b/core/java/android/view/WindowInsetsAnimationController.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.graphics.Insets;
 import android.view.WindowInsets.Type.InsetType;
+import android.view.WindowInsetsAnimationListener.InsetsAnimation;
 
 /**
  * Interface to control a window inset animation frame-by-frame.
@@ -28,8 +29,13 @@
 
     /**
      * Retrieves the {@link Insets} when the windows this animation is controlling are fully hidden.
+     * <p>
+     * If there are any animation listeners registered, this value is the same as
+     * {@link InsetsAnimation#getLowerBound()} that will be passed into the callbacks.
      *
      * @return Insets when the windows this animation is controlling are fully hidden.
+     *
+     * @see InsetsAnimation#getLowerBound()
      */
     @NonNull Insets getHiddenStateInsets();
 
@@ -38,8 +44,13 @@
      * <p>
      * In case the size of a window causing insets is changing in the middle of the animation, we
      * execute that height change after this animation has finished.
+     * <p>
+     * If there are any animation listeners registered, this value is the same as
+     * {@link InsetsAnimation#getUpperBound()} that will be passed into the callbacks.
      *
      * @return Insets when the windows this animation is controlling are fully shown.
+     *
+     * @see InsetsAnimation#getUpperBound()
      */
     @NonNull Insets getShownStateInsets();
 
@@ -59,8 +70,11 @@
      * <p>
      * Note that this will <b>not</b> inform the view system of a full inset change via
      * {@link View#dispatchApplyWindowInsets} in order to avoid a full layout pass during the
-     * animation. If you'd like to animate views during a window inset animation, use
-     * TODO add link to animation listeners.
+     * animation. If you'd like to animate views during a window inset animation, register a
+     * {@link WindowInsetsAnimationListener} by calling
+     * {@link View#setWindowInsetsAnimationListener(WindowInsetsAnimationListener)} that will be
+     * notified about any insets change via {@link WindowInsetsAnimationListener#onProgress} during
+     * the animation.
      * <p>
      * {@link View#dispatchApplyWindowInsets} will instead be called once the animation has
      * finished, i.e. once {@link #finish} has been called.
@@ -70,6 +84,9 @@
      *               the resulting insets of that configuration will match the passed in parameter.
      *               Note that these insets are being clamped to the range from
      *               {@link #getHiddenStateInsets} to {@link #getShownStateInsets}
+     *
+     * @see WindowInsetsAnimationListener
+     * @see View#setWindowInsetsAnimationListener(WindowInsetsAnimationListener)
      */
     void changeInsets(@NonNull Insets insets);
 
diff --git a/core/java/android/view/WindowInsetsAnimationListener.java b/core/java/android/view/WindowInsetsAnimationListener.java
new file mode 100644
index 0000000..682ab5b
--- /dev/null
+++ b/core/java/android/view/WindowInsetsAnimationListener.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2018 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.view;
+
+import android.graphics.Insets;
+
+/**
+ * Interface that allows the application to listen to animation events for windows that cause
+ * insets.
+ * @hide pending unhide
+ */
+public interface WindowInsetsAnimationListener {
+
+    /**
+     * Called when an inset animation gets started.
+     *
+     * @param animation The animation that is about to start.
+     */
+    void onStarted(InsetsAnimation animation);
+
+    /**
+     * Called when the insets change as part of running an animation. Note that even if multiple
+     * animations for different types are running, there will only be one progress callback per
+     * frame. The {@code insets} passed as an argument represents the overall state and will include
+     * all types, regardless of whether they are animating or not.
+     * <p>
+     * Note that insets dispatch is hierarchical: It will start at the root of the view hierarchy,
+     * and then traverse it and invoke the callback of the specific {@link View} being traversed.
+     * The callback may return a modified instance by calling {@link WindowInsets#inset(int, int, int, int)}
+     * to indicate that a part of the insets have been used to offset or clip its children, and the
+     * children shouldn't worry about that part anymore.
+     *
+     * @param insets The current insets.
+     * @return The insets to dispatch to the subtree of the hierarchy.
+     */
+    WindowInsets onProgress(WindowInsets insets);
+
+    /**
+     * Called when an inset animation has finished.
+     *
+     * @param animation The animation that has finished running.
+     */
+    void onFinished(InsetsAnimation animation);
+
+    /**
+     * Class representing an animation of a set of windows that cause insets.
+     */
+    class InsetsAnimation {
+
+        private final @WindowInsets.Type.InsetType int mTypeMask;
+        private final Insets mLowerBound;
+        private final Insets mUpperBound;
+
+        /**
+         * @hide
+         */
+        InsetsAnimation(int typeMask, Insets lowerBound, Insets upperBound) {
+            mTypeMask = typeMask;
+            mLowerBound = lowerBound;
+            mUpperBound = upperBound;
+        }
+
+        /**
+         * @return The bitmask of {@link WindowInsets.Type.InsetType}s that are animating.
+         */
+        public @WindowInsets.Type.InsetType int getTypeMask() {
+            return mTypeMask;
+        }
+
+        /**
+         * Queries the lower inset bound of the animation. If the animation is about showing or
+         * hiding a window that cause insets, the lower bound is {@link Insets#NONE} and the upper
+         * bound is the same as {@link WindowInsets#getInsets(int)} for the fully shown state. This
+         * is the same as {@link WindowInsetsAnimationController#getHiddenStateInsets} and
+         * {@link WindowInsetsAnimationController#getShownStateInsets} in case the listener gets
+         * invoked because of an animation that originates from
+         * {@link WindowInsetsAnimationController}.
+         * <p>
+         * However, if the size of a window that causes insets is changing, these are the
+         * lower/upper bounds of that size animation.
+         * <p>
+         * There are no overlapping animations for a specific type, but there may be two animations
+         * running at the same time for different inset types.
+         *
+         * @see #getUpperBound()
+         * @see WindowInsetsAnimationController#getHiddenStateInsets
+         * TODO: It's a bit weird that these are global per window but onProgress is hierarchical.
+         * TODO: If multiple types are animating, querying the bound per type isn't possible. Should
+         * we:
+         * 1. Offer bounds by type here?
+         * 2. Restrict one animation to one single type only?
+         * Returning WindowInsets here isn't feasible in case of overlapping animations: We can't
+         * fill in the insets for the types from the other animation into the WindowInsets object
+         * as it's changing as well.
+         */
+        public Insets getLowerBound() {
+            return mLowerBound;
+        }
+
+        /**
+         * @see #getLowerBound()
+         * @see WindowInsetsAnimationController#getShownStateInsets
+         */
+        public Insets getUpperBound() {
+            return mUpperBound;
+        }
+    }
+}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 90ccc25..93941d0 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -26,6 +26,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -329,6 +330,12 @@
     private static final int SYNC_CALLS_TIMEOUT_MS = 5000;
 
     /**
+     * @hide
+     */
+    @TestApi
+    public static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+
+    /**
      * Makes an authentication id from a request id and a dataset id.
      *
      * @param requestId The request id.
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 51b8734..0d16998 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3685,7 +3685,8 @@
      * @attr ref android.R.styleable#TextView_textCursorDrawable
      */
     public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) {
-        setTextCursorDrawable(mContext.getDrawable(textCursorDrawable));
+        setTextCursorDrawable(
+                textCursorDrawable != 0 ? mContext.getDrawable(textCursorDrawable) : null);
     }
 
     /**
diff --git a/core/java/com/android/internal/infra/AbstractRemoteService.java b/core/java/com/android/internal/infra/AbstractRemoteService.java
index e8ac223..5945958 100644
--- a/core/java/com/android/internal/infra/AbstractRemoteService.java
+++ b/core/java/com/android/internal/infra/AbstractRemoteService.java
@@ -213,6 +213,7 @@
         }
         mService = null;
         mServiceDied = true;
+        cancelScheduledUnbind();
         @SuppressWarnings("unchecked") // TODO(b/117779333): fix this warning
         final S castService = (S) this;
         mVultureCallback.onServiceDied(castService);
diff --git a/core/java/com/android/internal/os/RoSystemProperties.java b/core/java/com/android/internal/os/RoSystemProperties.java
index a319d83..b529bbe 100644
--- a/core/java/com/android/internal/os/RoSystemProperties.java
+++ b/core/java/com/android/internal/os/RoSystemProperties.java
@@ -30,6 +30,7 @@
     public static final String CONTROL_PRIVAPP_PERMISSIONS =
             SystemProperties.get("ro.control_privapp_permissions");
 
+    // ------ ro.hdmi.* -------- //
     /**
      * Property to indicate if a CEC audio device should forward volume keys when system audio
      * mode is off.
@@ -38,6 +39,14 @@
             SystemProperties.getBoolean(
                     "ro.hdmi.cec_audio_device_forward_volume_keys_system_audio_mode_off", false);
 
+    /**
+     * Property to indicate if the current device is a cec switch device.
+     *
+     * <p> Default is false.
+     */
+    public static final String PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH =
+            "ro.hdmi.property_is_device_hdmi_cec_switch";
+
     // ------ ro.config.* -------- //
     public static final boolean CONFIG_AVOID_GFX_ACCEL =
             SystemProperties.getBoolean("ro.config.avoid_gfx_accel", false);
diff --git a/core/java/com/android/internal/util/ContrastColorUtil.java b/core/java/com/android/internal/util/ContrastColorUtil.java
index a403c06..e0ba317f 100644
--- a/core/java/com/android/internal/util/ContrastColorUtil.java
+++ b/core/java/com/android/internal/util/ContrastColorUtil.java
@@ -586,7 +586,7 @@
      *
      * @param color the base color to use
      * @param amount the amount from 1 to 100 how much to modify the color
-     * @return the now color that was modified
+     * @return the new color that was modified
      */
     public static int getShiftedColor(int color, int amount) {
         final double[] result = ColorUtilsFromCompat.getTempDouble3Array();
@@ -599,6 +599,19 @@
         return ColorUtilsFromCompat.LABToColor(result[0], result[1], result[2]);
     }
 
+    /**
+     * Blends the provided color with white to create a muted version.
+     *
+     * @param color the color to mute
+     * @param alpha the amount from 0 to 1 to set the alpha component of the white scrim
+     * @return the new color that was modified
+     */
+    public static int getMutedColor(int color, float alpha) {
+        int whiteScrim = ColorUtilsFromCompat.setAlphaComponent(
+                Color.WHITE, (int) (255 * alpha));
+        return compositeColors(whiteScrim, color);
+    }
+
     private static boolean shouldUseDark(int backgroundColor, boolean defaultBackgroundIsDark) {
         if (backgroundColor == Notification.COLOR_DEFAULT) {
             return !defaultBackgroundIsDark;
@@ -675,6 +688,18 @@
         }
 
         /**
+         * Set the alpha component of {@code color} to be {@code alpha}.
+         */
+        @ColorInt
+        public static int setAlphaComponent(@ColorInt int color,
+                @IntRange(from = 0x0, to = 0xFF) int alpha) {
+            if (alpha < 0 || alpha > 255) {
+                throw new IllegalArgumentException("alpha must be between 0 and 255.");
+            }
+            return (color & 0x00ffffff) | (alpha << 24);
+        }
+
+        /**
          * Returns the luminance of a color as a float between {@code 0.0} and {@code 1.0}.
          * <p>Defined as the Y component in the XYZ representation of {@code color}.</p>
          */
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 888dab1..7d63ec9 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -29,7 +29,6 @@
 #include <time.h>
 #include <unistd.h>
 
-#include <atomic>
 #include <iomanip>
 #include <string>
 #include <vector>
@@ -42,6 +41,7 @@
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedUtfChars.h>
 #include "jni.h"
+#include <meminfo/procmeminfo.h>
 #include <meminfo/sysmeminfo.h>
 #include <memtrack/memtrack.h>
 #include <memunreachable/memunreachable.h>
@@ -150,14 +150,6 @@
     int swappedOutPss;
 };
 
-enum pss_rollup_support {
-  PSS_ROLLUP_UNTRIED,
-  PSS_ROLLUP_SUPPORTED,
-  PSS_ROLLUP_UNSUPPORTED
-};
-
-static std::atomic<pss_rollup_support> g_pss_rollup_support;
-
 #define BINDER_STATS "/proc/binder/stats"
 
 static jlong android_os_Debug_getNativeHeapSize(JNIEnv *env, jobject clazz)
@@ -555,37 +547,9 @@
     android_os_Debug_getDirtyPagesPid(env, clazz, getpid(), object);
 }
 
-UniqueFile OpenSmapsOrRollup(int pid)
-{
-    enum pss_rollup_support rollup_support =
-            g_pss_rollup_support.load(std::memory_order_relaxed);
-    if (rollup_support != PSS_ROLLUP_UNSUPPORTED) {
-        std::string smaps_rollup_path =
-                base::StringPrintf("/proc/%d/smaps_rollup", pid);
-        UniqueFile fp_rollup = MakeUniqueFile(smaps_rollup_path.c_str(), "re");
-        if (fp_rollup == nullptr && errno != ENOENT) {
-            return fp_rollup;  // Actual error, not just old kernel.
-        }
-        if (fp_rollup != nullptr) {
-            if (rollup_support == PSS_ROLLUP_UNTRIED) {
-                ALOGI("using rollup pss collection");
-                g_pss_rollup_support.store(PSS_ROLLUP_SUPPORTED,
-                                           std::memory_order_relaxed);
-            }
-            return fp_rollup;
-        }
-        g_pss_rollup_support.store(PSS_ROLLUP_UNSUPPORTED,
-                                   std::memory_order_relaxed);
-    }
-
-    std::string smaps_path = base::StringPrintf("/proc/%d/smaps", pid);
-    return MakeUniqueFile(smaps_path.c_str(), "re");
-}
-
 static jlong android_os_Debug_getPssPid(JNIEnv *env, jobject clazz, jint pid,
         jlongArray outUssSwapPssRss, jlongArray outMemtrack)
 {
-    char lineBuffer[1024];
     jlong pss = 0;
     jlong rss = 0;
     jlong swapPss = 0;
@@ -597,59 +561,14 @@
         pss = uss = rss = memtrack = graphics_mem.graphics + graphics_mem.gl + graphics_mem.other;
     }
 
-    {
-        UniqueFile fp = OpenSmapsOrRollup(pid);
-
-        if (fp != nullptr) {
-            char* line;
-
-            while (true) {
-                if (fgets(lineBuffer, sizeof (lineBuffer), fp.get()) == NULL) {
-                    break;
-                }
-                line = lineBuffer;
-
-                switch (line[0]) {
-                    case 'P':
-                        if (strncmp(line, "Pss:", 4) == 0) {
-                            char* c = line + 4;
-                            while (*c != 0 && (*c < '0' || *c > '9')) {
-                                c++;
-                            }
-                            pss += atoi(c);
-                        } else if (strncmp(line, "Private_Clean:", 14) == 0
-                                    || strncmp(line, "Private_Dirty:", 14) == 0) {
-                            char* c = line + 14;
-                            while (*c != 0 && (*c < '0' || *c > '9')) {
-                                c++;
-                            }
-                            uss += atoi(c);
-                        }
-                        break;
-                    case 'R':
-                        if (strncmp(line, "Rss:", 4) == 0) {
-                            char* c = line + 4;
-                            while (*c != 0 && (*c < '0' || *c > '9')) {
-                                c++;
-                            }
-                            rss += atoi(c);
-                        }
-                        break;
-                    case 'S':
-                        if (strncmp(line, "SwapPss:", 8) == 0) {
-                            char* c = line + 8;
-                            jlong lSwapPss;
-                            while (*c != 0 && (*c < '0' || *c > '9')) {
-                                c++;
-                            }
-                            lSwapPss = atoi(c);
-                            swapPss += lSwapPss;
-                            pss += lSwapPss; // Also in swap, those pages would be accounted as Pss without SWAP
-                        }
-                        break;
-                }
-            }
-        }
+    ::android::meminfo::ProcMemInfo proc_mem(pid);
+    ::android::meminfo::MemUsage stats;
+    if (proc_mem.SmapsOrRollup(&stats)) {
+        pss += stats.pss;
+        uss += stats.uss;
+        rss += stats.rss;
+        swapPss = stats.swap_pss;
+        pss += swapPss; // Also in swap, those pages would be accounted as Pss without SWAP
     }
 
     if (outUssSwapPssRss != NULL) {
@@ -686,34 +605,6 @@
     return android_os_Debug_getPssPid(env, clazz, getpid(), NULL, NULL);
 }
 
-static long get_allocated_vmalloc_memory() {
-    char line[1024];
-
-    long vmalloc_allocated_size = 0;
-
-    UniqueFile fp = MakeUniqueFile("/proc/vmallocinfo", "re");
-    if (fp == nullptr) {
-        return 0;
-    }
-
-    while (true) {
-        if (fgets(line, 1024, fp.get()) == NULL) {
-            break;
-        }
-
-        // check to see if there are pages mapped in vmalloc area
-        if (!strstr(line, "pages=")) {
-            continue;
-        }
-
-        long nr_pages;
-        if (sscanf(line, "%*x-%*x %*ld %*s pages=%ld", &nr_pages) == 1) {
-            vmalloc_allocated_size += (nr_pages * getpagesize());
-        }
-    }
-    return vmalloc_allocated_size;
-}
-
 // The 1:1 mapping of MEMINFO_* enums here must match with the constants from
 // Debug.java.
 enum {
@@ -763,9 +654,8 @@
     if (outArray != NULL) {
         outLen = MEMINFO_COUNT;
         for (int i = 0; i < outLen; i++) {
-            // TODO: move get_allocated_vmalloc_memory() to libmeminfo
             if (i == MEMINFO_VMALLOC_USED) {
-                outArray[i] = get_allocated_vmalloc_memory() / 1024;
+                outArray[i] = smi.ReadVmallocInfo() / 1024;
                 continue;
             }
             outArray[i] = mem[i];
@@ -775,7 +665,6 @@
     env->ReleaseLongArrayElements(out, outArray, 0);
 }
 
-
 static jint read_binder_stat(const char* stat)
 {
     UniqueFile fp = MakeUniqueFile(BINDER_STATS, "re");
diff --git a/core/jni/android_os_Debug.h b/core/jni/android_os_Debug.h
index c7b731b..747776a 100644
--- a/core/jni/android_os_Debug.h
+++ b/core/jni/android_os_Debug.h
@@ -19,6 +19,7 @@
 
 #include <memory>
 #include <stdio.h>
+#include <meminfo/meminfo.h>
 #include <android-base/stringprintf.h>
 #include <android-base/unique_fd.h>
 
@@ -34,8 +35,6 @@
     return UniqueFile(fopen(path, mode), safeFclose);
 }
 
-UniqueFile OpenSmapsOrRollup(int pid);
-
 }  // namespace android
 
 #endif  // ANDROID_OS_HW_BLOB_H
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 0c1a8aa..798825f 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -25,6 +25,7 @@
 #include <cutils/sched_policy.h>
 #include <utils/String8.h>
 #include <utils/Vector.h>
+#include <meminfo/procmeminfo.h>
 #include <meminfo/sysmeminfo.h>
 #include <processgroup/processgroup.h>
 
@@ -1083,21 +1084,12 @@
 
 static jlong android_os_Process_getPss(JNIEnv* env, jobject clazz, jint pid)
 {
-    UniqueFile file = OpenSmapsOrRollup(pid);
-    if (file == nullptr) {
+    ::android::meminfo::ProcMemInfo proc_mem(pid);
+    uint64_t pss;
+    if (!proc_mem.SmapsOrRollupPss(&pss)) {
         return (jlong) -1;
     }
 
-    // Tally up all of the Pss from the various maps
-    char line[256];
-    jlong pss = 0;
-    while (fgets(line, sizeof(line), file.get())) {
-        jlong v;
-        if (sscanf(line, "Pss: %" SCNd64 " kB", &v) == 1) {
-            pss += v;
-        }
-    }
-
     // Return the Pss value in bytes, not kilobytes
     return pss * 1024;
 }
diff --git a/core/proto/android/server/connectivity/data_stall_event.proto b/core/proto/android/server/connectivity/data_stall_event.proto
new file mode 100644
index 0000000..b70bb67
--- /dev/null
+++ b/core/proto/android/server/connectivity/data_stall_event.proto
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+syntax = "proto2";
+
+package com.android.server.connectivity;
+option java_multiple_files = true;
+option java_outer_classname = "DataStallEventProto";
+
+enum ProbeResult {
+    UNKNOWN = 0;
+    VALID = 1;
+    INVALID = 2;
+    PORTAL = 3;
+}
+
+enum ApBand {
+    AP_BAND_UNKNOWN = 0;
+    AP_BAND_2GHZ = 1;
+    AP_BAND_5GHZ = 2;
+}
+
+// Refer to definition in ServiceState.java.
+enum RadioTech {
+  RADIO_TECHNOLOGY_UNKNOWN = 0;
+  RADIO_TECHNOLOGY_GPRS = 1;
+  RADIO_TECHNOLOGY_EDGE = 2;
+  RADIO_TECHNOLOGY_UMTS = 3;
+  RADIO_TECHNOLOGY_IS95A = 4;
+  RADIO_TECHNOLOGY_IS95B = 5;
+  RADIO_TECHNOLOGY_1xRTT = 6;
+  RADIO_TECHNOLOGY_EVDO_0 = 7;
+  RADIO_TECHNOLOGY_EVDO_A = 8;
+  RADIO_TECHNOLOGY_HSDPA = 9;
+  RADIO_TECHNOLOGY_HSUPA = 10;
+  RADIO_TECHNOLOGY_HSPA = 11;
+  RADIO_TECHNOLOGY_EVDO_B = 12;
+  RADIO_TECHNOLOGY_EHRPD = 13;
+  RADIO_TECHNOLOGY_LTE = 14;
+  RADIO_TECHNOLOGY_HSPAP = 15;
+  RADIO_TECHNOLOGY_GSM = 16;
+  RADIO_TECHNOLOGY_TD_SCDMA = 17;
+  RADIO_TECHNOLOGY_IWLAN = 18;
+  RADIO_TECHNOLOGY_LTE_CA = 19;
+  RADIO_TECHNOLOGY_NR = 20;
+}
+
+// Cellular specific information.
+message CellularData {
+    // Indicate the radio technology at the time of data stall suspected.
+    optional RadioTech rat_type = 1;
+    // True if device is in roaming network at the time of data stall suspected.
+    optional bool is_roaming = 2;
+    // Registered network MccMnc when data stall happen
+    optional string network_mccmnc = 3;
+    // Indicate the SIM card carrier.
+    optional string sim_mccmnc = 4;
+    // Signal strength level at the time of data stall suspected.
+    optional int32 signal_strength = 5;
+}
+
+// Wifi specific information.
+message WifiData {
+    // Signal strength at the time of data stall suspected.
+    // RSSI range is between -55 to -110.
+    optional int32 signal_strength = 1;
+    // AP band.
+    optional ApBand wifi_band = 2;
+}
+
+message DnsEvent {
+    // The dns return code.
+    repeated int32 dns_return_code = 1;
+    // Indicate the timestamp of the dns event.
+    repeated int64 dns_time = 2;
+}
\ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8b85a28..8147b4a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3861,7 +3861,7 @@
 
     <!-- @SystemApi @hide Allows managing apk level rollbacks. -->
     <permission android:name="android.permission.MANAGE_ROLLBACKS"
-        android:protectionLevel="signature|installer" />
+        android:protectionLevel="signature|verifier" />
 
     <!-- @SystemApi @hide Allows an application to mark other applications as harmful -->
     <permission android:name="android.permission.SET_HARMFUL_APP_WARNINGS"
@@ -3906,7 +3906,8 @@
     <permission android:name="android.permission.ACCESS_NOTIFICATIONS"
         android:protectionLevel="signature|privileged|appop" />
 
-    <!-- Marker permission for applications that wish to access notification policy.
+    <!-- Marker permission for applications that wish to access notification policy. This permission
+        is not supported on managed profiles.
          <p>Protection level: normal
     -->
     <permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY"
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 54f6c63..18a42bc 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -593,7 +593,7 @@
          to be set for all windows of this activity -->
     <attr name="showForAllUsers" format="boolean" />
 
-    <!-- Specifies whether an {@link android.app.Activity} should be shown on top of the the lock screen
+    <!-- Specifies whether an {@link android.app.Activity} should be shown on top of the lock screen
          whenever the lockscreen is up and the activity is resumed. Normally an activity will be
          transitioned to the stopped state if it is started while the lockscreen is up, but with
          this flag set the activity will remain in the resumed state visible on-top of the lock
@@ -2412,6 +2412,19 @@
         <attr name="showForAllUsers" />
 
         <attr name="showWhenLocked" />
+        <!-- @hide @SystemApi Specifies whether this {@link android.app.Activity} should be shown on
+             top of the lock screen whenever the lockscreen is up and this activity has another
+             activity behind it with the {@link android.R.attr#showWhenLocked} attribute set. That
+             is, this activity is only visible on the lock screen if there is another activity with
+             the {@link android.R.attr#showWhenLocked} attribute visible at the same time on the
+             lock screen. A use case for this is permission dialogs, that should only be visible on
+             the lock screen if their requesting activity is also visible.
+
+         The default value of this attribute is <code>false</code>. -->
+    <attr name="inheritShowWhenLocked" format="boolean" />
+
+
+        <attr name="inheritShowWhenLocked" />
         <attr name="turnScreenOn" />
 
         <attr name="directBootAware" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 799d9d8..b3b30e9 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2934,6 +2934,8 @@
         <public name="foregroundServiceType" />
         <public name="hasFragileUserData" />
         <public name="minAspectRatio" />
+        <!-- @hide @SystemApi -->
+        <public name="inheritShowWhenLocked" />
     </public-group>
 
     <public-group type="drawable" first-id="0x010800b4">
diff --git a/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
new file mode 100644
index 0000000..8a3ba8c
--- /dev/null
+++ b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2018 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.service.notification;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.metrics.LogMaker;
+import android.os.UserHandle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StatusBarNotificationTest {
+
+    private final Context mMockContext = mock(Context.class);
+    @Mock
+    private PackageManager mPm;
+
+    private static final String PKG = "com.example.o";
+    private static final int UID = 9583;
+    private static final int ID = 1;
+    private static final String TAG = "tag1";
+    private static final String CHANNEL_ID = "channel";
+    private static final String CHANNEL_ID_LONG =
+            "give_a_developer_a_string_argument_and_who_knows_what_they_will_pass_in_there";
+    private static final String GROUP_ID_1 = "group1";
+    private static final String GROUP_ID_2 = "group2";
+    private static final String GROUP_ID_LONG =
+            "0|com.foo.bar|g:content://com.foo.bar.ui/account%3A-0000000/account/";
+    private static final android.os.UserHandle USER =
+            UserHandle.of(ActivityManager.getCurrentUser());
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mMockContext.getResources()).thenReturn(
+                InstrumentationRegistry.getContext().getResources());
+        when(mMockContext.getPackageManager()).thenReturn(mPm);
+        when(mMockContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
+    }
+
+    @Test
+    public void testLogMaker() {
+        final LogMaker logMaker = getNotification(PKG, GROUP_ID_1, CHANNEL_ID).getLogMaker();
+
+        assertEquals(CHANNEL_ID,
+                (String) logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID));
+        assertEquals(PKG, logMaker.getPackageName());
+        assertEquals(ID, logMaker.getTaggedData(MetricsEvent.NOTIFICATION_ID));
+        assertEquals(TAG, logMaker.getTaggedData(MetricsEvent.NOTIFICATION_TAG));
+        assertEquals(GROUP_ID_1,
+                logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
+    }
+
+    @Test
+    public void testLogMakerNoChannel() {
+        final LogMaker logMaker = getNotification(PKG, GROUP_ID_1, null).getLogMaker();
+
+        assertNull(logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID));
+    }
+
+    @Test
+    public void testLogMakerLongChannel() {
+        final LogMaker logMaker = getNotification(PKG, null, CHANNEL_ID_LONG).getLogMaker();
+        final String loggedId = (String) logMaker
+                .getTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID);
+        assertEquals(StatusBarNotification.MAX_LOG_TAG_LENGTH, loggedId.length());
+        assertEquals(CHANNEL_ID_LONG.substring(0, 10), loggedId.substring(0, 10));
+    }
+
+    @Test
+    public void testLogMakerNoGroup() {
+        final LogMaker logMaker = getNotification(PKG, null, CHANNEL_ID).getLogMaker();
+
+        assertNull(
+                logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
+    }
+
+    @Test
+    public void testLogMakerLongGroup() {
+        final LogMaker logMaker = getNotification(PKG, GROUP_ID_LONG, CHANNEL_ID)
+                .getLogMaker();
+
+        final String loggedId = (String)
+                logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID);
+        assertEquals(StatusBarNotification.MAX_LOG_TAG_LENGTH, loggedId.length());
+        assertEquals(GROUP_ID_LONG.substring(0, 10), loggedId.substring(0, 10));
+    }
+
+    @Test
+    public void testLogMakerOverrideGroup() {
+        StatusBarNotification sbn = getNotification(PKG, GROUP_ID_1, CHANNEL_ID);
+        assertEquals(GROUP_ID_1,
+                sbn.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
+
+        sbn.setOverrideGroupKey(GROUP_ID_2);
+        assertEquals(GROUP_ID_2,
+                sbn.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
+
+        sbn.setOverrideGroupKey(null);
+        assertEquals(GROUP_ID_1,
+                sbn.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
+    }
+
+    private StatusBarNotification getNotification(String pkg, String group, String channelId) {
+        final Notification.Builder builder = new Notification.Builder(mMockContext, channelId)
+                .setContentTitle("foo")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+
+        if (group != null) {
+            builder.setGroup(group);
+        }
+
+        Notification n = builder.build();
+        return new StatusBarNotification(
+                pkg, pkg, ID, TAG, UID, UID, n, USER, null, UID);
+    }
+
+}
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index d520f15..81ca910 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -19,6 +19,7 @@
 import static android.view.InsetsState.TYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.TYPE_TOP_BAR;
 import static junit.framework.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
 import android.graphics.Insets;
@@ -83,7 +84,7 @@
         consumers.put(TYPE_NAVIGATION_BAR, navConsumer);
         mController = new InsetsAnimationControlImpl(consumers,
                 new Rect(0, 0, 500, 500), state, mMockListener, WindowInsets.Type.systemBars(),
-                () -> mMockTransactionApplier);
+                () -> mMockTransactionApplier, mock(InsetsController.class));
     }
 
     @Test
diff --git a/core/tests/overlaytests/device/Android.mk b/core/tests/overlaytests/device/Android.mk
index 680ebeb..5630749 100644
--- a/core/tests/overlaytests/device/Android.mk
+++ b/core/tests/overlaytests/device/Android.mk
@@ -19,7 +19,7 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_PACKAGE_NAME := OverlayDeviceTests
 LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
 LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_TARGET_REQUIRED_MODULES := \
     OverlayDeviceTests_AppOverlayOne \
diff --git a/core/tests/overlaytests/device/AndroidManifest.xml b/core/tests/overlaytests/device/AndroidManifest.xml
index d14fdf5..4881636 100644
--- a/core/tests/overlaytests/device/AndroidManifest.xml
+++ b/core/tests/overlaytests/device/AndroidManifest.xml
@@ -23,7 +23,7 @@
         <uses-library android:name="android.test.runner"/>
     </application>
 
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
         android:targetPackage="com.android.overlaytest"
         android:label="Runtime resource overlay tests" />
 </manifest>
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java
index 5a86885..91fcdbb 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java
@@ -25,10 +25,11 @@
 import android.content.res.XmlResourceParser;
 import android.os.LocaleList;
 import android.os.ParcelFileDescriptor;
-import android.support.test.InstrumentationRegistry;
 import android.util.AttributeSet;
 import android.util.Xml;
 
+import androidx.test.InstrumentationRegistry;
+
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java
index f35e511..cd3ed9d 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java
@@ -16,7 +16,7 @@
 
 package com.android.overlaytest;
 
-import android.support.test.filters.MediumTest;
+import androidx.test.filters.MediumTest;
 
 import org.junit.BeforeClass;
 import org.junit.runner.RunWith;
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java
index 037449f..c0d4281 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java
@@ -16,7 +16,7 @@
 
 package com.android.overlaytest;
 
-import android.support.test.filters.MediumTest;
+import androidx.test.filters.MediumTest;
 
 import org.junit.BeforeClass;
 import org.junit.runner.RunWith;
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java
index f657b5c..33c7b25 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java
@@ -16,7 +16,7 @@
 
 package com.android.overlaytest;
 
-import android.support.test.filters.MediumTest;
+import androidx.test.filters.MediumTest;
 
 import org.junit.BeforeClass;
 import org.junit.runner.RunWith;
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 25dabad..0bffa38 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -39,6 +39,9 @@
     name: "privapp-permissions-platform.xml",
     sub_dir: "permissions",
     src: "privapp-permissions-platform.xml",
+    required: [
+        "privapp_whitelist_com.android.settings.intelligence",
+    ]
 }
 
 prebuilt_etc {
@@ -50,6 +53,13 @@
 }
 
 prebuilt_etc {
+    name: "privapp_whitelist_com.android.settings.intelligence",
+    sub_dir: "permissions",
+    src: "com.android.settings.intelligence.xml",
+    filename_from_src: true,
+}
+
+prebuilt_etc {
     name: "privapp_whitelist_com.android.systemui",
     product_specific: true,
     sub_dir: "permissions",
diff --git a/data/etc/com.android.settings.intelligence.xml b/data/etc/com.android.settings.intelligence.xml
new file mode 100644
index 0000000..6ca30c1
--- /dev/null
+++ b/data/etc/com.android.settings.intelligence.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 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
+  -->
+<permissions>
+    <privapp-permissions package="com.android.settings.intelligence">
+        <permission name="android.permission.MANAGE_FINGERPRINT"/>
+        <permission name="android.permission.MODIFY_PHONE_STATE"/>
+        <permission name="android.permission.READ_SEARCH_INDEXABLES"/>
+        <permission name="android.permission.WRITE_SETTINGS_HOMEPAGE_DATA"/>
+    </privapp-permissions>
+</permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index fbe613d..393a2a6 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -280,13 +280,6 @@
         <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
     </privapp-permissions>
 
-    <privapp-permissions package="com.android.settings.intelligence">
-        <permission name="android.permission.MANAGE_FINGERPRINT"/>
-        <permission name="android.permission.MODIFY_PHONE_STATE"/>
-        <permission name="android.permission.READ_SEARCH_INDEXABLES"/>
-        <permission name="android.permission.WRITE_SETTINGS_HOMEPAGE_DATA"/>
-    </privapp-permissions>
-
     <privapp-permissions package="com.android.sharedstoragebackup">
         <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
     </privapp-permissions>
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index f0efb58..95317a4 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -984,11 +984,12 @@
      *         {@link Named#SRGB sRGB} primaries.
      *     </li>
      *     <li>
-     *         Its white point is withing 1e-3 of the CIE standard
+     *         Its white point is within 1e-3 of the CIE standard
      *         illuminant {@link #ILLUMINANT_D65 D65}.
      *     </li>
      *     <li>Its opto-electronic transfer function is not linear.</li>
      *     <li>Its electro-optical transfer function is not linear.</li>
+     *     <li>Its transfer functions yield values within 1e-3 of {@link Named#SRGB}.</li>
      *     <li>Its range is \([0..1]\).</li>
      * </ul>
      * <p>This method always returns true for {@link Named#SRGB}.</p>
@@ -3145,19 +3146,35 @@
                 float max,
                 @IntRange(from = MIN_ID, to = MAX_ID) int id) {
             if (id == 0) return true;
-            if (!compare(primaries, SRGB_PRIMARIES)) {
+            if (!ColorSpace.compare(primaries, SRGB_PRIMARIES)) {
                 return false;
             }
-            if (!compare(whitePoint, ILLUMINANT_D65)) {
+            if (!ColorSpace.compare(whitePoint, ILLUMINANT_D65)) {
                 return false;
             }
-            if (OETF.applyAsDouble(0.5) < 0.5001) return false;
-            if (EOTF.applyAsDouble(0.5) > 0.5001) return false;
+
             if (min != 0.0f) return false;
             if (max != 1.0f) return false;
+
+            // We would have already returned true if this was SRGB itself, so
+            // it is safe to reference it here.
+            ColorSpace.Rgb srgb = (ColorSpace.Rgb) get(Named.SRGB);
+
+            for (double x = 0.0; x <= 1.0; x += 1 / 255.0) {
+                if (!compare(x, OETF, srgb.mOetf)) return false;
+                if (!compare(x, EOTF, srgb.mEotf)) return false;
+            }
+
             return true;
         }
 
+        private static boolean compare(double point, @NonNull DoubleUnaryOperator a,
+                @NonNull DoubleUnaryOperator b) {
+            double rA = a.applyAsDouble(point);
+            double rB = b.applyAsDouble(point);
+            return Math.abs(rA - rB) <= 1e-3;
+        }
+
         /**
          * Computes whether the specified CIE xyY or XYZ primaries (with Y set to 1) form
          * a wide color gamut. A color gamut is considered wide if its area is &gt; 90%
diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java
index 1ee851f..7b20f7a 100644
--- a/media/java/android/media/MediaSession2.java
+++ b/media/java/android/media/MediaSession2.java
@@ -250,7 +250,8 @@
                     if (controllerInfo.mAllowedCommands == null) {
                         // For trusted apps, send non-null allowed commands to keep
                         // connection.
-                        controllerInfo.mAllowedCommands = new Session2CommandGroup();
+                        controllerInfo.mAllowedCommands =
+                                new Session2CommandGroup.Builder().build();
                     }
                     if (DEBUG) {
                         Log.d(TAG, "Accepting connection: " + controllerInfo);
diff --git a/media/java/android/media/Session2CommandGroup.java b/media/java/android/media/Session2CommandGroup.java
index 4668ec4..519888e 100644
--- a/media/java/android/media/Session2CommandGroup.java
+++ b/media/java/android/media/Session2CommandGroup.java
@@ -35,7 +35,6 @@
  * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
  * for consistent behavior across all devices.
  * </p>
- * @hide
  */
 public final class Session2CommandGroup implements Parcelable {
     private static final String TAG = "Session2CommandGroup";
@@ -56,16 +55,11 @@
     Set<Session2Command> mCommands = new HashSet<>();
 
     /**
-     * Default Constructor.
-     */
-    public Session2CommandGroup() {}
-
-    /**
      * Creates a new Session2CommandGroup with commands copied from another object.
      *
      * @param commands The collection of commands to copy.
      */
-    public Session2CommandGroup(@Nullable Collection<Session2Command> commands) {
+    Session2CommandGroup(@Nullable Collection<Session2Command> commands) {
         if (commands != null) {
             mCommands.addAll(commands);
         }
@@ -129,7 +123,10 @@
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        if (dest == null) {
+            throw new IllegalArgumentException("parcel shouldn't be null");
+        }
         dest.writeParcelableArray(mCommands.toArray(new Session2Command[0]), 0);
     }
 
@@ -149,6 +146,9 @@
          * @param commandGroup
          */
         public Builder(@NonNull Session2CommandGroup commandGroup) {
+            if (commandGroup == null) {
+                throw new IllegalArgumentException("command group shouldn't be null");
+            }
             mCommands = commandGroup.getCommands();
         }
 
diff --git a/media/java/android/media/session/ISession2TokensListener.aidl b/media/java/android/media/session/ISession2TokensListener.aidl
new file mode 100644
index 0000000..7d1a4aa
--- /dev/null
+++ b/media/java/android/media/session/ISession2TokensListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2019 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.media.session;
+
+import android.media.Session2Token;
+
+/**
+ * Listens for changes to the list of session2 tokens.
+ * @hide
+ */
+oneway interface ISession2TokensListener {
+    void onSession2TokensChanged(in List<Session2Token> tokens);
+}
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 7ac3ef2..46516e0 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -23,6 +23,7 @@
 import android.media.session.IOnMediaKeyListener;
 import android.media.session.IOnVolumeKeyLongPressListener;
 import android.media.session.ISession;
+import android.media.session.ISession2TokensListener;
 import android.media.session.SessionCallbackLink;
 import android.os.Bundle;
 import android.view.KeyEvent;
@@ -45,6 +46,8 @@
     void addSessionsListener(in IActiveSessionsListener listener, in ComponentName compName,
             int userId);
     void removeSessionsListener(in IActiveSessionsListener listener);
+    void addSession2TokensListener(in ISession2TokensListener listener, int userId);
+    void removeSession2TokensListener(in ISession2TokensListener listener);
 
     // This is for the system volume UI only
     void setRemoteVolumeController(in IRemoteVolumeController rvc);
@@ -56,6 +59,5 @@
     void setOnVolumeKeyLongPressListener(in IOnVolumeKeyLongPressListener listener);
     void setOnMediaKeyListener(in IOnMediaKeyListener listener);
 
-    // MediaSession2
     boolean isTrusted(String controllerPackageName, int controllerPid, int controllerUid);
 }
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 56ea484..4596c22 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -41,6 +41,8 @@
 import android.util.Log;
 import android.view.KeyEvent;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
@@ -69,9 +71,13 @@
      */
     public static final int RESULT_MEDIA_KEY_HANDLED = 1;
 
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
     private final ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper> mListeners
             = new ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper>();
-    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private final ArrayMap<OnSession2TokensChangedListener, Session2TokensChangedWrapper>
+            mSession2TokensListeners = new ArrayMap<>();
     private final ISessionManager mService;
 
     private Context mContext;
@@ -324,6 +330,87 @@
     }
 
     /**
+     * Adds a listener to be notified when the {@link #getSession2Tokens()} changes.
+     * <p>
+     * This API is not generally intended for third party application developers.
+     * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
+     * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
+     * for consistent behavior across all devices.
+     *
+     * @param listener The listener to add
+     * @param handler The handler to call listener on. If {@code null}, calling thread's looper will
+     *                be used.
+     * @hide
+     */
+    // TODO(jaewan): Unhide
+    public void addOnSession2TokensChangedListener(
+            @NonNull OnSession2TokensChangedListener listener, @Nullable Handler handler) {
+        addOnSession2TokensChangedListener(UserHandle.myUserId(), listener, handler);
+    }
+
+    /**
+     * Adds a listener to be notified when the {@link #getSession2Tokens()} changes.
+     * <p>
+     * This API is not generally intended for third party application developers.
+     * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
+     * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
+     * for consistent behavior across all devices.
+     *
+     * @param userId The userId to listen for changes on
+     * @param listener The listener to add
+     * @param handler The handler to call listener on. If {@code null}, calling thread's looper will
+     *                be used.
+     * @hide
+     */
+    public void addOnSession2TokensChangedListener(int userId,
+            @NonNull OnSession2TokensChangedListener listener, @Nullable Handler handler) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener shouldn't be null");
+        }
+        synchronized (mLock) {
+            if (mSession2TokensListeners.get(listener) != null) {
+                Log.w(TAG, "Attempted to add session listener twice, ignoring.");
+                return;
+            }
+            Session2TokensChangedWrapper wrapper =
+                    new Session2TokensChangedWrapper(listener, handler);
+            try {
+                mService.addSession2TokensListener(wrapper.getStub(), userId);
+                mSession2TokensListeners.put(listener, wrapper);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error in addSessionTokensListener.", e);
+                e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Removes the {@link OnSession2TokensChangedListener} to stop receiving session token updates.
+     *
+     * @param listener The listener to remove.
+     * @hide
+     */
+    // TODO(jaewan): Unhide
+    public void removeOnSession2TokensChangedListener(
+            @NonNull OnSession2TokensChangedListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener may not be null");
+        }
+        final Session2TokensChangedWrapper wrapper;
+        synchronized (mLock) {
+            wrapper = mSession2TokensListeners.remove(listener);
+        }
+        if (wrapper != null) {
+            try {
+                mService.removeSession2TokensListener(wrapper.getStub());
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error in removeSessionTokensListener.", e);
+                e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
      * Set the remote volume controller to receive volume updates on. Only for
      * use by system UI.
      *
@@ -590,6 +677,26 @@
     }
 
     /**
+     * Listens for changes to the {@link #getSession2Tokens()}. This can be added
+     * using {@link #addOnSession2TokensChangedListener(OnSession2TokensChangedListener, Handler)}.
+     * <p>
+     * This API is not generally intended for third party application developers.
+     * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
+     * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
+     * for consistent behavior across all devices.
+     *
+     * @hide
+     */
+    public interface OnSession2TokensChangedListener {
+        /**
+         * Called when the {@link #getSession2Tokens()} is changed.
+         *
+         * @param tokens list of {@link Session2Token}
+         */
+        void onSession2TokensChanged(@NonNull List<Session2Token> tokens);
+    }
+
+    /**
      * Listens the volume key long-presses.
      * @hide
      */
@@ -807,6 +914,27 @@
         }
     }
 
+    private static final class Session2TokensChangedWrapper {
+        private final OnSession2TokensChangedListener mListener;
+        private final Handler mHandler;
+        private final ISession2TokensListener.Stub mStub =
+                new ISession2TokensListener.Stub() {
+                    @Override
+                    public void onSession2TokensChanged(final List<Session2Token> tokens) {
+                        mHandler.post(() -> mListener.onSession2TokensChanged(tokens));
+                    }
+                };
+
+        Session2TokensChangedWrapper(OnSession2TokensChangedListener listener, Handler handler) {
+            mListener = listener;
+            mHandler = (handler == null) ? new Handler() : new Handler(handler.getLooper());
+        }
+
+        public ISession2TokensListener.Stub getStub() {
+            return mStub;
+        }
+    }
+
     private static final class OnVolumeKeyLongPressListenerImpl
             extends IOnVolumeKeyLongPressListener.Stub {
         private OnVolumeKeyLongPressListener mListener;
diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
index 83fd8d9..499d493 100644
--- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java
+++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
@@ -385,11 +385,11 @@
     }
 
     @Override
-    public void onActionClicked(@NonNull String key, @NonNull Notification.Action action,
+    public void onActionInvoked(@NonNull String key, @NonNull Notification.Action action,
             @Source int source) {
         if (DEBUG) {
             Log.d(TAG,
-                    "onActionClicked() called with: key = [" + key + "], action = [" + action.title
+                    "onActionInvoked() called with: key = [" + key + "], action = [" + action.title
                             + "], source = [" + source + "]");
         }
         mSmartActionsHelper.onActionClicked(key, action, source);
diff --git a/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java b/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java
index 71fd9ce..acf1180 100644
--- a/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java
+++ b/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java
@@ -27,7 +27,9 @@
 import android.app.NotificationChannel;
 import android.app.Person;
 import android.app.RemoteInput;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
 import android.media.AudioAttributes;
 import android.media.AudioSystem;
 import android.os.Build;
@@ -67,8 +69,12 @@
 
     private boolean isPreChannelsNotification() {
         try {
-            mTargetSdkVersion = mPackageManager.getApplicationInfo(
-                    mSbn.getPackageName(), 0, mSbn.getUserId()).targetSdkVersion;
+            ApplicationInfo info = mPackageManager.getApplicationInfo(
+                    mSbn.getPackageName(), PackageManager.MATCH_ALL,
+                    mSbn.getUserId());
+            if (info != null) {
+                mTargetSdkVersion = info.targetSdkVersion;
+            }
         } catch (RemoteException e) {
             Log.w(TAG, "Couldn't look up " + mSbn.getPackageName());
         }
diff --git a/packages/SystemUI/res/layout/bubble_view.xml b/packages/SystemUI/res/layout/bubble_view.xml
new file mode 100644
index 0000000..204408cd
--- /dev/null
+++ b/packages/SystemUI/res/layout/bubble_view.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+<com.android.systemui.bubbles.BubbleView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:id="@+id/bubble_view">
+
+    <com.android.systemui.bubbles.BadgedImageView
+        android:id="@+id/bubble_image"
+        android:layout_width="@dimen/bubble_size"
+        android:layout_height="@dimen/bubble_size"
+        android:padding="@dimen/bubble_view_padding"
+        android:clipToPadding="false"/>
+
+    <TextView
+        android:id="@+id/message_view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:minWidth="@dimen/bubble_message_min_width"
+        android:maxWidth="@dimen/bubble_message_max_width"
+        android:padding="@dimen/bubble_message_padding"/>
+
+</com.android.systemui.bubbles.BubbleView>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 7cc5524..476089a 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -454,6 +454,10 @@
          RemoteInput.Builder.setEditChoicesBeforeSending. -->
     <bool name="config_smart_replies_in_notifications_edit_choices_before_sending">false</bool>
 
+    <!-- Smart replies in notifications: Whether smart suggestions in notifications are enabled in
+         heads-up notifications.  -->
+    <bool name="config_smart_replies_in_notifications_show_in_heads_up">true</bool>
+
     <!-- Screenshot editing default activity.  Must handle ACTION_EDIT image/png intents.
          Blank sends the user to the Chooser first.
          This name is in the ComponentName flattened format (package/class)  -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 10e5f74..ef16bca 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -981,14 +981,16 @@
 
     <!-- How much a bubble is elevated -->
     <dimen name="bubble_elevation">8dp</dimen>
+    <!-- Padding around a collapsed bubble -->
+    <dimen name="bubble_view_padding">0dp</dimen>
     <!-- Padding between bubbles when displayed in expanded state -->
     <dimen name="bubble_padding">8dp</dimen>
-    <!-- Padding around the view displayed when the bubble is expanded -->
-    <dimen name="bubble_expanded_view_padding">8dp</dimen>
     <!-- Size of the collapsed bubble -->
     <dimen name="bubble_size">56dp</dimen>
-    <!-- Size of an icon displayed within the bubble -->
-    <dimen name="bubble_icon_size">24dp</dimen>
+    <!-- How much to inset the icon in the circle -->
+    <dimen name="bubble_icon_inset">16dp</dimen>
+    <!-- Padding around the view displayed when the bubble is expanded -->
+    <dimen name="bubble_expanded_view_padding">8dp</dimen>
     <!-- Default height of the expanded view shown when the bubble is expanded -->
     <dimen name="bubble_expanded_default_height">400dp</dimen>
     <!-- Height of the triangle that points to the expanded bubble -->
@@ -1001,4 +1003,10 @@
     <dimen name="bubble_expanded_header_height">48dp</dimen>
     <!-- Left and right padding applied to the header. -->
     <dimen name="bubble_expanded_header_horizontal_padding">24dp</dimen>
+    <!-- Max width of the message bubble-->
+    <dimen name="bubble_message_max_width">144dp</dimen>
+    <!-- Min width of the message bubble -->
+    <dimen name="bubble_message_min_width">32dp</dimen>
+    <!-- Interior padding of the message bubble -->
+    <dimen name="bubble_message_padding">4dp</dimen>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java
new file mode 100644
index 0000000..845b084
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2018 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.bubbles;
+
+import static android.graphics.Paint.ANTI_ALIAS_FLAG;
+import static android.graphics.Paint.FILTER_BITMAP_FLAG;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.Log;
+
+// XXX: Mostly opied from launcher code / can we share?
+/**
+ * Contains parameters necessary to draw a badge for an icon (e.g. the size of the badge).
+ */
+public class BadgeRenderer {
+
+    private static final String TAG = "BadgeRenderer";
+
+    // The badge sizes are defined as percentages of the app icon size.
+    private static final float SIZE_PERCENTAGE = 0.38f;
+
+    // Extra scale down of the dot
+    private static final float DOT_SCALE = 0.6f;
+
+    private final float mDotCenterOffset;
+    private final float mCircleRadius;
+    private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);
+
+    public BadgeRenderer(int iconSizePx) {
+        mDotCenterOffset = SIZE_PERCENTAGE * iconSizePx;
+        int size = (int) (DOT_SCALE * mDotCenterOffset);
+        mCircleRadius = size / 2f;
+    }
+
+    /**
+     * Draw a circle in the top right corner of the given bounds.
+     *
+     * @param color The color (based on the icon) to use for the badge.
+     * @param iconBounds The bounds of the icon being badged.
+     * @param badgeScale The progress of the animation, from 0 to 1.
+     * @param spaceForOffset How much space to offset the badge up and to the left or right.
+     * @param onLeft Whether the badge should be draw on left or right side.
+     */
+    public void draw(Canvas canvas, int color, Rect iconBounds, float badgeScale,
+            Point spaceForOffset, boolean onLeft) {
+        if (iconBounds == null) {
+            Log.e(TAG, "Invalid null argument(s) passed in call to draw.");
+            return;
+        }
+        canvas.save();
+        // We draw the badge relative to its center.
+        int x = onLeft ? iconBounds.left : iconBounds.right;
+        float offset = onLeft ? (mDotCenterOffset / 2) : -(mDotCenterOffset / 2);
+        float badgeCenterX = x + offset;
+        float badgeCenterY = iconBounds.top + mDotCenterOffset / 2;
+
+        canvas.translate(badgeCenterX + spaceForOffset.x, badgeCenterY - spaceForOffset.y);
+
+        canvas.scale(badgeScale, badgeScale);
+        mCirclePaint.setColor(color);
+        canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint);
+        canvas.restore();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
new file mode 100644
index 0000000..92d3cc1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2018 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.bubbles;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+import com.android.systemui.R;
+
+/**
+ * View that circle crops its contents and supports displaying a coloured dot on a top corner.
+ */
+public class BadgedImageView extends ImageView {
+
+    private BadgeRenderer mDotRenderer;
+    private int mIconSize;
+    private Rect mTempBounds = new Rect();
+    private Point mTempPoint = new Point();
+    private Path mClipPath = new Path();
+
+    private float mDotScale = 0f;
+    private int mUpdateDotColor;
+    private boolean mShowUpdateDot;
+    private boolean mOnLeft;
+
+    public BadgedImageView(Context context) {
+        this(context, null);
+    }
+
+    public BadgedImageView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        setScaleType(ScaleType.CENTER_CROP);
+        mIconSize = getResources().getDimensionPixelSize(R.dimen.bubble_size);
+        mDotRenderer = new BadgeRenderer(mIconSize);
+    }
+
+    // TODO: Clipping oval path isn't great: rerender image into a separate, rounded bitmap and
+    // then draw would be better
+    @Override
+    public void onDraw(Canvas canvas) {
+        canvas.save();
+        // Circle crop
+        mClipPath.addOval(getPaddingStart(), getPaddingTop(),
+                getWidth() - getPaddingEnd(), getHeight() - getPaddingBottom(), Path.Direction.CW);
+        canvas.clipPath(mClipPath);
+        super.onDraw(canvas);
+
+        // After we've circle cropped what we're showing, restore so we don't clip the badge
+        canvas.restore();
+
+        // Draw the badge
+        if (mShowUpdateDot) {
+            getDrawingRect(mTempBounds);
+            mTempPoint.set((getWidth() - mIconSize) / 2, getPaddingTop());
+            mDotRenderer.draw(canvas, mUpdateDotColor, mTempBounds, mDotScale, mTempPoint,
+                    mOnLeft);
+        }
+    }
+
+    /**
+     * Set whether the dot should appear on left or right side of the view.
+     */
+    public void setDotPosition(boolean onLeft) {
+        mOnLeft = onLeft;
+        invalidate();
+    }
+
+    /**
+     * Set whether the dot should show or not.
+     */
+    public void setShowDot(boolean showBadge) {
+        mShowUpdateDot = showBadge;
+        invalidate();
+    }
+
+    /**
+     * @return whether the dot is being displayed.
+     */
+    public boolean isShowingDot() {
+        return mShowUpdateDot;
+    }
+
+    /**
+     * The colour to use for the dot.
+     */
+    public void setDotColor(int color) {
+        mUpdateDotColor = color;
+        invalidate();
+    }
+
+    /**
+     * How big the dot should be, fraction from 0 to 1.
+     */
+    public void setDotScale(float fraction) {
+        mDotScale = fraction;
+        invalidate();
+    }
+
+    public float getDotScale() {
+        return mDotScale;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 9f3ff78..d7bf77d 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -21,33 +21,42 @@
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
 import static com.android.systemui.bubbles.BubbleMovementHelper.EDGE_OVERLAP;
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.statusbar.notification.NotificationAlertingManager.alertAgain;
 
+import android.annotation.Nullable;
+import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
 
-import androidx.annotation.Nullable;
-
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotificationInflater;
 import com.android.systemui.statusbar.phone.StatusBarWindowController;
 
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -66,8 +75,6 @@
 
     // Enables some subset of notifs to automatically become bubbles
     private static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false;
-    // When a bubble is dismissed, recreate it as a notification
-    private static final boolean DEBUG_DEMOTE_TO_NOTIF = false;
 
     // Secure settings
     private static final String ENABLE_AUTO_BUBBLE_MESSAGES = "experiment_autobubble_messaging";
@@ -80,6 +87,7 @@
     private final NotificationEntryManager mNotificationEntryManager;
     private BubbleStateChangeListener mStateChangeListener;
     private BubbleExpandListener mExpandListener;
+    private LayoutInflater mInflater;
 
     private final Map<String, BubbleView> mBubbles = new HashMap<>();
     private BubbleStackView mStackView;
@@ -87,6 +95,12 @@
 
     // Bubbles get added to the status bar view
     private final StatusBarWindowController mStatusBarWindowController;
+    private StatusBarStateListener mStatusBarStateListener;
+
+    private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider =
+            Dependency.get(NotificationInterruptionStateProvider.class);
+
+    private INotificationManager mNotificationManagerService;
 
     // Used for determining view rect for touch interaction
     private Rect mTempRect = new Rect();
@@ -107,23 +121,53 @@
     public interface BubbleExpandListener {
         /**
          * Called when the expansion state of the bubble stack changes.
-         *
          * @param isExpanding whether it's expanding or collapsing
-         * @param amount fraction of how expanded or collapsed it is, 1 being fully, 0 at the start
+         * @param key the notification key associated with bubble being expanded
          */
-        void onBubbleExpandChanged(boolean isExpanding, float amount);
+        void onBubbleExpandChanged(boolean isExpanding, String key);
+    }
+
+    /**
+     * Listens for the current state of the status bar and updates the visibility state
+     * of bubbles as needed.
+     */
+    private class StatusBarStateListener implements StatusBarStateController.StateListener {
+        private int mState;
+        /**
+         * Returns the current status bar state.
+         */
+        public int getCurrentState() {
+            return mState;
+        }
+
+        @Override
+        public void onStateChanged(int newState) {
+            mState = newState;
+            updateVisibility();
+        }
     }
 
     @Inject
     public BubbleController(Context context, StatusBarWindowController statusBarWindowController) {
         mContext = context;
-        mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
         WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         mDisplaySize = new Point();
         wm.getDefaultDisplay().getSize(mDisplaySize);
-        mStatusBarWindowController = statusBarWindowController;
+        mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 
+        mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
         mNotificationEntryManager.addNotificationEntryListener(mEntryListener);
+
+        try {
+            mNotificationManagerService = INotificationManager.Stub.asInterface(
+                    ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE));
+        } catch (ServiceManager.ServiceNotFoundException e) {
+            e.printStackTrace();
+        }
+
+        mStatusBarWindowController = statusBarWindowController;
+        mStatusBarStateListener = new StatusBarStateListener();
+        Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener);
     }
 
     /**
@@ -148,7 +192,12 @@
      * screen (e.g. if on AOD).
      */
     public boolean hasBubbles() {
-        return mBubbles.size() > 0;
+        for (BubbleView bv : mBubbles.values()) {
+            if (!bv.getEntry().isBubbleDismissed()) {
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
@@ -163,7 +212,7 @@
      */
     public void collapseStack() {
         if (mStackView != null) {
-            mStackView.animateExpansion(false);
+            mStackView.collapseStack();
         }
     }
 
@@ -174,33 +223,32 @@
         if (mStackView == null) {
             return;
         }
-        Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize);
-        // Reset the position of the stack (TODO - or should we save / respect last user position?)
-        mStackView.setPosition(startPoint.x, startPoint.y);
-        for (String key: mBubbles.keySet()) {
-            removeBubble(key);
+        Set<String> keys = mBubbles.keySet();
+        for (String key: keys) {
+            mBubbles.get(key).getEntry().setBubbleDismissed(true);
         }
+        mStackView.stackDismissed();
+
+        // Reset the position of the stack (TODO - or should we save / respect last user position?)
+        Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize);
+        mStackView.setPosition(startPoint.x, startPoint.y);
+
+        updateVisibility();
         mNotificationEntryManager.updateNotifications();
-        updateBubblesShowing();
     }
 
     /**
-     * Adds a bubble associated with the provided notification entry or updates it if it exists.
+     * Adds or updates a bubble associated with the provided notification entry.
+     *
+     * @param notif the notification associated with this bubble.
+     * @param updatePosition whether this update should promote the bubble to the top of the stack.
      */
-    public void addBubble(NotificationEntry notif) {
+    public void updateBubble(NotificationEntry notif, boolean updatePosition) {
         if (mBubbles.containsKey(notif.key)) {
             // It's an update
             BubbleView bubble = mBubbles.get(notif.key);
-            mStackView.updateBubble(bubble, notif);
+            mStackView.updateBubble(bubble, notif, updatePosition);
         } else {
-            // It's new
-            BubbleView bubble = new BubbleView(mContext);
-            bubble.setNotif(notif);
-            if (shouldUseActivityView(mContext)) {
-                bubble.setAppOverlayIntent(getAppOverlayIntent(notif));
-            }
-            mBubbles.put(bubble.getKey(), bubble);
-
             boolean setPosition = mStackView != null && mStackView.getVisibility() != VISIBLE;
             if (mStackView == null) {
                 setPosition = true;
@@ -215,15 +263,22 @@
                     mStackView.setExpandListener(mExpandListener);
                 }
             }
+            // It's new
+            BubbleView bubble = (BubbleView) mInflater.inflate(
+                    R.layout.bubble_view, mStackView, false /* attachToRoot */);
+            bubble.setNotif(notif);
+            if (shouldUseActivityView(mContext)) {
+                bubble.setAppOverlayIntent(getAppOverlayIntent(notif));
+            }
+            mBubbles.put(bubble.getKey(), bubble);
             mStackView.addBubble(bubble);
             if (setPosition) {
                 // Need to add the bubble to the stack before we can know the width
                 Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize);
                 mStackView.setPosition(startPoint.x, startPoint.y);
-                mStackView.setVisibility(VISIBLE);
             }
-            updateBubblesShowing();
         }
+        updateVisibility();
     }
 
     @Nullable
@@ -245,79 +300,96 @@
      * Removes the bubble associated with the {@param uri}.
      */
     void removeBubble(String key) {
-        BubbleView bv = mBubbles.get(key);
+        BubbleView bv = mBubbles.remove(key);
         if (mStackView != null && bv != null) {
             mStackView.removeBubble(bv);
             bv.destroyActivityView(mStackView);
-            bv.getEntry().setBubbleDismissed(true);
         }
 
-        NotificationEntry entry = mNotificationEntryManager.getNotificationData().get(key);
+        NotificationEntry entry = bv != null ? bv.getEntry() : null;
         if (entry != null) {
             entry.setBubbleDismissed(true);
-            if (!DEBUG_DEMOTE_TO_NOTIF) {
-                mNotificationEntryManager.performRemoveNotification(entry.notification);
-            }
+            mNotificationEntryManager.updateNotifications();
         }
-        mNotificationEntryManager.updateNotifications();
-
-        updateBubblesShowing();
+        updateVisibility();
     }
 
     @SuppressWarnings("FieldCanBeLocal")
     private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
         @Override
         public void onPendingEntryAdded(NotificationEntry entry) {
-            if (shouldAutoBubble(mContext, entry)) {
+            if (shouldAutoBubble(mContext, entry) || shouldBubble(entry)) {
+                // TODO: handle group summaries
+                // It's a new notif, it shows in the shade and as a bubble
                 entry.setIsBubble(true);
+                entry.setShowInShadeWhenBubble(true);
+            }
+        }
+
+        @Override
+        public void onEntryInflated(NotificationEntry entry,
+                @NotificationInflater.InflationFlag int inflatedFlags) {
+            if (entry.isBubble() && mNotificationInterruptionStateProvider.shouldBubbleUp(entry)) {
+                updateBubble(entry, true /* updatePosition */);
+            }
+        }
+
+        @Override
+        public void onPreEntryUpdated(NotificationEntry entry) {
+            if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
+                    && alertAgain(entry, entry.notification.getNotification())) {
+                entry.setShowInShadeWhenBubble(true);
+                entry.setBubbleDismissed(false); // updates come back as bubbles even if dismissed
+                if (mBubbles.containsKey(entry.key)) {
+                    mBubbles.get(entry.key).updateDotVisibility();
+                }
+                updateBubble(entry, true /* updatePosition */);
+            }
+        }
+
+        @Override
+        public void onEntryRemoved(NotificationEntry entry,
+                @Nullable NotificationVisibility visibility,
+                boolean removedByUser) {
+            entry.setShowInShadeWhenBubble(false);
+            if (mBubbles.containsKey(entry.key)) {
+                mBubbles.get(entry.key).updateDotVisibility();
+            }
+            if (!removedByUser) {
+                // This was a cancel so we should remove the bubble
+                removeBubble(entry.key);
             }
         }
     };
 
+    /**
+     * Lets any listeners know if bubble state has changed.
+     */
     private void updateBubblesShowing() {
-        boolean hasBubblesShowing = false;
-        for (BubbleView bv : mBubbles.values()) {
-            if (!bv.getEntry().isBubbleDismissed()) {
-                hasBubblesShowing = true;
-                break;
-            }
+        if (mStackView == null) {
+            return;
         }
+
         boolean hadBubbles = mStatusBarWindowController.getBubblesShowing();
+        boolean hasBubblesShowing = hasBubbles() && mStackView.getVisibility() == VISIBLE;
         mStatusBarWindowController.setBubblesShowing(hasBubblesShowing);
-        if (mStackView != null && !hasBubblesShowing) {
-            mStackView.setVisibility(INVISIBLE);
-        }
         if (mStateChangeListener != null && hadBubbles != hasBubblesShowing) {
             mStateChangeListener.onHasBubblesChanged(hasBubblesShowing);
         }
     }
 
     /**
-     * Sets the visibility of the bubbles, doesn't un-bubble them, just changes visibility.
+     * Updates the visibility of the bubbles based on current state.
+     * Does not un-bubble, just hides or un-hides. Will notify any
+     * {@link BubbleStateChangeListener}s if visibility changes.
      */
-    public void updateVisibility(boolean visible) {
-        if (mStackView == null) {
-            return;
-        }
-        ArrayList<BubbleView> viewsToRemove = new ArrayList<>();
-        for (BubbleView bv : mBubbles.values()) {
-            NotificationEntry entry = bv.getEntry();
-            if (entry != null) {
-                if (entry.isRowRemoved() || entry.isBubbleDismissed() || entry.isRowDismissed()) {
-                    viewsToRemove.add(bv);
-                }
-            }
-        }
-        for (BubbleView bubbleView : viewsToRemove) {
-            mBubbles.remove(bubbleView.getKey());
-            mStackView.removeBubble(bubbleView);
-            bubbleView.destroyActivityView(mStackView);
-        }
-        if (mStackView != null) {
-            mStackView.setVisibility(visible ? VISIBLE : INVISIBLE);
-            if (!visible) {
-                collapseStack();
-            }
+    public void updateVisibility() {
+        if (mStatusBarStateListener.getCurrentState() == SHADE && hasBubbles()) {
+            // Bubbles only appear in unlocked shade
+            mStackView.setVisibility(hasBubbles() ? VISIBLE : INVISIBLE);
+        } else if (mStackView != null) {
+            mStackView.setVisibility(INVISIBLE);
+            collapseStack();
         }
         updateBubblesShowing();
     }
@@ -368,18 +440,41 @@
     }
 
     /**
-     * Whether the notification should bubble or not.
+     * Whether the notification has been developer configured to bubble and is allowed by the user.
      */
-    private static boolean shouldAutoBubble(Context context, NotificationEntry entry) {
+    private boolean shouldBubble(NotificationEntry entry) {
+        StatusBarNotification n = entry.notification;
+        boolean canAppOverlay = false;
+        try {
+            canAppOverlay = mNotificationManagerService.areAppOverlaysAllowedForPackage(
+                    n.getPackageName(), n.getUid());
+        } catch (RemoteException e) {
+            Log.w(TAG, "Error calling NoMan to determine if app can overlay", e);
+        }
+
+        boolean canChannelOverlay = mNotificationEntryManager.getNotificationData().getChannel(
+                entry.key).canOverlayApps();
+        boolean hasOverlayIntent = n.getNotification().getAppOverlayIntent() != null;
+        return hasOverlayIntent && canChannelOverlay && canAppOverlay;
+    }
+
+    /**
+     * Whether the notification should bubble or not. Gated by debug flag.
+     * <p>
+     * If a notification has been set to bubble via proper bubble APIs or if it is an important
+     * message-like notification.
+     * </p>
+     */
+    private boolean shouldAutoBubble(Context context, NotificationEntry entry) {
         if (entry.isBubbleDismissed()) {
             return false;
         }
+        StatusBarNotification n = entry.notification;
 
         boolean autoBubbleMessages = shouldAutoBubbleMessages(context) || DEBUG_ENABLE_AUTO_BUBBLE;
         boolean autoBubbleOngoing = shouldAutoBubbleOngoing(context) || DEBUG_ENABLE_AUTO_BUBBLE;
         boolean autoBubbleAll = shouldAutoBubbleAll(context) || DEBUG_ENABLE_AUTO_BUBBLE;
 
-        StatusBarNotification n = entry.notification;
         boolean hasRemoteInput = false;
         if (n.getNotification().actions != null) {
             for (Notification.Action action : n.getNotification().actions) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
index badefe1..71ae1f8 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
@@ -21,6 +21,7 @@
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.drawable.ShapeDrawable;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.LinearLayout;
@@ -88,6 +89,7 @@
      */
     public void setHeaderText(CharSequence text) {
         mHeaderView.setText(text);
+        mHeaderView.setVisibility(TextUtils.isEmpty(text) ? GONE : VISIBLE);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 3280a33..9a11b96 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -64,9 +64,9 @@
 
     private boolean mIsExpanded;
     private int mExpandedBubbleHeight;
+    private BubbleTouchHandler mTouchHandler;
     private BubbleView mExpandedBubble;
     private Point mCollapsedPosition;
-    private BubbleTouchHandler mTouchHandler;
     private BubbleController.BubbleExpandListener mExpandListener;
 
     private boolean mViewUpdatedRequested = false;
@@ -211,13 +211,24 @@
      */
     public void setExpandedBubble(BubbleView bubbleToExpand) {
         mExpandedBubble = bubbleToExpand;
+        boolean prevExpanded = mIsExpanded;
         mIsExpanded = true;
-        updateExpandedBubble();
-        requestUpdate();
+        if (!prevExpanded) {
+            // If we weren't previously expanded we should animate open.
+            animateExpansion(true /* expand */);
+        } else {
+            // If we were expanded just update the views
+            updateExpandedBubble();
+            requestUpdate();
+        }
+        mExpandedBubble.getEntry().setShowInShadeWhenBubble(false);
+        notifyExpansionChanged(mExpandedBubble, true /* expanded */);
     }
 
     /**
-     * Adds a bubble to the stack.
+     * Adds a bubble to the top of the stack.
+     *
+     * @param bubbleView the view to add to the stack.
      */
     public void addBubble(BubbleView bubbleView) {
         mBubbleContainer.addView(bubbleView, 0,
@@ -234,17 +245,26 @@
         mBubbleContainer.removeView(bubbleView);
         boolean wasExpanded = mIsExpanded;
         int bubbleCount = mBubbleContainer.getChildCount();
-        if (bubbleView.equals(mExpandedBubble) && bubbleCount > 0) {
+        if (mIsExpanded && bubbleView.equals(mExpandedBubble) && bubbleCount > 0) {
             // If we have other bubbles and are expanded go to the next one or previous
             // if the bubble removed was last
             int nextIndex = bubbleCount > removedIndex ? removedIndex : bubbleCount - 1;
-            mExpandedBubble = (BubbleView) mBubbleContainer.getChildAt(nextIndex);
+            BubbleView expandedBubble = (BubbleView) mBubbleContainer.getChildAt(nextIndex);
+            setExpandedBubble(expandedBubble);
         }
         mIsExpanded = wasExpanded && mBubbleContainer.getChildCount() > 0;
-        requestUpdate();
-        if (wasExpanded && !mIsExpanded && mExpandListener != null) {
-            mExpandListener.onBubbleExpandChanged(mIsExpanded, 1 /* amount */);
+        if (wasExpanded != mIsExpanded) {
+            notifyExpansionChanged(mExpandedBubble, mIsExpanded);
         }
+        requestUpdate();
+    }
+
+    /**
+     * Dismiss the stack of bubbles.
+     */
+    public void stackDismissed() {
+        collapseStack();
+        mBubbleContainer.removeAllViews();
     }
 
     /**
@@ -252,11 +272,19 @@
      *
      * @param bubbleView the view to update in the stack.
      * @param entry the entry to update it with.
+     * @param updatePosition whether this bubble should be moved to top of the stack.
      */
-    public void updateBubble(BubbleView bubbleView, NotificationEntry entry) {
-        // TODO - move to top of bubble stack, make it show its update if it makes sense
+    public void updateBubble(BubbleView bubbleView, NotificationEntry entry,
+            boolean updatePosition) {
         bubbleView.update(entry);
-        if (bubbleView.equals(mExpandedBubble)) {
+        if (updatePosition && !mIsExpanded) {
+            // If alerting it gets promoted to top of the stack
+            mBubbleContainer.removeView(bubbleView);
+            mBubbleContainer.addView(bubbleView, 0);
+            requestUpdate();
+        }
+        if (mIsExpanded && bubbleView.equals(mExpandedBubble)) {
+            entry.setShowInShadeWhenBubble(false);
             requestUpdate();
         }
     }
@@ -287,17 +315,36 @@
     }
 
     /**
+     * Collapses the stack of bubbles.
+     */
+    public void collapseStack() {
+        if (mIsExpanded) {
+            // TODO: Save opened bubble & move it to top of stack
+            animateExpansion(false /* shouldExpand */);
+            notifyExpansionChanged(mExpandedBubble, mIsExpanded);
+        }
+    }
+
+    /**
+     * Expands the stack fo bubbles.
+     */
+    public void expandStack() {
+        if (!mIsExpanded) {
+            mExpandedBubble = getTopBubble();
+            mExpandedBubble.getEntry().setShowInShadeWhenBubble(false);
+            animateExpansion(true /* shouldExpand */);
+            notifyExpansionChanged(mExpandedBubble, true /* expanded */);
+        }
+    }
+
+    /**
      * Tell the stack to animate to collapsed or expanded state.
      */
-    public void animateExpansion(boolean shouldExpand) {
+    private void animateExpansion(boolean shouldExpand) {
         if (mIsExpanded != shouldExpand) {
             mIsExpanded = shouldExpand;
-            mExpandedBubble = shouldExpand ? getTopBubble() : null;
             updateExpandedBubble();
 
-            if (mExpandListener != null) {
-                mExpandListener.onBubbleExpandChanged(mIsExpanded, 1 /* amount */);
-            }
             if (shouldExpand) {
                 // Save current position so that we might return there
                 savePosition();
@@ -347,6 +394,13 @@
         mCollapsedPosition = getPosition();
     }
 
+    private void notifyExpansionChanged(BubbleView bubbleView, boolean expanded) {
+        if (mExpandListener != null) {
+            NotificationEntry entry = bubbleView != null ? bubbleView.getEntry() : null;
+            mExpandListener.onBubbleExpandChanged(expanded, entry != null ? entry.key : null);
+        }
+    }
+
     private BubbleView getTopBubble() {
         return getBubbleAt(0);
     }
@@ -400,6 +454,7 @@
         }
 
         if (mExpandedBubble.hasAppOverlayIntent()) {
+            // Bubble with activity view expanded state
             ActivityView expandedView = mExpandedBubble.getActivityView();
             expandedView.setLayoutParams(new ViewGroup.LayoutParams(
                     ViewGroup.LayoutParams.MATCH_PARENT, mExpandedBubbleHeight));
@@ -417,19 +472,27 @@
 
                 @Override
                 public void onActivityViewDestroyed(ActivityView view) {
-                    NotificationEntry entry = mExpandedBubble.getEntry();
+                    NotificationEntry entry = mExpandedBubble != null
+                            ? mExpandedBubble.getEntry() : null;
                     Log.d(TAG, "onActivityViewDestroyed(key="
                             + ((entry != null) ? entry.key : "(none)") + "): " + view);
                 }
             });
         } else {
+            // Bubble with notification view expanded state
             ExpandableNotificationRow row = mExpandedBubble.getRowView();
-            if (!row.equals(mExpandedViewContainer.getExpandedView())) {
-                // Different expanded view than what we have
+            if (row.getParent() != null) {
+                // Row might still be in the shade when we expand
+                ((ViewGroup) row.getParent()).removeView(row);
+            }
+            if (mIsExpanded) {
+                mExpandedViewContainer.setExpandedView(row);
+            } else {
                 mExpandedViewContainer.setExpandedView(null);
             }
-            mExpandedViewContainer.setExpandedView(row);
+            // Bubble with notification as expanded state doesn't need a header / title
             mExpandedViewContainer.setHeaderText(null);
+
         }
         int pointerPosition = mExpandedBubble.getPosition().x
                 + (mExpandedBubble.getWidth() / 2);
@@ -456,7 +519,8 @@
         int bubbsCount = mBubbleContainer.getChildCount();
         for (int i = 0; i < bubbsCount; i++) {
             BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
-            bv.setZ(bubbsCount - 1);
+            bv.updateDotVisibility();
+            bv.setZ(bubbsCount - i);
 
             int transX = mIsExpanded ? (bv.getWidth() + mBubblePadding) * i : mBubblePadding * i;
             ViewState viewState = new ViewState();
@@ -510,6 +574,7 @@
     private void applyRowState(ExpandableNotificationRow view) {
         view.reset();
         view.setHeadsUp(false);
+        view.resetTranslation();
         view.setOnKeyguard(false);
         view.setOnAmbient(false);
         view.setClipBottomAmount(0);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index 96b2dba..97784b0 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -110,7 +110,7 @@
                 : stack.getTargetView(event);
         boolean isFloating = targetView instanceof FloatingView;
         if (!isFloating || targetView == null || action == MotionEvent.ACTION_OUTSIDE) {
-            stack.animateExpansion(false /* shouldExpand */);
+            stack.collapseStack();
             cleanUpDismissTarget();
             resetTouches();
             return false;
@@ -196,9 +196,13 @@
                         mMovementHelper.getTranslateAnim(floatingView, toGoTo, 100, 0).start();
                     }
                 } else if (floatingView.equals(stack.getExpandedBubble())) {
-                    stack.animateExpansion(false /* shouldExpand */);
+                    stack.collapseStack();
                 } else if (isBubbleStack) {
-                    stack.animateExpansion(!stack.isExpanded() /* shouldExpand */);
+                    if (stack.isExpanded()) {
+                        stack.collapseStack();
+                    } else {
+                        stack.expandStack();
+                    }
                 } else {
                     stack.setExpandedBubble((BubbleView) floatingView);
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index c1bbb93..91893ef 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -16,40 +16,47 @@
 
 package com.android.systemui.bubbles;
 
+import android.annotation.Nullable;
 import android.app.ActivityView;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.graphics.Color;
 import android.graphics.Point;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
-import android.graphics.drawable.ShapeDrawable;
-import android.graphics.drawable.shapes.OvalShape;
+import android.graphics.drawable.InsetDrawable;
+import android.graphics.drawable.LayerDrawable;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
+import android.widget.FrameLayout;
+import android.widget.TextView;
 
-import com.android.internal.util.ContrastColorUtil;
+import com.android.internal.graphics.ColorUtils;
+import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
 /**
- * A floating object on the screen that has a collapsed and expanded state.
+ * A floating object on the screen that can post message updates.
  */
-class BubbleView extends LinearLayout implements BubbleTouchHandler.FloatingView {
+public class BubbleView extends FrameLayout implements BubbleTouchHandler.FloatingView {
     private static final String TAG = "BubbleView";
 
+    // Same value as Launcher3 badge code
+    private static final float WHITE_SCRIM_ALPHA = 0.54f;
     private Context mContext;
-    private View mIconView;
+
+    private BadgedImageView mBadgedImageView;
+    private TextView mMessageView;
+    private int mPadding;
+    private int mIconInset;
 
     private NotificationEntry mEntry;
-    private int mBubbleSize;
-    private int mIconSize;
     private PendingIntent mAppOverlayIntent;
     private ActivityView mActivityView;
 
@@ -67,66 +74,156 @@
 
     public BubbleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        setOrientation(LinearLayout.VERTICAL);
         mContext = context;
-        mBubbleSize = getResources().getDimensionPixelSize(R.dimen.bubble_size);
-        mIconSize = getResources().getDimensionPixelSize(R.dimen.bubble_icon_size);
+        // XXX: can this padding just be on the view and we look it up?
+        mPadding = getResources().getDimensionPixelSize(R.dimen.bubble_view_padding);
+        mIconInset = getResources().getDimensionPixelSize(R.dimen.bubble_icon_inset);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mBadgedImageView = (BadgedImageView) findViewById(R.id.bubble_image);
+        mMessageView = (TextView) findViewById(R.id.message_view);
+        mMessageView.setVisibility(GONE);
+        mMessageView.setPivotX(0);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        updateViews();
+    }
+
+    @Override
+    protected void onMeasure(int widthSpec, int heightSpec) {
+        measureChild(mBadgedImageView, widthSpec, heightSpec);
+        measureChild(mMessageView, widthSpec, heightSpec);
+        boolean messageGone = mMessageView.getVisibility() == GONE;
+        int imageHeight = mBadgedImageView.getMeasuredHeight();
+        int imageWidth = mBadgedImageView.getMeasuredWidth();
+        int messageHeight = messageGone ? 0 : mMessageView.getMeasuredHeight();
+        int messageWidth = messageGone ? 0 : mMessageView.getMeasuredWidth();
+        setMeasuredDimension(
+                getPaddingStart() + imageWidth + mPadding + messageWidth + getPaddingEnd(),
+                getPaddingTop() + Math.max(imageHeight, messageHeight) + getPaddingBottom());
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        left = getPaddingStart();
+        top = getPaddingTop();
+        int imageWidth = mBadgedImageView.getMeasuredWidth();
+        int imageHeight = mBadgedImageView.getMeasuredHeight();
+        int messageWidth = mMessageView.getMeasuredWidth();
+        int messageHeight = mMessageView.getMeasuredHeight();
+        mBadgedImageView.layout(left, top, left + imageWidth, top + imageHeight);
+        mMessageView.layout(left + imageWidth + mPadding, top,
+                left + imageWidth + mPadding + messageWidth, top + messageHeight);
     }
 
     /**
      * Populates this view with a notification.
+     * <p>
+     * This should only be called when a new notification is being set on the view, updates to the
+     * current notification should use {@link #update(NotificationEntry)}.
      *
      * @param entry the notification to display as a bubble.
      */
     public void setNotif(NotificationEntry entry) {
-        removeAllViews();
-        // TODO: migrate to inflater
-        mIconView = new ImageView(mContext);
-        addView(mIconView);
-
-        LinearLayout.LayoutParams iconLp = (LinearLayout.LayoutParams) mIconView.getLayoutParams();
-        iconLp.width = mBubbleSize;
-        iconLp.height = mBubbleSize;
-        mIconView.setLayoutParams(iconLp);
-
-        update(entry);
-    }
-
-    /**
-     * Updates the UI based on the entry.
-     */
-    public void update(NotificationEntry entry) {
         mEntry = entry;
-        Notification n = entry.notification.getNotification();
-        Icon ic = n.getLargeIcon() != null ? n.getLargeIcon() : n.getSmallIcon();
-
-        if (n.getLargeIcon() == null) {
-            createCircledIcon(n.color, ic, ((ImageView) mIconView));
-        } else {
-            ((ImageView) mIconView).setImageIcon(ic);
-        }
+        updateViews();
     }
 
     /**
-     * @return the key identifying this bubble / notification entry associated with this
-     * bubble, if it exists.
+     * The {@link NotificationEntry} associated with this view, if one exists.
      */
-    public String getKey() {
-        return mEntry == null ? null : mEntry.key;
-    }
-
-    /**
-     * @return the notification entry associated with this bubble.
-     */
+    @Nullable
     public NotificationEntry getEntry() {
         return mEntry;
     }
 
     /**
-     * @return the view to display notification content when the bubble is expanded.
+     * The key for the {@link NotificationEntry} associated with this view, if one exists.
      */
+    @Nullable
+    public String getKey() {
+        return (mEntry != null) ? mEntry.key : null;
+    }
+
+    /**
+     * Updates the UI based on the entry, updates badge and animates messages as needed.
+     */
+    public void update(NotificationEntry entry) {
+        mEntry = entry;
+        updateViews();
+    }
+
+
+    /**
+     * @return the {@link ExpandableNotificationRow} view to display notification content when the
+     * bubble is expanded.
+     */
+    @Nullable
     public ExpandableNotificationRow getRowView() {
-        return mEntry.getRow();
+        return (mEntry != null) ? mEntry.getRow() : null;
+    }
+
+    /**
+     * Marks this bubble as "read", i.e. no badge should show.
+     */
+    public void updateDotVisibility() {
+        boolean showDot = getEntry().showInShadeWhenBubble();
+        animateDot(showDot);
+    }
+
+    /**
+     * Animates the badge to show or hide.
+     */
+    private void animateDot(boolean showDot) {
+        if (mBadgedImageView.isShowingDot() != showDot) {
+            mBadgedImageView.setShowDot(showDot);
+            mBadgedImageView.clearAnimation();
+            mBadgedImageView.animate().setDuration(200)
+                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+                    .setUpdateListener((valueAnimator) -> {
+                        float fraction = valueAnimator.getAnimatedFraction();
+                        fraction = showDot ? fraction : 1 - fraction;
+                        mBadgedImageView.setDotScale(fraction);
+                    }).withEndAction(() -> {
+                        if (!showDot) {
+                            mBadgedImageView.setShowDot(false);
+                        }
+                    }).start();
+        }
+    }
+
+    private void updateViews() {
+        if (mEntry == null) {
+            return;
+        }
+        Notification n = mEntry.notification.getNotification();
+        boolean isLarge = n.getLargeIcon() != null;
+        Icon ic = isLarge ? n.getLargeIcon() : n.getSmallIcon();
+        Drawable iconDrawable = ic.loadDrawable(mContext);
+        if (!isLarge) {
+            // Center icon on coloured background
+            iconDrawable.setTint(Color.WHITE); // TODO: dark mode
+            Drawable bg = new ColorDrawable(n.color);
+            InsetDrawable d = new InsetDrawable(iconDrawable, mIconInset);
+            Drawable[] layers = {bg, d};
+            mBadgedImageView.setImageDrawable(new LayerDrawable(layers));
+        } else {
+            mBadgedImageView.setImageDrawable(iconDrawable);
+        }
+        int badgeColor = determineDominateColor(iconDrawable, n.color);
+        mBadgedImageView.setDotColor(badgeColor);
+        animateDot(mEntry.showInShadeWhenBubble() /* showDot */);
+    }
+
+    private int determineDominateColor(Drawable d, int defaultTint) {
+        // XXX: should we pull from the drawable, app icon, notif tint?
+        return ColorUtils.blendARGB(defaultTint, Color.WHITE, WHITE_SCRIM_ALPHA);
     }
 
     /**
@@ -170,8 +267,8 @@
 
     @Override
     public void setPosition(int x, int y) {
-        setTranslationX(x);
-        setTranslationY(y);
+        setPositionX(x);
+        setPositionY(y);
     }
 
     @Override
@@ -189,25 +286,6 @@
         return new Point((int) getTranslationX(), (int) getTranslationY());
     }
 
-    // Seems sub optimal
-    private void createCircledIcon(int tint, Icon icon, ImageView v) {
-        // TODO: dark mode
-        icon.setTint(Color.WHITE);
-        icon.scaleDownIfNecessary(mIconSize, mIconSize);
-        v.setImageDrawable(icon.loadDrawable(mContext));
-        v.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
-        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) v.getLayoutParams();
-        int color = ContrastColorUtil.ensureContrast(tint, Color.WHITE,
-                false /* isBgDarker */, 3);
-        Drawable d = new ShapeDrawable(new OvalShape());
-        d.setTint(color);
-        v.setBackgroundDrawable(d);
-
-        lp.width = mBubbleSize;
-        lp.height = mBubbleSize;
-        v.setLayoutParams(lp);
-    }
-
     /**
      * @return whether an ActivityView should be used to display the content of this Bubble
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index bf6caa0..f2ff85b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar;
 
-import static com.android.systemui.statusbar.StatusBarState.SHADE;
-
 import android.content.Context;
 import android.content.res.Resources;
 import android.os.Trace;
@@ -26,7 +24,6 @@
 import android.view.ViewGroup;
 
 import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -66,7 +63,6 @@
     protected final VisualStabilityManager mVisualStabilityManager;
     private final StatusBarStateController mStatusBarStateController;
     private final NotificationEntryManager mEntryManager;
-    private final BubbleController mBubbleController;
 
     // Lazy
     private final Lazy<ShadeController> mShadeController;
@@ -80,41 +76,6 @@
 
     private NotificationPresenter mPresenter;
     private NotificationListContainer mListContainer;
-    private StatusBarStateListener mStatusBarStateListener;
-
-    /**
-     * Listens for the current state of the status bar and updates the visibility state
-     * of bubbles as needed.
-     */
-    public class StatusBarStateListener implements StatusBarStateController.StateListener {
-        private int mState;
-        private BubbleController mController;
-
-        public StatusBarStateListener(BubbleController controller) {
-            mController = controller;
-        }
-
-        /**
-         * Returns the current status bar state.
-         */
-        public int getCurrentState() {
-            return mState;
-        }
-
-        @Override
-        public void onStateChanged(int newState) {
-            mState = newState;
-            // Order here matters because we need to remove the expandable notification row
-            // from it's current parent (NSSL or bubble) before it can be added to the new parent
-            if (mState == SHADE) {
-                updateNotificationViews();
-                mController.updateVisibility(true);
-            } else {
-                mController.updateVisibility(false);
-                updateNotificationViews();
-            }
-        }
-    }
 
     @Inject
     public NotificationViewHierarchyManager(Context context,
@@ -123,20 +84,16 @@
             VisualStabilityManager visualStabilityManager,
             StatusBarStateController statusBarStateController,
             NotificationEntryManager notificationEntryManager,
-            BubbleController bubbleController,
             Lazy<ShadeController> shadeController) {
         mLockscreenUserManager = notificationLockscreenUserManager;
         mGroupManager = groupManager;
         mVisualStabilityManager = visualStabilityManager;
         mStatusBarStateController = statusBarStateController;
         mEntryManager = notificationEntryManager;
-        mBubbleController = bubbleController;
         mShadeController = shadeController;
         Resources res = context.getResources();
         mAlwaysExpandNonGroupedNotification =
                 res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
-        mStatusBarStateListener = new StatusBarStateListener(mBubbleController);
-        mStatusBarStateController.addCallback(mStatusBarStateListener);
     }
 
     public void setUpWithPresenter(NotificationPresenter presenter,
@@ -153,7 +110,6 @@
         ArrayList<NotificationEntry> activeNotifications = mEntryManager.getNotificationData()
                 .getActiveNotifications();
         ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
-        ArrayList<NotificationEntry> toBubble = new ArrayList<>();
         final int N = activeNotifications.size();
         for (int i = 0; i < N; i++) {
             NotificationEntry ent = activeNotifications.get(i);
@@ -162,13 +118,6 @@
                 // temporarily become children if they were isolated before.
                 continue;
             }
-            ent.getRow().setStatusBarState(mStatusBarStateListener.getCurrentState());
-            boolean showAsBubble = ent.isBubble() && !ent.isBubbleDismissed()
-                    && mStatusBarStateListener.getCurrentState() == SHADE;
-            if (showAsBubble) {
-                toBubble.add(ent);
-                continue;
-            }
 
             int userId = ent.notification.getUserId();
 
@@ -269,12 +218,6 @@
 
         }
 
-        for (int i = 0; i < toBubble.size(); i++) {
-            // TODO: might make sense to leave them in the shade and just reposition them
-            NotificationEntry ent = toBubble.get(i);
-            mBubbleController.addBubble(ent);
-        }
-
         mVisualStabilityManager.onReorderingFinished();
         // clear the map again for the next usage
         mTmpChildOrderMap.clear();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
index 60d8cf4..5605f3d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
@@ -150,7 +150,14 @@
         }
     }
 
-    private static boolean alertAgain(
+    /**
+     * Checks whether an update for a notification warrants an alert for the user.
+     *
+     * @param oldEntry the entry for this notification.
+     * @param newNotification the new notification for this entry.
+     * @return whether this notification should alert the user.
+     */
+    public static boolean alertAgain(
             NotificationEntry oldEntry, Notification newNotification) {
         return oldEntry == null || !oldEntry.hasInterrupted()
                 || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
index e199ead..154d7b35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
@@ -134,6 +134,10 @@
             }
         }
 
+        if (entry.isBubble() && !entry.showInShadeWhenBubble()) {
+            return true;
+        }
+
         return false;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
index fc7a2b3..c50f10b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
@@ -135,6 +135,29 @@
     }
 
     /**
+     * Whether the notification should appear as a bubble with a fly-out on top of the screen.
+     *
+     * @param entry the entry to check
+     * @return true if the entry should bubble up, false otherwise
+     */
+    public boolean shouldBubbleUp(NotificationEntry entry) {
+        StatusBarNotification sbn = entry.notification;
+        if (!entry.isBubble()) {
+            if (DEBUG) {
+                Log.d(TAG, "No bubble up: notification " + sbn.getKey()
+                        + " is bubble? " + entry.isBubble());
+            }
+            return false;
+        }
+
+        if (!canHeadsUpCommon(entry)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
      * Whether the notification should peek in from the top and alert the user.
      *
      * @param entry the entry to check
@@ -150,10 +173,12 @@
             return false;
         }
 
-        // TODO: need to changes this, e.g. should still heads up in expanded shade, might want
-        // message bubble from the bubble to go through heads up path
         boolean inShade = mStatusBarStateController.getState() == SHADE;
-        if (entry.isBubble() && !entry.isBubbleDismissed() && inShade) {
+        if (entry.isBubble() && inShade) {
+            if (DEBUG) {
+                Log.d(TAG, "No heads up: in unlocked shade where notification is shown as a "
+                        + "bubble: " + sbn.getKey());
+            }
             return false;
         }
 
@@ -164,9 +189,13 @@
             return false;
         }
 
-        if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
+        if (!canHeadsUpCommon(entry)) {
+            return false;
+        }
+
+        if (entry.importance < NotificationManager.IMPORTANCE_HIGH) {
             if (DEBUG) {
-                Log.d(TAG, "No heads up: no huns or vr mode");
+                Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey());
             }
             return false;
         }
@@ -186,34 +215,6 @@
             return false;
         }
 
-        if (entry.shouldSuppressPeek()) {
-            if (DEBUG) {
-                Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey());
-            }
-            return false;
-        }
-
-        if (isSnoozedPackage(sbn)) {
-            if (DEBUG) {
-                Log.d(TAG, "No heads up: snoozed package: " + sbn.getKey());
-            }
-            return false;
-        }
-
-        if (entry.hasJustLaunchedFullScreenIntent()) {
-            if (DEBUG) {
-                Log.d(TAG, "No heads up: recent fullscreen: " + sbn.getKey());
-            }
-            return false;
-        }
-
-        if (entry.importance < NotificationManager.IMPORTANCE_HIGH) {
-            if (DEBUG) {
-                Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey());
-            }
-            return false;
-        }
-
         if (!mHeadsUpSuppressor.canHeadsUp(entry, sbn)) {
             return false;
         }
@@ -302,6 +303,49 @@
         return true;
     }
 
+    /**
+     * Common checks between heads up alerting and bubble fly out alerting. See
+     * {@link #shouldHeadsUp(NotificationEntry)} and
+     * {@link #shouldBubbleUp(NotificationEntry)}. Notifications that fail any of these
+     * checks should not interrupt the user on screen.
+     *
+     * @param entry the entry to check
+     * @return true if these checks pass, false if the notification should not interrupt on screen
+     */
+    public boolean canHeadsUpCommon(NotificationEntry entry) {
+        StatusBarNotification sbn = entry.notification;
+
+        if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
+            if (DEBUG) {
+                Log.d(TAG, "No heads up: no huns or vr mode");
+            }
+            return false;
+        }
+
+        if (entry.shouldSuppressPeek()) {
+            if (DEBUG) {
+                Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey());
+            }
+            return false;
+        }
+
+        if (isSnoozedPackage(sbn)) {
+            if (DEBUG) {
+                Log.d(TAG, "No heads up: snoozed package: " + sbn.getKey());
+            }
+            return false;
+        }
+
+        if (entry.hasJustLaunchedFullScreenIntent()) {
+            if (DEBUG) {
+                Log.d(TAG, "No heads up: recent fullscreen: " + sbn.getKey());
+            }
+            return false;
+        }
+
+        return true;
+    }
+
     private boolean isSnoozedPackage(StatusBarNotification sbn) {
         return mHeadsUpManager.isSnoozed(sbn.getPackageName());
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 58aa02c..ee551ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -141,6 +141,14 @@
     private boolean mIsBubble;
 
     /**
+     * Whether this notification should be shown in the shade when it is also displayed as a bubble.
+     *
+     * <p>When a notification is a bubble we don't show it in the shade once the bubble has been
+     * expanded</p>
+     */
+    private boolean mShowInShadeWhenBubble;
+
+    /**
      * Whether the user has dismissed this notification when it was in bubble form.
      */
     private boolean mUserDismissedBubble;
@@ -200,6 +208,23 @@
     }
 
     /**
+     * Sets whether this notification should be shown in the shade when it is also displayed as a
+     * bubble.
+     */
+    public void setShowInShadeWhenBubble(boolean showInShade) {
+        mShowInShadeWhenBubble = showInShade;
+    }
+
+    /**
+     * Whether this notification should be shown in the shade when it is also displayed as a
+     * bubble.
+     */
+    public boolean showInShadeWhenBubble() {
+        // We always show it in the shade if non-clearable
+        return !isClearable() || mShowInShadeWhenBubble;
+    }
+
+    /**
      * Resets the notification entry to be re-used.
      */
     public void reset() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 95bd1ce..df0189f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.notification.row;
 
-import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_AMBIENT;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
@@ -2322,7 +2321,7 @@
     }
 
     private boolean isShownAsBubble() {
-        return mEntry.isBubble() && (mStatusBarState == SHADE || mStatusBarState == -1);
+        return mEntry.isBubble() && !mEntry.showInShadeWhenBubble() && !mEntry.isBubbleDismissed();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
index 607d96d..6df72fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
@@ -20,11 +20,13 @@
         .USER_SENTIMENT_NEGATIVE;
 
 import android.content.Context;
+import android.metrics.LogMaker;
 import android.util.Log;
 
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.Dependency;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -58,6 +60,8 @@
      */
     private boolean mIsShadeExpanded;
 
+    private MetricsLogger mMetricsLogger = new MetricsLogger();
+
     @Inject
     public NotificationBlockingHelperManager(Context context) {
         mContext = context;
@@ -100,6 +104,11 @@
             mBlockingHelperRow = row;
             mBlockingHelperRow.setBlockingHelperShowing(true);
 
+            // Log triggering of blocking helper by the system. This log line
+            // should be emitted before the "display" log line.
+            mMetricsLogger.write(
+                    getLogMaker().setSubtype(MetricsEvent.BLOCKING_HELPER_TRIGGERED_BY_SYSTEM));
+
             // We don't care about the touch origin (x, y) since we're opening guts without any
             // explicit user interaction.
             manager.openGuts(mBlockingHelperRow, 0, 0, menuRow.getLongpressMenuItem(mContext));
@@ -153,6 +162,13 @@
                 || mNonBlockablePkgs.contains(makeChannelKey(packageName, channelName));
     }
 
+    private LogMaker getLogMaker() {
+        return mBlockingHelperRow.getStatusBarNotification()
+            .getLogMaker()
+            .setCategory(MetricsEvent.NOTIFICATION_ITEM)
+            .setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER);
+    }
+
     // Format must stay in sync with frameworks/base/core/res/res/values/config.xml
     // config_nonBlockableNotificationPackages
     private String makeChannelKey(String pkg, String channel) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index bf30cf9..c161da3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1489,7 +1489,7 @@
                 }
             }
         }
-        if (mHeadsUpChild != null) {
+        if (mHeadsUpChild != null && mSmartReplyConstants.getShowInHeadsUp()) {
             mHeadsUpSmartReplyView =
                     applySmartReplyView(mHeadsUpChild, smartRepliesAndActions, entry);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index b1eab80..5253e38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -123,11 +123,15 @@
     private OnClickListener mOnKeepShowing = v -> {
         mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING;
         closeControls(v);
+        mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+                .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_STAY_SILENT));
     };
 
     private OnClickListener mOnToggleSilent = v -> {
         Runnable saveImportance = () -> {
             swapContent(ACTION_TOGGLE_SILENT, true /* animate */);
+            mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+                    .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_ALERT_ME));
         };
         if (mCheckSaveListener != null) {
             mCheckSaveListener.checkSave(saveImportance, mSbn);
@@ -139,6 +143,8 @@
     private OnClickListener mOnStopOrMinimizeNotifications = v -> {
         Runnable saveImportance = () -> {
             swapContent(ACTION_BLOCK, true /* animate */);
+            mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+                    .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_BLOCKED));
         };
         if (mCheckSaveListener != null) {
             mCheckSaveListener.checkSave(saveImportance, mSbn);
@@ -153,6 +159,8 @@
         logBlockingHelperCounter(NotificationCounters.BLOCKING_HELPER_UNDO);
         mMetricsLogger.write(importanceChangeLogMaker().setType(MetricsEvent.TYPE_DISMISS));
         swapContent(ACTION_UNDO, true /* animate */);
+        mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+                .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_UNDO));
     };
 
     public NotificationInfo(Context context, AttributeSet attrs) {
@@ -251,6 +259,9 @@
         bindHeader();
         bindPrompt();
         bindButtons();
+
+        mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+                .setSubtype(MetricsEvent.BLOCKING_HELPER_DISPLAY));
     }
 
     private void bindHeader() throws RemoteException {
@@ -588,6 +599,8 @@
         confirmation.setAlpha(1f);
         header.setVisibility(VISIBLE);
         header.setAlpha(1f);
+        mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+                .setSubtype(MetricsEvent.BLOCKING_HELPER_DISMISS));
     }
 
     @Override
@@ -733,4 +746,8 @@
             }
         }
     }
+
+    private LogMaker getLogMaker() {
+        return mSbn.getLogMaker().setCategory(MetricsEvent.NOTIFICATION_ITEM);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 3f93192..514bb22 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -461,13 +461,6 @@
     private NotificationMediaManager mMediaManager;
     protected NotificationLockscreenUserManager mLockscreenUserManager;
     protected NotificationRemoteInputManager mRemoteInputManager;
-    protected BubbleController mBubbleController;
-    private final BubbleController.BubbleExpandListener mBubbleExpandListener =
-            (isExpanding, amount) -> {
-                if (amount == 1) {
-                    updateScrimController();
-                }
-            };
 
     private final BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() {
         @Override
@@ -589,6 +582,12 @@
     private NotificationActivityStarter mNotificationActivityStarter;
     private boolean mPulsing;
     private ContentObserver mFeatureFlagObserver;
+    protected BubbleController mBubbleController;
+    private final BubbleController.BubbleExpandListener mBubbleExpandListener =
+            (isExpanding, key) -> {
+                mEntryManager.updateNotifications();
+                updateScrimController();
+            };
 
     @Override
     public void onActiveStateChanged(int code, int uid, String packageName, boolean active) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 4f61009..04d24dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -346,7 +346,7 @@
     }
 
     private void handleFullScreenIntent(NotificationEntry entry) {
-        boolean isHeadsUped = mNotificationInterruptionStateProvider.shouldHeadsUp(entry);
+        boolean isHeadsUped = mNotificationInterruptionStateProvider.canHeadsUpCommon(entry);
         if (!isHeadsUped && entry.notification.getNotification().fullScreenIntent != null) {
             if (shouldSuppressFullScreenIntent(entry)) {
                 if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
index 0c63e29..3bd0d45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
@@ -45,16 +45,19 @@
             "max_squeeze_remeasure_attempts";
     private static final String KEY_EDIT_CHOICES_BEFORE_SENDING =
             "edit_choices_before_sending";
+    private static final String KEY_SHOW_IN_HEADS_UP = "show_in_heads_up";
 
     private final boolean mDefaultEnabled;
     private final boolean mDefaultRequiresP;
     private final int mDefaultMaxSqueezeRemeasureAttempts;
     private final boolean mDefaultEditChoicesBeforeSending;
+    private final boolean mDefaultShowInHeadsUp;
 
     private boolean mEnabled;
     private boolean mRequiresTargetingP;
     private int mMaxSqueezeRemeasureAttempts;
     private boolean mEditChoicesBeforeSending;
+    private boolean mShowInHeadsUp;
 
     private final Context mContext;
     private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -73,6 +76,8 @@
                 R.integer.config_smart_replies_in_notifications_max_squeeze_remeasure_attempts);
         mDefaultEditChoicesBeforeSending = resources.getBoolean(
                 R.bool.config_smart_replies_in_notifications_edit_choices_before_sending);
+        mDefaultShowInHeadsUp = resources.getBoolean(
+                R.bool.config_smart_replies_in_notifications_show_in_heads_up);
 
         mContext.getContentResolver().registerContentObserver(
                 Settings.Global.getUriFor(Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS),
@@ -99,6 +104,7 @@
                     KEY_MAX_SQUEEZE_REMEASURE_ATTEMPTS, mDefaultMaxSqueezeRemeasureAttempts);
             mEditChoicesBeforeSending = mParser.getBoolean(
                     KEY_EDIT_CHOICES_BEFORE_SENDING, mDefaultEditChoicesBeforeSending);
+            mShowInHeadsUp = mParser.getBoolean(KEY_SHOW_IN_HEADS_UP, mDefaultShowInHeadsUp);
         }
     }
 
@@ -142,4 +148,11 @@
                 return mEditChoicesBeforeSending;
         }
     }
+
+    /**
+     * Returns whether smart suggestions should be enabled in heads-up notifications.
+     */
+    public boolean getShowInHeadsUp() {
+        return mShowInHeadsUp;
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 21d3652..fa5cf04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -19,7 +19,6 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -74,7 +73,8 @@
     private ExpandableNotificationRow mRow;
     private ExpandableNotificationRow mRow2;
 
-    private final NotificationData mNotificationData = new NotificationData();
+    @Mock
+    private NotificationData mNotificationData;
 
     @Before
     public void setUp() throws Exception {
@@ -93,6 +93,7 @@
 
         // Return non-null notification data from the NEM
         when(mNotificationEntryManager.getNotificationData()).thenReturn(mNotificationData);
+        when(mNotificationData.getChannel(mRow.getEntry().key)).thenReturn(mRow.getEntry().channel);
 
         mBubbleController = new TestableBubbleController(mContext, mStatusBarWindowController);
 
@@ -103,26 +104,21 @@
     }
 
     @Test
-    public void testIsBubble() {
-        assertTrue(mRow.getEntry().isBubble());
-    }
-
-    @Test
     public void testAddBubble() {
-        mBubbleController.addBubble(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
         assertTrue(mBubbleController.hasBubbles());
     }
 
     @Test
     public void testHasBubbles() {
         assertFalse(mBubbleController.hasBubbles());
-        mBubbleController.addBubble(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
         assertTrue(mBubbleController.hasBubbles());
     }
 
     @Test
     public void testRemoveBubble() {
-        mBubbleController.addBubble(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
         assertTrue(mBubbleController.hasBubbles());
 
         mBubbleController.removeBubble(mRow.getEntry().key);
@@ -133,35 +129,35 @@
 
     @Test
     public void testDismissStack() {
-        mBubbleController.addBubble(mRow.getEntry());
-        mBubbleController.addBubble(mRow2.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
+        mBubbleController.updateBubble(mRow2.getEntry(), true /* updatePosition */);
         assertTrue(mBubbleController.hasBubbles());
 
         mBubbleController.dismissStack();
         assertFalse(mStatusBarWindowController.getBubblesShowing());
-        verify(mNotificationEntryManager, times(3)).updateNotifications();
+        verify(mNotificationEntryManager).updateNotifications();
     }
 
     @Test
     public void testIsStackExpanded() {
         assertFalse(mBubbleController.isStackExpanded());
-        mBubbleController.addBubble(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
 
         BubbleStackView stackView = mBubbleController.getStackView();
-        stackView.animateExpansion(true /* expanded */);
+        stackView.expandStack();
         assertTrue(mBubbleController.isStackExpanded());
 
-        stackView.animateExpansion(false /* expanded */);
+        stackView.collapseStack();
         assertFalse(mBubbleController.isStackExpanded());
     }
 
     @Test
     public void testCollapseStack() {
-        mBubbleController.addBubble(mRow.getEntry());
-        mBubbleController.addBubble(mRow2.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
+        mBubbleController.updateBubble(mRow2.getEntry(), true /* updatePosition */);
 
         BubbleStackView stackView = mBubbleController.getStackView();
-        stackView.animateExpansion(true /* expanded */);
+        stackView.expandStack();
         assertTrue(mBubbleController.isStackExpanded());
 
         mBubbleController.collapseStack();
@@ -174,6 +170,12 @@
         assertTrue(mRow.getEntry().isBubble());
     }
 
+    @Test
+    public void testMarkNewNotificationAsShowInShade() {
+        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        assertTrue(mRow.getEntry().showInShadeWhenBubble());
+    }
+
     static class TestableBubbleController extends BubbleController {
 
         TestableBubbleController(Context context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index 529da82..2b13f86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -17,13 +17,16 @@
 package com.android.systemui.statusbar;
 
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.Instrumentation;
 import android.app.Notification;
 import android.app.NotificationChannel;
+import android.app.PendingIntent;
 import android.content.Context;
+import android.content.Intent;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.support.test.InstrumentationRegistry;
@@ -86,8 +89,7 @@
      * @throws Exception
      */
     public ExpandableNotificationRow createRow(String pkg, int uid) throws Exception {
-        return createRow(pkg, uid, false /* isGroupSummary */, null /* groupKey */,
-                false /* isBubble */);
+        return createRow(pkg, uid, false /* isGroupSummary */, null /* groupKey */);
     }
 
     /**
@@ -98,8 +100,7 @@
      * @throws Exception
      */
     public ExpandableNotificationRow createRow(Notification notification) throws Exception {
-        return generateRow(notification, PKG, UID, 0 /* extraInflationFlags */,
-                false /* isBubble */);
+        return generateRow(notification, PKG, UID, 0 /* extraInflationFlags */);
     }
 
     /**
@@ -112,8 +113,7 @@
      */
     public ExpandableNotificationRow createRow(@InflationFlag int extraInflationFlags)
             throws Exception {
-        return generateRow(createNotification(), PKG, UID, extraInflationFlags,
-                false /* isBubble */);
+        return generateRow(createNotification(), PKG, UID, extraInflationFlags);
     }
 
     /**
@@ -134,20 +134,21 @@
         return createGroup(2);
     }
 
-    /**
-     * Retursn an {@link ExpandableNotificationRow} that should be a bubble.
-     */
-    public ExpandableNotificationRow createBubble() throws Exception {
-        return createRow(PKG, UID, false /* isGroupSummary */, null /* groupKey */,
-                true /* isBubble */);
-    }
-
     private ExpandableNotificationRow createGroupSummary(String groupkey) throws Exception {
-        return createRow(PKG, UID, true /* isGroupSummary */, groupkey, false);
+        return createRow(PKG, UID, true /* isGroupSummary */, groupkey);
     }
 
     private ExpandableNotificationRow createGroupChild(String groupkey) throws Exception {
-        return createRow(PKG, UID, false /* isGroupSummary */, groupkey, false);
+        return createRow(PKG, UID, false /* isGroupSummary */, groupkey);
+    }
+
+    /**
+     * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble.
+     */
+    public ExpandableNotificationRow createBubble() throws Exception {
+        Notification n = createNotification(false /* isGroupSummary */,
+                null /* groupKey */, true /* isBubble */);
+        return generateRow(n, PKG, UID, 0 /* extraInflationFlags */, IMPORTANCE_HIGH);
     }
 
     /**
@@ -157,7 +158,6 @@
      * @param uid uid used for creating a {@link StatusBarNotification}
      * @param isGroupSummary whether the notification row is a group summary
      * @param groupKey the group key for the notification group used across notifications
-     * @param isBubble
      * @return a row with that's either a standalone notification or a group notification if the
      *         groupKey is non-null
      * @throws Exception
@@ -166,10 +166,10 @@
             String pkg,
             int uid,
             boolean isGroupSummary,
-            @Nullable String groupKey, boolean isBubble)
+            @Nullable String groupKey)
             throws Exception {
         Notification notif = createNotification(isGroupSummary, groupKey);
-        return generateRow(notif, pkg, uid, 0 /* inflationFlags */, isBubble);
+        return generateRow(notif, pkg, uid, 0 /* inflationFlags */);
     }
 
     /**
@@ -188,8 +188,20 @@
      * @param groupKey the group key for the notification group used across notifications
      * @return a notification that is in the group specified or standalone if unspecified
      */
+    private Notification createNotification(boolean isGroupSummary, @Nullable String groupKey) {
+        return createNotification(isGroupSummary, groupKey, false /* isBubble */);
+    }
+
+    /**
+     * Creates a notification with the given parameters.
+     *
+     * @param isGroupSummary whether the notification is a group summary
+     * @param groupKey the group key for the notification group used across notifications
+     * @param isBubble whether this notification should bubble
+     * @return a notification that is in the group specified or standalone if unspecified
+     */
     private Notification createNotification(boolean isGroupSummary,
-            @Nullable String groupKey) {
+            @Nullable String groupKey, boolean isBubble) {
         Notification publicVersion = new Notification.Builder(mContext).setSmallIcon(
                 R.drawable.ic_person)
                 .setCustomContentView(new RemoteViews(mContext.getPackageName(),
@@ -207,6 +219,10 @@
         if (!TextUtils.isEmpty(groupKey)) {
             notificationBuilder.setGroup(groupKey);
         }
+        if (isBubble) {
+            PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+            notificationBuilder.setAppOverlayIntent(bubbleIntent);
+        }
         return notificationBuilder.build();
     }
 
@@ -214,7 +230,17 @@
             Notification notification,
             String pkg,
             int uid,
-            @InflationFlag int extraInflationFlags, boolean isBubble)
+            @InflationFlag int extraInflationFlags)
+            throws Exception {
+        return generateRow(notification, pkg, uid, extraInflationFlags, IMPORTANCE_DEFAULT);
+    }
+
+    private ExpandableNotificationRow generateRow(
+            Notification notification,
+            String pkg,
+            int uid,
+            @InflationFlag int extraInflationFlags,
+            int importance)
             throws Exception {
         LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
                 mContext.LAYOUT_INFLATER_SERVICE);
@@ -242,9 +268,8 @@
         entry.setRow(row);
         entry.createIcons(mContext, sbn);
         entry.channel = new NotificationChannel(
-                notification.getChannelId(), notification.getChannelId(), IMPORTANCE_DEFAULT);
+                notification.getChannelId(), notification.getChannelId(), importance);
         entry.channel.setBlockableSystem(true);
-        entry.setIsBubble(isBubble);
         row.setEntry(entry);
         row.getNotificationInflater().addInflationFlags(extraInflationFlags);
         NotificationInflaterTest.runThenWaitForInflation(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index bf91305..56e1fc6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -36,7 +36,6 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.InitController;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
@@ -96,7 +95,7 @@
 
         mViewHierarchyManager = new NotificationViewHierarchyManager(mContext,
                 mLockscreenUserManager, mGroupManager, mVisualStabilityManager,
-                mock(StatusBarStateController.class), mEntryManager, mock(BubbleController.class),
+                mock(StatusBarStateController.class), mEntryManager,
                 () -> mShadeController);
         Dependency.get(InitController.class).executePostInitTasks();
         mViewHierarchyManager.setUpWithPresenter(mPresenter, mListContainer);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index ecb0cf8..f6791dd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -45,7 +45,6 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.INotificationManager;
@@ -73,6 +72,7 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
@@ -498,12 +498,15 @@
     }
 
     @Test
-    public void testLogBlockingHelperCounter_doesntLogForNormalGutsView() throws Exception {
+    public void testLogBlockingHelperCounter_logGutsViewDisplayed() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
                 IMPORTANCE_DEFAULT);
         mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent");
-        verifyZeroInteractions(mMetricsLogger);
+        verify(mMetricsLogger).write(argThat(logMaker ->
+                logMaker.getType() == MetricsEvent.NOTIFICATION_BLOCKING_HELPER
+                        && logMaker.getSubtype() == MetricsEvent.BLOCKING_HELPER_DISPLAY
+        ));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
index 37a56a3..3cbf902 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
@@ -54,6 +54,7 @@
                 R.integer.config_smart_replies_in_notifications_max_squeeze_remeasure_attempts, 7);
         resources.addOverride(
                 R.bool.config_smart_replies_in_notifications_edit_choices_before_sending, false);
+        resources.addOverride(R.bool.config_smart_replies_in_notifications_show_in_heads_up, true);
         mConstants = new SmartReplyConstants(Handler.createAsync(Looper.myLooper()), mContext);
     }
 
@@ -152,6 +153,26 @@
                         RemoteInput.EDIT_CHOICES_BEFORE_SENDING_DISABLED));
     }
 
+    @Test
+    public void testShowInHeadsUpWithNoConfig() {
+        assertTrue(mConstants.isEnabled());
+        assertTrue(mConstants.getShowInHeadsUp());
+    }
+
+    @Test
+    public void testShowInHeadsUpEnabled() {
+        overrideSetting("enabled=true,show_in_heads_up=true");
+        triggerConstantsOnChange();
+        assertTrue(mConstants.getShowInHeadsUp());
+    }
+
+    @Test
+    public void testShowInHeadsUpDisabled() {
+        overrideSetting("enabled=true,show_in_heads_up=false");
+        triggerConstantsOnChange();
+        assertFalse(mConstants.getShowInHeadsUp());
+    }
+
     private void overrideSetting(String flags) {
         Settings.Global.putString(mContext.getContentResolver(),
                 Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS, flags);
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index aff5e13..5b45a08 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -213,6 +213,25 @@
         IOOP_SYNC = 4;
     }
 
+  // Subtypes of notifications blocking helper view
+  // (NOTIFICATION_BLOCKING_HELPER).
+  enum NotificationBlockingHelper {
+    BLOCKING_HELPER_UNKNOWN = 0;
+    BLOCKING_HELPER_DISPLAY = 1;
+    BLOCKING_HELPER_DISMISS = 2;
+    // When the view of the notification blocking helper was triggered by
+    // system.
+    BLOCKING_HELPER_TRIGGERED_BY_SYSTEM = 3;
+    // "block" was clicked.
+    BLOCKING_HELPER_CLICK_BLOCKED = 4;
+    // "stay silent" was clicked.
+    BLOCKING_HELPER_CLICK_STAY_SILENT = 5;
+    // "alert me" was clicked.
+    BLOCKING_HELPER_CLICK_ALERT_ME = 6;
+    // "undo" was clicked (enables the user to undo "stop notification" action).
+    BLOCKING_HELPER_CLICK_UNDO = 7;
+  }
+
   // Known visual elements: views or controls.
   enum View {
     // Unknown view
@@ -6768,6 +6787,13 @@
     // OS: Q
     ACCESSIBILITY_VIBRATION_RING = 1620;
 
+    // ACTION: Notification blocking helper view, which helps the user to block
+    // application or channel from showing notifications.
+    // SUBTYPE: NotificationBlockingHelper enum.
+    // CATEGORY: NOTIFICATION
+    // OS: Q
+    NOTIFICATION_BLOCKING_HELPER = 1621;
+
     // ---- End Q Constants, all Q constants go above this line ----
 
     // Add new aosp constants above this line.
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 6cccd62..24fd7b9 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.MANAGE_AUTO_FILL;
 import static android.content.Context.AUTOFILL_MANAGER_SERVICE;
 import static android.util.DebugUtils.flagsToString;
+import static android.view.autofill.AutofillManager.MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS;
 
 import static com.android.server.autofill.Helper.sDebug;
 import static com.android.server.autofill.Helper.sFullScreenMode;
@@ -101,8 +102,6 @@
 
     private static final Object sLock = AutofillManagerService.class;
 
-    private static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
-
     /**
      * IME supports Smart Suggestions.
      */
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 239a386..a8ff9b0 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -168,7 +168,7 @@
                 }
             };
 
-            // TODO(b/111330312): set cancellation signal, timeout (from both mClient and service),
+            // TODO(b/122728762): set cancellation signal, timeout (from both mClient and service),
             // cache IAugmentedAutofillManagerClient reference, etc...
             try {
                 mClient.getAugmentedAutofillClient(receiver);
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 0348f2b..cf4963c 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -2581,7 +2581,10 @@
 
         final RemoteAugmentedAutofillService remoteService = mService
                 .getRemoteAugmentedAutofillServiceLocked();
-        if (remoteService == null) return null;
+        if (remoteService == null) {
+            if (sVerbose) Slog.v(TAG, "triggerAugmentedAutofillLocked(): no service for user");
+            return null;
+        }
 
         // Define which mode will be used
         final int mode;
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 12db4f3..ff378b3 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -149,6 +149,9 @@
 
         if (userBackupManagerService != null) {
             userBackupManagerService.tearDownService();
+
+            KeyValueBackupJob.cancel(userId, mContext);
+            FullBackupJob.cancel(userId, mContext);
         }
     }
 
@@ -577,9 +580,9 @@
      * @return Whether ongoing work will continue. The return value here will be passed along as the
      *     return value to the callback {@link JobService#onStartJob(JobParameters)}.
      */
-    public boolean beginFullBackup(FullBackupJob scheduledJob) {
+    public boolean beginFullBackup(@UserIdInt int userId, FullBackupJob scheduledJob) {
         UserBackupManagerService userBackupManagerService =
-                getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "beginFullBackup()");
+                getServiceForUserIfCallerHasPermission(userId, "beginFullBackup()");
 
         return userBackupManagerService != null
                 && userBackupManagerService.beginFullBackup(scheduledJob);
@@ -589,9 +592,9 @@
      * Used by the {@link JobScheduler} to end the current full backup task when conditions are no
      * longer met for running the full backup job.
      */
-    public void endFullBackup() {
+    public void endFullBackup(@UserIdInt int userId) {
         UserBackupManagerService userBackupManagerService =
-                getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "endFullBackup()");
+                getServiceForUserIfCallerHasPermission(userId, "endFullBackup()");
 
         if (userBackupManagerService != null) {
             userBackupManagerService.endFullBackup();
diff --git a/services/backup/java/com/android/server/backup/FullBackupJob.java b/services/backup/java/com/android/server/backup/FullBackupJob.java
index 5708b1c..33d21cb0 100644
--- a/services/backup/java/com/android/server/backup/FullBackupJob.java
+++ b/services/backup/java/com/android/server/backup/FullBackupJob.java
@@ -22,18 +22,30 @@
 import android.app.job.JobService;
 import android.content.ComponentName;
 import android.content.Context;
+import android.os.Bundle;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 public class FullBackupJob extends JobService {
+    private static final String USER_ID_EXTRA_KEY = "userId";
+
+    @VisibleForTesting
+    static final int MIN_JOB_ID = 52418896;
+    @VisibleForTesting
+    static final int MAX_JOB_ID = 52419896;
+
     private static ComponentName sIdleService =
             new ComponentName("android", FullBackupJob.class.getName());
 
-    private static final int JOB_ID = 0x5038;
+    @GuardedBy("mParamsForUser")
+    private final SparseArray<JobParameters> mParamsForUser = new SparseArray<>();
 
-    private JobParameters mParams;
-
-    public static void schedule(Context ctx, long minDelay, BackupManagerConstants constants) {
+    public static void schedule(int userId, Context ctx, long minDelay,
+            BackupManagerConstants constants) {
         JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
-        JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, sIdleService);
+        JobInfo.Builder builder = new JobInfo.Builder(getJobIdForUserId(userId), sIdleService);
         synchronized (constants) {
             builder.setRequiresDeviceIdle(true)
                     .setRequiredNetworkType(constants.getFullBackupRequiredNetworkType())
@@ -42,14 +54,28 @@
         if (minDelay > 0) {
             builder.setMinimumLatency(minDelay);
         }
+
+        Bundle extraInfo = new Bundle();
+        extraInfo.putInt(USER_ID_EXTRA_KEY, userId);
+        builder.setTransientExtras(extraInfo);
+
         js.schedule(builder.build());
     }
 
+    public static void cancel(int userId, Context ctx) {
+        JobScheduler js = (JobScheduler) ctx.getSystemService(
+                Context.JOB_SCHEDULER_SERVICE);
+        js.cancel(getJobIdForUserId(userId));
+    }
+
     // callback from the Backup Manager Service: it's finished its work for this pass
-    public void finishBackupPass() {
-        if (mParams != null) {
-            jobFinished(mParams, false);
-            mParams = null;
+    public void finishBackupPass(int userId) {
+        synchronized (mParamsForUser) {
+            JobParameters jobParameters = mParamsForUser.get(userId);
+            if (jobParameters != null) {
+                jobFinished(jobParameters, false);
+                mParamsForUser.remove(userId);
+            }
         }
     }
 
@@ -57,19 +83,33 @@
 
     @Override
     public boolean onStartJob(JobParameters params) {
-        mParams = params;
+        int userId = params.getTransientExtras().getInt(USER_ID_EXTRA_KEY);
+
+        synchronized (mParamsForUser) {
+            mParamsForUser.put(userId, params);
+        }
+
         Trampoline service = BackupManagerService.getInstance();
-        return service.beginFullBackup(this);
+        return service.beginFullBackup(userId, this);
     }
 
     @Override
     public boolean onStopJob(JobParameters params) {
-        if (mParams != null) {
-            mParams = null;
-            Trampoline service = BackupManagerService.getInstance();
-            service.endFullBackup();
+        int userId = params.getTransientExtras().getInt(USER_ID_EXTRA_KEY);
+
+        synchronized (mParamsForUser) {
+            if (mParamsForUser.removeReturnOld(userId) == null) {
+                return false;
+            }
         }
+
+        Trampoline service = BackupManagerService.getInstance();
+        service.endFullBackup(userId);
+
         return false;
     }
 
+    private static int getJobIdForUserId(int userId) {
+        return JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, userId);
+    }
 }
diff --git a/services/backup/java/com/android/server/backup/JobIdManager.java b/services/backup/java/com/android/server/backup/JobIdManager.java
new file mode 100644
index 0000000..2e834dbf
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/JobIdManager.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup;
+
+/**
+ * Allocates job IDs for {@link FullBackupJob} and {@link KeyValueBackupJob}
+ */
+public class JobIdManager {
+    public static int getJobIdForUserId(int minJobId, int maxJobId, int userId) {
+        if (minJobId + userId > maxJobId) {
+            throw new RuntimeException("No job IDs available in the given range");
+        }
+
+        return minJobId + userId;
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
index f2e7435..d43859e 100644
--- a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
+++ b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
@@ -25,8 +25,14 @@
 import android.app.job.JobService;
 import android.content.ComponentName;
 import android.content.Context;
+import android.os.Bundle;
 import android.os.RemoteException;
 import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.util.SparseLongArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.Random;
 
@@ -38,7 +44,8 @@
     private static final String TAG = "KeyValueBackupJob";
     private static ComponentName sKeyValueJobService =
             new ComponentName("android", KeyValueBackupJob.class.getName());
-    private static final int JOB_ID = 0x5039;
+
+    private static final String USER_ID_EXTRA_KEY = "userId";
 
     // Once someone asks for a backup, this is how long we hold off until we find
     // an on-charging opportunity.  If we hit this max latency we will run the operation
@@ -46,16 +53,22 @@
     // BackupManager.backupNow().
     private static final long MAX_DEFERRAL = AlarmManager.INTERVAL_DAY;
 
-    private static boolean sScheduled = false;
-    private static long sNextScheduled = 0;
+    @GuardedBy("KeyValueBackupJob.class")
+    private static final SparseBooleanArray sScheduledForUserId = new SparseBooleanArray();
+    @GuardedBy("KeyValueBackupJob.class")
+    private static final SparseLongArray sNextScheduledForUserId = new SparseLongArray();
 
-    public static void schedule(Context ctx, BackupManagerConstants constants) {
-        schedule(ctx, 0, constants);
+    private static final int MIN_JOB_ID = 52417896;
+    private static final int MAX_JOB_ID = 52418896;
+
+    public static void schedule(int userId, Context ctx, BackupManagerConstants constants) {
+        schedule(userId, ctx, 0, constants);
     }
 
-    public static void schedule(Context ctx, long delay, BackupManagerConstants constants) {
+    public static void schedule(int userId, Context ctx, long delay,
+            BackupManagerConstants constants) {
         synchronized (KeyValueBackupJob.class) {
-            if (sScheduled) {
+            if (sScheduledForUserId.get(userId)) {
                 return;
             }
 
@@ -76,51 +89,61 @@
             if (DEBUG_SCHEDULING) {
                 Slog.v(TAG, "Scheduling k/v pass in " + (delay / 1000 / 60) + " minutes");
             }
-            JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, sKeyValueJobService)
+
+            JobInfo.Builder builder = new JobInfo.Builder(getJobIdForUserId(userId),
+                    sKeyValueJobService)
                     .setMinimumLatency(delay)
                     .setRequiredNetworkType(networkType)
                     .setRequiresCharging(needsCharging)
                     .setOverrideDeadline(MAX_DEFERRAL);
+
+            Bundle extraInfo = new Bundle();
+            extraInfo.putInt(USER_ID_EXTRA_KEY, userId);
+            builder.setTransientExtras(extraInfo);
+
             JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
             js.schedule(builder.build());
 
-            sNextScheduled = System.currentTimeMillis() + delay;
-            sScheduled = true;
+            sScheduledForUserId.put(userId, true);
+            sNextScheduledForUserId.put(userId, System.currentTimeMillis() + delay);
         }
     }
 
-    public static void cancel(Context ctx) {
+    public static void cancel(int userId, Context ctx) {
         synchronized (KeyValueBackupJob.class) {
-            JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
-            js.cancel(JOB_ID);
-            sNextScheduled = 0;
-            sScheduled = false;
+            JobScheduler js = (JobScheduler) ctx.getSystemService(
+                    Context.JOB_SCHEDULER_SERVICE);
+            js.cancel(getJobIdForUserId(userId));
+
+            clearScheduledForUserId(userId);
         }
     }
 
-    public static long nextScheduled() {
+    public static long nextScheduled(int userId) {
         synchronized (KeyValueBackupJob.class) {
-            return sNextScheduled;
+            return sNextScheduledForUserId.get(userId);
         }
     }
 
-    public static boolean isScheduled() {
+    @VisibleForTesting
+    public static boolean isScheduled(int userId) {
         synchronized (KeyValueBackupJob.class) {
-            return sScheduled;
+            return sScheduledForUserId.get(userId);
         }
     }
 
     @Override
     public boolean onStartJob(JobParameters params) {
+        int userId = params.getTransientExtras().getInt(USER_ID_EXTRA_KEY);
+
         synchronized (KeyValueBackupJob.class) {
-            sNextScheduled = 0;
-            sScheduled = false;
+            clearScheduledForUserId(userId);
         }
 
         // Time to run a key/value backup!
         Trampoline service = BackupManagerService.getInstance();
         try {
-            service.backupNow();
+            service.backupNowForUser(userId);
         } catch (RemoteException e) {}
 
         // This was just a trigger; ongoing wakelock management is done by the
@@ -134,4 +157,13 @@
         return false;
     }
 
+    @GuardedBy("KeyValueBackupJob.class")
+    private static void clearScheduledForUserId(int userId) {
+        sScheduledForUserId.delete(userId);
+        sNextScheduledForUserId.delete(userId);
+    }
+
+    private static int getJobIdForUserId(int userId) {
+        return JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, userId);
+    }
 }
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index 79d4a2c..4ca2545 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -698,15 +698,15 @@
 
     // Full backup/restore entry points - non-Binder; called directly
     // by the full-backup scheduled job
-    /* package */ boolean beginFullBackup(FullBackupJob scheduledJob) {
+    /* package */ boolean beginFullBackup(@UserIdInt int userId, FullBackupJob scheduledJob) {
         BackupManagerService svc = mService;
-        return (svc != null) ? svc.beginFullBackup(scheduledJob) : false;
+        return (svc != null) ? svc.beginFullBackup(userId, scheduledJob) : false;
     }
 
-    /* package */ void endFullBackup() {
+    /* package */ void endFullBackup(@UserIdInt int userId) {
         BackupManagerService svc = mService;
         if (svc != null) {
-            svc.endFullBackup();
+            svc.endFullBackup(userId);
         }
     }
 }
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index ddce6bb..529430c 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -17,6 +17,7 @@
 package com.android.server.backup;
 
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.annotation.WorkerThread;
 import android.app.backup.BackupManager;
 import android.app.backup.BackupTransport;
@@ -29,7 +30,6 @@
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -61,7 +61,7 @@
     public static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
 
     private final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST);
-    private final Context mContext;
+    private final @UserIdInt int mUserId;
     private final PackageManager mPackageManager;
     private final Set<ComponentName> mTransportWhitelist;
     private final TransportClientManager mTransportClientManager;
@@ -86,22 +86,24 @@
     @Nullable
     private volatile String mCurrentTransportName;
 
-    TransportManager(Context context, Set<ComponentName> whitelist, String selectedTransport) {
-        mContext = context;
+    TransportManager(@UserIdInt int userId, Context context, Set<ComponentName> whitelist,
+            String selectedTransport) {
+        mUserId = userId;
         mPackageManager = context.getPackageManager();
         mTransportWhitelist = Preconditions.checkNotNull(whitelist);
         mCurrentTransportName = selectedTransport;
         mTransportStats = new TransportStats();
-        mTransportClientManager = new TransportClientManager(context, mTransportStats);
+        mTransportClientManager = new TransportClientManager(mUserId, context, mTransportStats);
     }
 
     @VisibleForTesting
     TransportManager(
+            @UserIdInt int userId,
             Context context,
             Set<ComponentName> whitelist,
             String selectedTransport,
             TransportClientManager transportClientManager) {
-        mContext = context;
+        mUserId = userId;
         mPackageManager = context.getPackageManager();
         mTransportWhitelist = Preconditions.checkNotNull(whitelist);
         mCurrentTransportName = selectedTransport;
@@ -575,7 +577,7 @@
     private void registerTransportsForIntent(
             Intent intent, Predicate<ComponentName> transportComponentFilter) {
         List<ResolveInfo> hosts =
-                mPackageManager.queryIntentServicesAsUser(intent, 0, UserHandle.USER_SYSTEM);
+                mPackageManager.queryIntentServicesAsUser(intent, 0, mUserId);
         if (hosts == null) {
             return;
         }
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 6b0adfb..ed6a46c 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -384,7 +384,7 @@
             Slog.v(TAG, "Starting with transport " + currentTransport);
         }
         TransportManager transportManager =
-                new TransportManager(context, transportWhitelist, currentTransport);
+                new TransportManager(userId, context, transportWhitelist, currentTransport);
 
         File baseStateDir = UserBackupManagerFiles.getBaseStateDir(userId);
         File dataDir = UserBackupManagerFiles.getDataDir(userId);
@@ -1674,8 +1674,8 @@
             }
             // We don't want the backup jobs to kick in any time soon.
             // Reschedules them to run in the distant future.
-            KeyValueBackupJob.schedule(mContext, BUSY_BACKOFF_MIN_MILLIS, mConstants);
-            FullBackupJob.schedule(mContext, 2 * BUSY_BACKOFF_MIN_MILLIS, mConstants);
+            KeyValueBackupJob.schedule(mUserId, mContext, BUSY_BACKOFF_MIN_MILLIS, mConstants);
+            FullBackupJob.schedule(mUserId, mContext, 2 * BUSY_BACKOFF_MIN_MILLIS, mConstants);
         } finally {
             Binder.restoreCallingIdentity(oldToken);
         }
@@ -1910,7 +1910,7 @@
                 Runnable r = new Runnable() {
                     @Override
                     public void run() {
-                        FullBackupJob.schedule(mContext, latency, mConstants);
+                        FullBackupJob.schedule(mUserId, mContext, latency, mConstants);
                     }
                 };
                 mBackupHandler.postDelayed(r, 2500);
@@ -2033,7 +2033,7 @@
                 mPowerManager.getPowerSaveState(ServiceType.FULL_BACKUP);
         if (result.batterySaverEnabled) {
             if (DEBUG) Slog.i(TAG, "Deferring scheduled full backups in battery saver mode");
-            FullBackupJob.schedule(mContext, keyValueBackupInterval, mConstants);
+            FullBackupJob.schedule(mUserId, mContext, keyValueBackupInterval, mConstants);
             return false;
         }
 
@@ -2147,7 +2147,7 @@
                 mBackupHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        FullBackupJob.schedule(mContext, deferTime, mConstants);
+                        FullBackupJob.schedule(mUserId, mContext, deferTime, mConstants);
                     }
                 });
                 return false;
@@ -2251,7 +2251,7 @@
         }
 
         // ...and schedule a backup pass if necessary
-        KeyValueBackupJob.schedule(mContext, mConstants);
+        KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
     }
 
     // Note: packageName is currently unused, but may be in the future
@@ -2401,7 +2401,8 @@
                     mPowerManager.getPowerSaveState(ServiceType.KEYVALUE_BACKUP);
             if (result.batterySaverEnabled) {
                 if (DEBUG) Slog.v(TAG, "Not running backup while in battery save mode");
-                KeyValueBackupJob.schedule(mContext, mConstants);   // try again in several hours
+                // Try again in several hours.
+                KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
             } else {
                 if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass");
                 synchronized (mQueueLock) {
@@ -2414,7 +2415,7 @@
                     }
 
                     // ...and cancel any pending scheduled job, because we've just superseded it
-                    KeyValueBackupJob.cancel(mContext);
+                    KeyValueBackupJob.cancel(mUserId, mContext);
                 }
             }
         } finally {
@@ -2737,13 +2738,13 @@
             synchronized (mQueueLock) {
                 if (enable && !wasEnabled && mSetupComplete) {
                     // if we've just been enabled, start scheduling backup passes
-                    KeyValueBackupJob.schedule(mContext, mConstants);
+                    KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
                     scheduleNextFullBackupJob(0);
                 } else if (!enable) {
                     // No longer enabled, so stop running backups
                     if (MORE_DEBUG) Slog.i(TAG, "Opting out of backup");
 
-                    KeyValueBackupJob.cancel(mContext);
+                    KeyValueBackupJob.cancel(mUserId, mContext);
 
                     // This also constitutes an opt-out, so we wipe any data for
                     // this device from the backend.  We start that process with
@@ -3451,7 +3452,7 @@
             pw.println(isBackupOperationInProgress() ? "Backup in progress" : "No backups running");
             pw.println("Last backup pass started: " + mLastBackupPass
                     + " (now = " + System.currentTimeMillis() + ')');
-            pw.println("  next scheduled: " + KeyValueBackupJob.nextScheduled());
+            pw.println("  next scheduled: " + KeyValueBackupJob.nextScheduled(mUserId));
 
             pw.println("Transport whitelist:");
             for (ComponentName transport : mTransportManager.getTransportWhitelist()) {
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index 2ee96d1..0fb4f93 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -633,7 +633,7 @@
             unregisterTask();
 
             if (mJob != null) {
-                mJob.finishBackupPass();
+                mJob.finishBackupPass(backupManagerService.getUserId());
             }
 
             synchronized (backupManagerService.getQueueLock()) {
diff --git a/services/backup/java/com/android/server/backup/internal/SetupObserver.java b/services/backup/java/com/android/server/backup/internal/SetupObserver.java
index 62ae3eb..c5e912e 100644
--- a/services/backup/java/com/android/server/backup/internal/SetupObserver.java
+++ b/services/backup/java/com/android/server/backup/internal/SetupObserver.java
@@ -77,7 +77,8 @@
                 if (MORE_DEBUG) {
                     Slog.d(TAG, "Setup complete so starting backups");
                 }
-                KeyValueBackupJob.schedule(mContext, mUserBackupManagerService.getConstants());
+                KeyValueBackupJob.schedule(mUserBackupManagerService.getUserId(), mContext,
+                        mUserBackupManagerService.getConstants());
                 mUserBackupManagerService.scheduleNextFullBackupJob(0);
             }
         }
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index f39d795..ef7ff92 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -1003,7 +1003,7 @@
             // Use the scheduler's default.
             delay = 0;
         }
-        KeyValueBackupJob.schedule(
+        KeyValueBackupJob.schedule(mBackupManagerService.getUserId(),
                 mBackupManagerService.getContext(), delay, mBackupManagerService.getConstants());
 
         for (String packageName : mOriginalQueue) {
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClient.java b/services/backup/java/com/android/server/backup/transport/TransportClient.java
index e4dcb25..7c5a57c 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportClient.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportClient.java
@@ -20,6 +20,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.annotation.WorkerThread;
 import android.content.ComponentName;
 import android.content.Context;
@@ -34,7 +35,6 @@
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
 import android.util.EventLog;
-import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -79,6 +79,7 @@
     @VisibleForTesting static final String TAG = "TransportClient";
     private static final int LOG_BUFFER_SIZE = 5;
 
+    private final @UserIdInt int mUserId;
     private final Context mContext;
     private final TransportStats mTransportStats;
     private final Intent mBindIntent;
@@ -106,6 +107,7 @@
     private volatile IBackupTransport mTransport;
 
     TransportClient(
+            @UserIdInt int userId,
             Context context,
             TransportStats transportStats,
             Intent bindIntent,
@@ -113,6 +115,7 @@
             String identifier,
             String caller) {
         this(
+                userId,
                 context,
                 transportStats,
                 bindIntent,
@@ -124,6 +127,7 @@
 
     @VisibleForTesting
     TransportClient(
+            @UserIdInt int userId,
             Context context,
             TransportStats transportStats,
             Intent bindIntent,
@@ -131,6 +135,7 @@
             String identifier,
             String caller,
             Handler listenerHandler) {
+        mUserId = userId;
         mContext = context;
         mTransportStats = transportStats;
         mTransportComponent = transportComponent;
@@ -213,7 +218,7 @@
                                     mBindIntent,
                                     mConnection,
                                     Context.BIND_AUTO_CREATE,
-                                    UserHandle.SYSTEM);
+                                    UserHandle.of(mUserId));
                     if (hasBound) {
                         // We don't need to set a time-out because we are guaranteed to get a call
                         // back in ServiceConnection, either an onServiceConnected() or
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClientManager.java b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
index f4e3928..a4e9b10 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
@@ -19,12 +19,15 @@
 import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
 import static com.android.server.backup.transport.TransportUtils.formatMessage;
 
+import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
+
 import com.android.server.backup.TransportManager;
 import com.android.server.backup.transport.TransportUtils.Priority;
+
 import java.io.PrintWriter;
 import java.util.Map;
 import java.util.WeakHashMap;
@@ -36,13 +39,16 @@
 public class TransportClientManager {
     private static final String TAG = "TransportClientManager";
 
+    private final @UserIdInt int mUserId;
     private final Context mContext;
     private final TransportStats mTransportStats;
     private final Object mTransportClientsLock = new Object();
     private int mTransportClientsCreated = 0;
     private Map<TransportClient, String> mTransportClientsCallerMap = new WeakHashMap<>();
 
-    public TransportClientManager(Context context, TransportStats transportStats) {
+    public TransportClientManager(@UserIdInt int userId, Context context,
+            TransportStats transportStats) {
+        mUserId = userId;
         mContext = context;
         mTransportStats = transportStats;
     }
@@ -89,6 +95,7 @@
         synchronized (mTransportClientsLock) {
             TransportClient transportClient =
                     new TransportClient(
+                            mUserId,
                             mContext,
                             mTransportStats,
                             bindIntent,
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index a9f190c..62da3f8 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -34,9 +34,9 @@
 import android.util.MathUtils;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.StatsLog;
 
 import com.android.internal.util.ArrayUtils;
-import com.android.server.pm.PackageManagerService;
 
 import java.io.File;
 
@@ -179,6 +179,7 @@
     }
 
     private static void executeRescueLevelInternal(Context context, int level) throws Exception {
+        StatsLog.write(StatsLog.RESCUE_PARTY_RESET_REPORTED, level);
         switch (level) {
             case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
                 resetAllSettings(context, Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index bab9a65..a96676e 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -516,7 +516,7 @@
                 }
                 // This app knows it is in the new model where this operation is not
                 // allowed, so tell it what has happened.
-                UidRecord uidRec = mAm.mActiveUids.get(r.appInfo.uid);
+                UidRecord uidRec = mAm.mProcessList.getUidRecordLocked(r.appInfo.uid);
                 return new ComponentName("?", "app is in background uid " + uidRec);
             }
         }
@@ -739,7 +739,7 @@
 
     private void stopServiceLocked(ServiceRecord service) {
         if (service.delayed) {
-            // If service isn't actually running, but is is being held in the
+            // If service isn't actually running, but is being held in the
             // delayed list, then we need to keep it started but note that it
             // should be stopped once no longer delayed.
             if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "Delaying stop of pending: " + service);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 26141f7..3b08a00 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -24,8 +24,6 @@
 import static android.Manifest.permission.REMOVE_TASKS;
 import static android.app.ActivityManager.INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS;
 import static android.app.ActivityManager.INSTR_FLAG_MOUNT_EXTERNAL_STORAGE_FULL;
-import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
-import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
 import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
 import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
@@ -59,16 +57,11 @@
 import static android.os.Process.PROC_SPACE_TERM;
 import static android.os.Process.ROOT_UID;
 import static android.os.Process.SCHED_FIFO;
-import static android.os.Process.SCHED_OTHER;
 import static android.os.Process.SCHED_RESET_ON_FORK;
 import static android.os.Process.SE_UID;
 import static android.os.Process.SHELL_UID;
 import static android.os.Process.SIGNAL_USR1;
 import static android.os.Process.SYSTEM_UID;
-import static android.os.Process.THREAD_GROUP_BG_NONINTERACTIVE;
-import static android.os.Process.THREAD_GROUP_DEFAULT;
-import static android.os.Process.THREAD_GROUP_RESTRICTED;
-import static android.os.Process.THREAD_GROUP_TOP_APP;
 import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
 import static android.os.Process.getTotalMemory;
 import static android.os.Process.isThreadInProcess;
@@ -79,7 +72,6 @@
 import static android.os.Process.readProcFile;
 import static android.os.Process.removeAllProcessGroups;
 import static android.os.Process.sendSignal;
-import static android.os.Process.setProcessGroup;
 import static android.os.Process.setThreadPriority;
 import static android.os.Process.setThreadScheduler;
 import static android.os.Process.zygoteProcess;
@@ -96,11 +88,9 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_BACKGROUND;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ_REASON;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
@@ -109,7 +99,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_USAGE_STATS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_WHITELISTS;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
@@ -404,7 +393,7 @@
     public static final int TOP_APP_PRIORITY_BOOST = -10;
 
     static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerService" : TAG_AM;
-    private static final String TAG_BACKUP = TAG + POSTFIX_BACKUP;
+    static final String TAG_BACKUP = TAG + POSTFIX_BACKUP;
     private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
     private static final String TAG_CLEANUP = TAG + POSTFIX_CLEANUP;
     private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
@@ -412,9 +401,9 @@
     static final String TAG_LRU = TAG + POSTFIX_LRU;
     private static final String TAG_MU = TAG + POSTFIX_MU;
     private static final String TAG_NETWORK = TAG + POSTFIX_NETWORK;
-    private static final String TAG_OOM_ADJ = TAG + POSTFIX_OOM_ADJ;
+    static final String TAG_OOM_ADJ = TAG + POSTFIX_OOM_ADJ;
     private static final String TAG_POWER = TAG + POSTFIX_POWER;
-    private static final String TAG_PROCESS_OBSERVERS = TAG + POSTFIX_PROCESS_OBSERVERS;
+    static final String TAG_PROCESS_OBSERVERS = TAG + POSTFIX_PROCESS_OBSERVERS;
     static final String TAG_PROCESSES = TAG + POSTFIX_PROCESSES;
     private static final String TAG_PROVIDER = TAG + POSTFIX_PROVIDER;
     static final String TAG_PSS = TAG + POSTFIX_PSS;
@@ -541,6 +530,8 @@
 
     private static final int NATIVE_DUMP_TIMEOUT_MS = 2000; // 2 seconds;
 
+    final OomAdjuster mOomAdjuster;
+
     /** All system services */
     SystemServiceManager mSystemServiceManager;
 
@@ -555,7 +546,7 @@
     public OomAdjProfiler mOomAdjProfiler = new OomAdjProfiler();
 
     // Whether we should use SCHED_FIFO for UI and RenderThreads.
-    private boolean mUseFifoUiScheduling = false;
+    boolean mUseFifoUiScheduling = false;
 
     BroadcastQueue mFgBroadcastQueue;
     BroadcastQueue mBgBroadcastQueue;
@@ -654,11 +645,6 @@
     final ProcessStatsService mProcessStats;
 
     /**
-     * Service for compacting background apps.
-     */
-    final AppCompactor mAppCompact;
-
-    /**
      * Non-persistent appId whitelist for background restrictions
      */
     int[] mBackgroundAppIdWhitelist = new int[] {
@@ -821,8 +807,6 @@
      */
     boolean mFullPssPending = false;
 
-    /** Track all uids that have actively running processes. */
-    final ActiveUids mActiveUids;
 
     /**
      * This is for verifying the UID report flow.
@@ -1104,32 +1088,7 @@
     /**
      * State of external calls telling us if the device is awake or asleep.
      */
-    private int mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
-
-    /**
-     * Current sequence id for oom_adj computation traversal.
-     */
-    int mAdjSeq = 0;
-
-    /**
-     * Keep track of the non-cached/empty process we last found, to help
-     * determine how to distribute cached/empty processes next time.
-     */
-    int mNumNonCachedProcs = 0;
-
-    /**
-     * Keep track of the number of cached hidden procs, to balance oom adj
-     * distribution between those and empty procs.
-     */
-    int mNumCachedHiddenProcs = 0;
-
-    /**
-     * Keep track of the number of service processes we last found, to
-     * determine on the next iteration which should be B services.
-     */
-    int mNumServiceProcs = 0;
-    int mNewNumAServiceProcs = 0;
-    int mNewNumServiceProcs = 0;
+    int mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
 
     /**
      * Allow the current computed overall memory level of the system to go down?
@@ -1256,10 +1215,6 @@
     String mTrackAllocationApp = null;
     String mNativeDebuggingApp = null;
 
-    final long[] mTmpLong = new long[3];
-
-    private final ArraySet<BroadcastQueue> mTmpBroadcastQueue = new ArraySet();
-
     private final Injector mInjector;
 
     static final class ProcessChangeItem {
@@ -1334,6 +1289,7 @@
         }
     }
 
+    // TODO: Move below 4 members and code to ProcessList
     final RemoteCallbackList<IProcessObserver> mProcessObservers = new RemoteCallbackList<>();
     ProcessChangeItem[] mActiveProcessChanges = new ProcessChangeItem[5];
 
@@ -2225,12 +2181,15 @@
         mUiContext = null;
         mAppErrors = null;
         mPackageWatchdog = null;
-        mActiveUids = new ActiveUids(this, false /* postChangesToAtm */);
         mAppOpsService = mInjector.getAppOpsService(null /* file */, null /* handler */);
         mBatteryStatsService = null;
         mHandler = hasHandlerThread ? new MainHandler(handlerThread.getLooper()) : null;
         mHandlerThread = handlerThread;
         mConstants = hasHandlerThread ? new ActivityManagerConstants(this, mHandler) : null;
+        final ActiveUids activeUids = new ActiveUids(this, false /* postChangesToAtm */);
+        mProcessList.init(this, activeUids);
+        mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids);
+
         mIntentFirewall = hasHandlerThread
                 ? new IntentFirewall(new IntentFirewallInterface(), mHandler) : null;
         mProcessCpuThread = null;
@@ -2246,7 +2205,6 @@
                 ? new PendingIntentController(handlerThread.getLooper(), mUserController) : null;
         mProcStartHandlerThread = null;
         mProcStartHandler = null;
-        mAppCompact = null;
         mHiddenApiBlacklist = null;
         mFactoryTest = FACTORY_TEST_OFF;
     }
@@ -2276,8 +2234,9 @@
         mProcStartHandler = new Handler(mProcStartHandlerThread.getLooper());
 
         mConstants = new ActivityManagerConstants(this, mHandler);
-
-        mProcessList.init(this);
+        final ActiveUids activeUids = new ActiveUids(this, true /* postChangesToAtm */);
+        mProcessList.init(this, activeUids);
+        mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids);
 
         mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
                 "foreground", BROADCAST_FG_TIMEOUT, false);
@@ -2293,7 +2252,6 @@
         mProviderMap = new ProviderMap(this);
         mPackageWatchdog = PackageWatchdog.getInstance(mUiContext);
         mAppErrors = new AppErrors(mUiContext, this, mPackageWatchdog);
-        mActiveUids = new ActiveUids(this, true /* postChangesToAtm */);
 
         final File systemDir = SystemServiceManager.ensureSystemDir();
 
@@ -2330,8 +2288,6 @@
                 DisplayThread.get().getLooper());
         mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
 
-        mAppCompact = new AppCompactor(this);
-
         mProcessCpuThread = new Thread("CpuTracker") {
             @Override
             public void run() {
@@ -2378,7 +2334,8 @@
         try {
             Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(),
                     Process.THREAD_GROUP_SYSTEM);
-            Process.setThreadGroupAndCpuset(mAppCompact.mCompactionThread.getThreadId(),
+            Process.setThreadGroupAndCpuset(
+                    mOomAdjuster.mAppCompact.mCompactionThread.getThreadId(),
                     Process.THREAD_GROUP_SYSTEM);
         } catch (Exception e) {
             Slog.w(TAG, "Setting background thread cpuset failed");
@@ -5347,7 +5304,7 @@
 
     private boolean isAppForeground(int uid) {
         synchronized (this) {
-            UidRecord uidRec = mActiveUids.get(uid);
+            UidRecord uidRec = mProcessList.mActiveUids.get(uid);
             if (uidRec == null || uidRec.idle) {
                 return false;
             }
@@ -5359,15 +5316,10 @@
     // be guarded by permission checking.
     int getUidState(int uid) {
         synchronized (this) {
-            return getUidStateLocked(uid);
+            return mProcessList.getUidProcStateLocked(uid);
         }
     }
 
-    int getUidStateLocked(int uid) {
-        UidRecord uidRec = mActiveUids.get(uid);
-        return uidRec == null ? PROCESS_STATE_NONEXISTENT : uidRec.getCurProcState();
-    }
-
     // =========================================================
     // PROCESS INFO
     // =========================================================
@@ -5659,7 +5611,7 @@
 
     int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk,
             int callingPid, boolean alwaysRestrict, boolean disabledOnly, boolean forcedStandby) {
-        UidRecord uidRec = mActiveUids.get(uid);
+        UidRecord uidRec = mProcessList.getUidRecordLocked(uid);
         if (DEBUG_BACKGROUND_CHECK) Slog.d(TAG, "checkAllowBackground: uid=" + uid + " pkg="
                 + packageName + " rec=" + uidRec + " always=" + alwaysRestrict + " idle="
                 + (uidRec != null ? uidRec.idle : false));
@@ -7880,8 +7832,7 @@
         }
 
         synchronized (this) {
-            UidRecord uidRec = mActiveUids.get(uid);
-            return uidRec != null ? uidRec.getCurProcState() : PROCESS_STATE_NONEXISTENT;
+            return mProcessList.getUidProcStateLocked(uid);
         }
     }
 
@@ -7917,7 +7868,7 @@
     }
 
     boolean isUidActiveLocked(int uid) {
-        final UidRecord uidRecord = mActiveUids.get(uid);
+        final UidRecord uidRecord = mProcessList.getUidRecordLocked(uid);
         return uidRecord != null && !uidRecord.setIdle;
     }
 
@@ -8709,7 +8660,7 @@
 
                 if (mForceBackgroundCheck) {
                     // Stop background services for idle UIDs.
-                    doStopUidForIdleUidsLocked();
+                    mProcessList.doStopUidForIdleUidsLocked();
                 }
             }
         }
@@ -9558,7 +9509,8 @@
                     proto.end(broadcastToken);
 
                     long serviceToken = proto.start(ActivityManagerServiceProto.SERVICES);
-                    mServices.writeToProto(proto, ActivityManagerServiceDumpServicesProto.ACTIVE_SERVICES);
+                    mServices.writeToProto(proto,
+                            ActivityManagerServiceDumpServicesProto.ACTIVE_SERVICES);
                     proto.end(serviceToken);
 
                     long processToken = proto.start(ActivityManagerServiceProto.PROCESSES);
@@ -10110,11 +10062,13 @@
             }
         }
 
-        if (mActiveUids.size() > 0) {
-            if (dumpUids(pw, dumpPackage, dumpAppId, mActiveUids, "UID states:", needSep)) {
+        if (mProcessList.mActiveUids.size() > 0) {
+            if (dumpUids(pw, dumpPackage, dumpAppId, mProcessList.mActiveUids,
+                    "UID states:", needSep)) {
                 needSep = true;
             }
         }
+
         if (dumpAll) {
             if (mValidateUids.size() > 0) {
                 if (dumpUids(pw, dumpPackage, dumpAppId, mValidateUids, "UID validation:",
@@ -10377,12 +10331,8 @@
                 pw.print("  mLastPowerCheckUptime=");
                         TimeUtils.formatDuration(mLastPowerCheckUptime, pw);
                         pw.println("");
-                pw.println("  mAdjSeq=" + mAdjSeq + " mLruSeq=" + mProcessList.mLruSeq);
-                pw.println("  mNumNonCachedProcs=" + mNumNonCachedProcs
-                        + " (" + mProcessList.getLruSizeLocked() + " total)"
-                        + " mNumCachedHiddenProcs=" + mNumCachedHiddenProcs
-                        + " mNumServiceProcs=" + mNumServiceProcs
-                        + " mNewNumServiceProcs=" + mNewNumServiceProcs);
+                mOomAdjuster.dumpSequenceNumbersLocked(pw);
+                mOomAdjuster.dumpProcCountsLocked(pw);
                 pw.println("  mAllowLowerMemLevel=" + mAllowLowerMemLevel
                         + " mLastMemoryLevel=" + mLastMemoryLevel
                         + " mLastNumProcesses=" + mLastNumProcesses);
@@ -10434,7 +10384,8 @@
                 if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
                     continue;
                 }
-                r.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.PROCS, mProcessList.mLruProcesses.indexOf(r)
+                r.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.PROCS,
+                        mProcessList.mLruProcesses.indexOf(r)
                 );
                 if (r.isPersistent()) {
                     numPers++;
@@ -10458,19 +10409,20 @@
                     && !ai.mTargetInfo.packageName.equals(dumpPackage)) {
                 continue;
             }
-            ai.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.ACTIVE_INSTRUMENTATIONS);
+            ai.writeToProto(proto,
+                    ActivityManagerServiceDumpProcessesProto.ACTIVE_INSTRUMENTATIONS);
         }
 
         int whichAppId = getAppId(dumpPackage);
-        for (int i=0; i<mActiveUids.size(); i++) {
-            UidRecord uidRec = mActiveUids.valueAt(i);
+        for (int i = 0; i < mProcessList.mActiveUids.size(); i++) {
+            UidRecord uidRec = mProcessList.mActiveUids.valueAt(i);
             if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
                 continue;
             }
             uidRec.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.ACTIVE_UIDS);
         }
 
-        for (int i=0; i<mValidateUids.size(); i++) {
+        for (int i = 0; i < mValidateUids.size(); i++) {
             UidRecord uidRec = mValidateUids.valueAt(i);
             if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
                 continue;
@@ -10545,12 +10497,15 @@
             r.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.ON_HOLD_PROCS);
         }
 
-        writeProcessesToGcToProto(proto, ActivityManagerServiceDumpProcessesProto.GC_PROCS, dumpPackage);
-        mAppErrors.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.APP_ERRORS, dumpPackage);
+        writeProcessesToGcToProto(proto, ActivityManagerServiceDumpProcessesProto.GC_PROCS,
+                dumpPackage);
+        mAppErrors.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.APP_ERRORS,
+                dumpPackage);
         mAtmInternal.writeProcessesToProto(proto, dumpPackage, mWakefulness, mTestPssMode);
 
         if (dumpPackage == null) {
-            mUserController.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.USER_CONTROLLER);
+            mUserController.writeToProto(proto,
+            ActivityManagerServiceDumpProcessesProto.USER_CONTROLLER);
         }
 
         final int NI = mUidObservers.getRegisteredCallbackCount();
@@ -10662,11 +10617,7 @@
             proto.write(ActivityManagerServiceDumpProcessesProto.CALL_FINISH_BOOTING, mCallFinishBooting);
             proto.write(ActivityManagerServiceDumpProcessesProto.BOOT_ANIMATION_COMPLETE, mBootAnimationComplete);
             proto.write(ActivityManagerServiceDumpProcessesProto.LAST_POWER_CHECK_UPTIME_MS, mLastPowerCheckUptime);
-            proto.write(ActivityManagerServiceDumpProcessesProto.ADJ_SEQ, mAdjSeq);
-            proto.write(ActivityManagerServiceDumpProcessesProto.LRU_SEQ, mProcessList.mLruSeq);
-            proto.write(ActivityManagerServiceDumpProcessesProto.NUM_NON_CACHED_PROCS, mNumNonCachedProcs);
-            proto.write(ActivityManagerServiceDumpProcessesProto.NUM_SERVICE_PROCS, mNumServiceProcs);
-            proto.write(ActivityManagerServiceDumpProcessesProto.NEW_NUM_SERVICE_PROCS, mNewNumServiceProcs);
+            mOomAdjuster.dumpProcessListVariablesLocked(proto);
             proto.write(ActivityManagerServiceDumpProcessesProto.ALLOW_LOWER_MEM_LEVEL, mAllowLowerMemLevel);
             proto.write(ActivityManagerServiceDumpProcessesProto.LAST_MEMORY_LEVEL, mLastMemoryLevel);
             proto.write(ActivityManagerServiceDumpProcessesProto.LAST_NUM_PROCESSES, mLastNumProcesses);
@@ -14640,7 +14591,7 @@
                     Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
                 final int uid = getUidFromIntent(intent);
                 if (uid != -1) {
-                    final UidRecord uidRec = mActiveUids.get(uid);
+                    final UidRecord uidRec = mProcessList.getUidRecordLocked(uid);
                     if (uidRec != null) {
                         uidRec.updateHasInternetPermission();
                     }
@@ -15418,7 +15369,7 @@
     // Returns whether the app is receiving broadcast.
     // If receiving, fetch all broadcast queues which the app is
     // the current [or imminent] receiver on.
-    private boolean isReceivingBroadcastLocked(ProcessRecord app,
+    boolean isReceivingBroadcastLocked(ProcessRecord app,
             ArraySet<BroadcastQueue> receivingQueues) {
         final int N = app.curReceivers.size();
         if (N > 0) {
@@ -15538,1087 +15489,6 @@
         }
     }
 
-    private final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback =
-            new ComputeOomAdjWindowCallback();
-
-    /** These methods are called inline during computeOomAdjLocked(), on the same thread */
-    private final class ComputeOomAdjWindowCallback
-            implements WindowProcessController.ComputeOomAdjCallback {
-
-        ProcessRecord app;
-        int adj;
-        boolean foregroundActivities;
-        int procState;
-        int schedGroup;
-        int appUid;
-        int logUid;
-        int processStateCurTop;
-
-        void initialize(ProcessRecord app, int adj, boolean foregroundActivities,
-                int procState, int schedGroup, int appUid, int logUid, int processStateCurTop) {
-            this.app = app;
-            this.adj = adj;
-            this.foregroundActivities = foregroundActivities;
-            this.procState = procState;
-            this.schedGroup = schedGroup;
-            this.appUid = appUid;
-            this.logUid = logUid;
-            this.processStateCurTop = processStateCurTop;
-        }
-
-        @Override
-        public void onVisibleActivity() {
-            // App has a visible activity; only upgrade adjustment.
-            if (adj > ProcessList.VISIBLE_APP_ADJ) {
-                adj = ProcessList.VISIBLE_APP_ADJ;
-                app.adjType = "vis-activity";
-                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to vis-activity: " + app);
-                }
-            }
-            if (procState > processStateCurTop) {
-                procState = processStateCurTop;
-                app.adjType = "vis-activity";
-                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ,
-                            "Raise procstate to vis-activity (top): " + app);
-                }
-            }
-            if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) {
-                schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
-            }
-            app.cached = false;
-            app.empty = false;
-            foregroundActivities = true;
-        }
-
-        @Override
-        public void onPausedActivity() {
-            if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
-                adj = ProcessList.PERCEPTIBLE_APP_ADJ;
-                app.adjType = "pause-activity";
-                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to pause-activity: "  + app);
-                }
-            }
-            if (procState > processStateCurTop) {
-                procState = processStateCurTop;
-                app.adjType = "pause-activity";
-                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ,
-                            "Raise procstate to pause-activity (top): "  + app);
-                }
-            }
-            if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) {
-                schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
-            }
-            app.cached = false;
-            app.empty = false;
-            foregroundActivities = true;
-        }
-
-        @Override
-        public void onStoppingActivity(boolean finishing) {
-            if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
-                adj = ProcessList.PERCEPTIBLE_APP_ADJ;
-                app.adjType = "stop-activity";
-                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ,
-                            "Raise adj to stop-activity: "  + app);
-                }
-            }
-
-            // For the process state, we will at this point consider the process to be cached. It
-            // will be cached either as an activity or empty depending on whether the activity is
-            // finishing. We do this so that we can treat the process as cached for purposes of
-            // memory trimming (determining current memory level, trim command to send to process)
-            // since there can be an arbitrary number of stopping processes and they should soon all
-            // go into the cached state.
-            if (!finishing) {
-                if (procState > PROCESS_STATE_LAST_ACTIVITY) {
-                    procState = PROCESS_STATE_LAST_ACTIVITY;
-                    app.adjType = "stop-activity";
-                    if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                        reportOomAdjMessageLocked(TAG_OOM_ADJ,
-                                "Raise procstate to stop-activity: " + app);
-                    }
-                }
-            }
-            app.cached = false;
-            app.empty = false;
-            foregroundActivities = true;
-        }
-
-        @Override
-        public void onOtherActivity() {
-            if (procState > PROCESS_STATE_CACHED_ACTIVITY) {
-                procState = PROCESS_STATE_CACHED_ACTIVITY;
-                app.adjType = "cch-act";
-                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ,
-                            "Raise procstate to cached activity: " + app);
-                }
-            }
-        }
-    }
-
-    private final boolean computeOomAdjLocked(ProcessRecord app, int cachedAdj, ProcessRecord TOP_APP,
-            boolean doingAll, long now, boolean cycleReEval) {
-        if (mAdjSeq == app.adjSeq) {
-            if (app.adjSeq == app.completedAdjSeq) {
-                // This adjustment has already been computed successfully.
-                return false;
-            } else {
-                // The process is being computed, so there is a cycle. We cannot
-                // rely on this process's state.
-                app.containsCycle = true;
-
-                return false;
-            }
-        }
-
-        if (app.thread == null) {
-            app.adjSeq = mAdjSeq;
-            app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_BACKGROUND);
-            app.setCurProcState(ActivityManager.PROCESS_STATE_CACHED_EMPTY);
-            app.curAdj = ProcessList.CACHED_APP_MAX_ADJ;
-            app.setCurRawAdj(ProcessList.CACHED_APP_MAX_ADJ);
-            app.completedAdjSeq = app.adjSeq;
-            return false;
-        }
-
-        app.adjTypeCode = ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN;
-        app.adjSource = null;
-        app.adjTarget = null;
-        app.empty = false;
-        app.cached = false;
-
-        final WindowProcessController wpc = app.getWindowProcessController();
-        final int appUid = app.info.uid;
-        final int logUid = mCurOomAdjUid;
-
-        int prevAppAdj = app.curAdj;
-        int prevProcState = app.getCurProcState();
-
-        if (app.maxAdj <= ProcessList.FOREGROUND_APP_ADJ) {
-            // The max adjustment doesn't allow this app to be anything
-            // below foreground, so it is not worth doing work for it.
-            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making fixed: " + app);
-            }
-            app.adjType = "fixed";
-            app.adjSeq = mAdjSeq;
-            app.setCurRawAdj(app.maxAdj);
-            app.setHasForegroundActivities(false);
-            app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_DEFAULT);
-            app.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT);
-            // System processes can do UI, and when they do we want to have
-            // them trim their memory after the user leaves the UI.  To
-            // facilitate this, here we need to determine whether or not it
-            // is currently showing UI.
-            app.systemNoUi = true;
-            if (app == TOP_APP) {
-                app.systemNoUi = false;
-                app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP);
-                app.adjType = "pers-top-activity";
-            } else if (app.hasTopUi()) {
-                // sched group/proc state adjustment is below
-                app.systemNoUi = false;
-                app.adjType = "pers-top-ui";
-            } else if (wpc.hasVisibleActivities()) {
-                app.systemNoUi = false;
-            }
-            if (!app.systemNoUi) {
-                if (mWakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE) {
-                    // screen on, promote UI
-                    app.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT_UI);
-                    app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP);
-                } else {
-                    // screen off, restrict UI scheduling
-                    app.setCurProcState(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
-                    app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_RESTRICTED);
-                }
-            }
-            app.setCurRawProcState(app.getCurProcState());
-            app.curAdj = app.maxAdj;
-            app.completedAdjSeq = app.adjSeq;
-            // if curAdj is less than prevAppAdj, then this process was promoted
-            return app.curAdj < prevAppAdj || app.getCurProcState() < prevProcState;
-        }
-
-        app.systemNoUi = false;
-
-        final int PROCESS_STATE_CUR_TOP = mAtmInternal.getTopProcessState();
-
-        // Determine the importance of the process, starting with most
-        // important to least, and assign an appropriate OOM adjustment.
-        int adj;
-        int schedGroup;
-        int procState;
-        int cachedAdjSeq;
-
-        boolean foregroundActivities = false;
-        mTmpBroadcastQueue.clear();
-        if (PROCESS_STATE_CUR_TOP == ActivityManager.PROCESS_STATE_TOP && app == TOP_APP) {
-            // The last app on the list is the foreground app.
-            adj = ProcessList.FOREGROUND_APP_ADJ;
-            schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
-            app.adjType = "top-activity";
-            foregroundActivities = true;
-            procState = PROCESS_STATE_CUR_TOP;
-            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making top: " + app);
-            }
-        } else if (app.runningRemoteAnimation) {
-            adj = ProcessList.VISIBLE_APP_ADJ;
-            schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
-            app.adjType = "running-remote-anim";
-            procState = PROCESS_STATE_CUR_TOP;
-            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making running remote anim: " + app);
-            }
-        } else if (app.getActiveInstrumentation() != null) {
-            // Don't want to kill running instrumentation.
-            adj = ProcessList.FOREGROUND_APP_ADJ;
-            schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
-            app.adjType = "instrumentation";
-            procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
-            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making instrumentation: " + app);
-            }
-        } else if (isReceivingBroadcastLocked(app, mTmpBroadcastQueue)) {
-            // An app that is currently receiving a broadcast also
-            // counts as being in the foreground for OOM killer purposes.
-            // It's placed in a sched group based on the nature of the
-            // broadcast as reflected by which queue it's active in.
-            adj = ProcessList.FOREGROUND_APP_ADJ;
-            schedGroup = (mTmpBroadcastQueue.contains(mFgBroadcastQueue))
-                    ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
-            app.adjType = "broadcast";
-            procState = ActivityManager.PROCESS_STATE_RECEIVER;
-            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making broadcast: " + app);
-            }
-        } else if (app.executingServices.size() > 0) {
-            // An app that is currently executing a service callback also
-            // counts as being in the foreground.
-            adj = ProcessList.FOREGROUND_APP_ADJ;
-            schedGroup = app.execServicesFg ?
-                    ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
-            app.adjType = "exec-service";
-            procState = ActivityManager.PROCESS_STATE_SERVICE;
-            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making exec-service: " + app);
-            }
-            //Slog.i(TAG, "EXEC " + (app.execServicesFg ? "FG" : "BG") + ": " + app);
-        } else if (app == TOP_APP) {
-            adj = ProcessList.FOREGROUND_APP_ADJ;
-            schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
-            app.adjType = "top-sleeping";
-            foregroundActivities = true;
-            procState = PROCESS_STATE_CUR_TOP;
-            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making top (sleeping): " + app);
-            }
-        } else {
-            // As far as we know the process is empty.  We may change our mind later.
-            schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
-            // At this point we don't actually know the adjustment.  Use the cached adj
-            // value that the caller wants us to.
-            adj = cachedAdj;
-            procState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
-            app.cached = true;
-            app.empty = true;
-            app.adjType = "cch-empty";
-            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making empty: " + app);
-            }
-        }
-
-        // Examine all activities if not already foreground.
-        if (!foregroundActivities && wpc.hasActivities()) {
-            mTmpComputeOomAdjWindowCallback.initialize(app, adj, foregroundActivities, procState,
-                    schedGroup, appUid, logUid, PROCESS_STATE_CUR_TOP);
-            final int minLayer = wpc.computeOomAdjFromActivities(
-                    ProcessList.VISIBLE_APP_LAYER_MAX, mTmpComputeOomAdjWindowCallback);
-
-            adj = mTmpComputeOomAdjWindowCallback.adj;
-            foregroundActivities = mTmpComputeOomAdjWindowCallback.foregroundActivities;
-            procState = mTmpComputeOomAdjWindowCallback.procState;
-            schedGroup = mTmpComputeOomAdjWindowCallback.schedGroup;
-
-            if (adj == ProcessList.VISIBLE_APP_ADJ) {
-                adj += minLayer;
-            }
-        }
-
-        if (procState > ActivityManager.PROCESS_STATE_CACHED_RECENT && app.hasRecentTasks()) {
-            procState = ActivityManager.PROCESS_STATE_CACHED_RECENT;
-            app.adjType = "cch-rec";
-            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to cached recent: " + app);
-            }
-        }
-
-        if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
-                || procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
-            if (app.hasForegroundServices()) {
-                // The user is aware of this app, so make it visible.
-                adj = ProcessList.PERCEPTIBLE_APP_ADJ;
-                procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
-                app.cached = false;
-                app.adjType = "fg-service";
-                schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
-                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to fg service: " + app);
-                }
-            } else if (app.hasOverlayUi()) {
-                // The process is display an overlay UI.
-                adj = ProcessList.PERCEPTIBLE_APP_ADJ;
-                procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
-                app.cached = false;
-                app.adjType = "has-overlay-ui";
-                schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
-                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to overlay ui: " + app);
-                }
-            }
-        }
-
-        // If the app was recently in the foreground and moved to a foreground service status,
-        // allow it to get a higher rank in memory for some time, compared to other foreground
-        // services so that it can finish performing any persistence/processing of in-memory state.
-        if (app.hasForegroundServices() && adj > ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ
-                && (app.lastTopTime + mConstants.TOP_TO_FGS_GRACE_DURATION > now
-                    || app.setProcState <= ActivityManager.PROCESS_STATE_TOP)) {
-            adj = ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ;
-            app.adjType = "fg-service-act";
-            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to recent fg: " + app);
-            }
-        }
-
-        if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
-                || procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) {
-            if (app.forcingToImportant != null) {
-                // This is currently used for toasts...  they are not interactive, and
-                // we don't want them to cause the app to become fully foreground (and
-                // thus out of background check), so we yes the best background level we can.
-                adj = ProcessList.PERCEPTIBLE_APP_ADJ;
-                procState = ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
-                app.cached = false;
-                app.adjType = "force-imp";
-                app.adjSource = app.forcingToImportant;
-                schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
-                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to force imp: " + app);
-                }
-            }
-        }
-
-        if (mAtmInternal.isHeavyWeightProcess(app.getWindowProcessController())) {
-            if (adj > ProcessList.HEAVY_WEIGHT_APP_ADJ) {
-                // We don't want to kill the current heavy-weight process.
-                adj = ProcessList.HEAVY_WEIGHT_APP_ADJ;
-                schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
-                app.cached = false;
-                app.adjType = "heavy";
-                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to heavy: " + app);
-                }
-            }
-            if (procState > ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
-                procState = ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
-                app.adjType = "heavy";
-                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to heavy: " + app);
-                }
-            }
-        }
-
-        if (wpc.isHomeProcess()) {
-            if (adj > ProcessList.HOME_APP_ADJ) {
-                // This process is hosting what we currently consider to be the
-                // home app, so we don't want to let it go into the background.
-                adj = ProcessList.HOME_APP_ADJ;
-                schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
-                app.cached = false;
-                app.adjType = "home";
-                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to home: " + app);
-                }
-            }
-            if (procState > ActivityManager.PROCESS_STATE_HOME) {
-                procState = ActivityManager.PROCESS_STATE_HOME;
-                app.adjType = "home";
-                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to home: " + app);
-                }
-            }
-        }
-
-        if (wpc.isPreviousProcess() && app.hasActivities()) {
-            if (adj > ProcessList.PREVIOUS_APP_ADJ) {
-                // This was the previous process that showed UI to the user.
-                // We want to try to keep it around more aggressively, to give
-                // a good experience around switching between two apps.
-                adj = ProcessList.PREVIOUS_APP_ADJ;
-                schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
-                app.cached = false;
-                app.adjType = "previous";
-                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to prev: " + app);
-                }
-            }
-            if (procState > PROCESS_STATE_LAST_ACTIVITY) {
-                procState = PROCESS_STATE_LAST_ACTIVITY;
-                app.adjType = "previous";
-                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to prev: " + app);
-                }
-            }
-        }
-
-        if (false) Slog.i(TAG, "OOM " + app + ": initial adj=" + adj
-                + " reason=" + app.adjType);
-
-        // By default, we use the computed adjustment.  It may be changed if
-        // there are applications dependent on our services or providers, but
-        // this gives us a baseline and makes sure we don't get into an
-        // infinite recursion. If we're re-evaluating due to cycles, use the previously computed
-        // values.
-        app.setCurRawAdj(!cycleReEval ? adj : Math.min(adj, app.getCurRawAdj()));
-        app.setCurRawProcState(!cycleReEval
-                ? procState
-                : Math.min(procState, app.getCurRawProcState()));
-
-        app.hasStartedServices = false;
-        app.adjSeq = mAdjSeq;
-
-        if (mBackupTarget != null && app == mBackupTarget.app) {
-            // If possible we want to avoid killing apps while they're being backed up
-            if (adj > ProcessList.BACKUP_APP_ADJ) {
-                if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "oom BACKUP_APP_ADJ for " + app);
-                adj = ProcessList.BACKUP_APP_ADJ;
-                if (procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) {
-                    procState = ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
-                }
-                app.adjType = "backup";
-                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to backup: " + app);
-                }
-                app.cached = false;
-            }
-            if (procState > ActivityManager.PROCESS_STATE_BACKUP) {
-                procState = ActivityManager.PROCESS_STATE_BACKUP;
-                app.adjType = "backup";
-                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to backup: " + app);
-                }
-            }
-        }
-
-        boolean mayBeTop = false;
-        String mayBeTopType = null;
-        Object mayBeTopSource = null;
-        Object mayBeTopTarget = null;
-
-        for (int is = app.services.size()-1;
-                is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
-                        || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
-                        || procState > ActivityManager.PROCESS_STATE_TOP);
-                is--) {
-            ServiceRecord s = app.services.valueAt(is);
-            if (s.startRequested) {
-                app.hasStartedServices = true;
-                if (procState > ActivityManager.PROCESS_STATE_SERVICE) {
-                    procState = ActivityManager.PROCESS_STATE_SERVICE;
-                    app.adjType = "started-services";
-                    if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                        reportOomAdjMessageLocked(TAG_OOM_ADJ,
-                                "Raise procstate to started service: " + app);
-                    }
-                }
-                if (app.hasShownUi && !wpc.isHomeProcess()) {
-                    // If this process has shown some UI, let it immediately
-                    // go to the LRU list because it may be pretty heavy with
-                    // UI stuff.  We'll tag it with a label just to help
-                    // debug and understand what is going on.
-                    if (adj > ProcessList.SERVICE_ADJ) {
-                        app.adjType = "cch-started-ui-services";
-                    }
-                } else {
-                    if (now < (s.lastActivity + mConstants.MAX_SERVICE_INACTIVITY)) {
-                        // This service has seen some activity within
-                        // recent memory, so we will keep its process ahead
-                        // of the background processes.
-                        if (adj > ProcessList.SERVICE_ADJ) {
-                            adj = ProcessList.SERVICE_ADJ;
-                            app.adjType = "started-services";
-                            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                                reportOomAdjMessageLocked(TAG_OOM_ADJ,
-                                        "Raise adj to started service: " + app);
-                            }
-                            app.cached = false;
-                        }
-                    }
-                    // If we have let the service slide into the background
-                    // state, still have some text describing what it is doing
-                    // even though the service no longer has an impact.
-                    if (adj > ProcessList.SERVICE_ADJ) {
-                        app.adjType = "cch-started-services";
-                    }
-                }
-            }
-
-            for (int conni = s.connections.size()-1;
-                    conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
-                            || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
-                            || procState > ActivityManager.PROCESS_STATE_TOP);
-                    conni--) {
-                ArrayList<ConnectionRecord> clist = s.connections.valueAt(conni);
-                for (int i = 0;
-                        i < clist.size() && (adj > ProcessList.FOREGROUND_APP_ADJ
-                                || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
-                                || procState > ActivityManager.PROCESS_STATE_TOP);
-                        i++) {
-                    // XXX should compute this based on the max of
-                    // all connected clients.
-                    ConnectionRecord cr = clist.get(i);
-                    if (cr.binding.client == app) {
-                        // Binding to oneself is not interesting.
-                        continue;
-                    }
-
-                    boolean trackedProcState = false;
-                    if ((cr.flags&Context.BIND_WAIVE_PRIORITY) == 0) {
-                        ProcessRecord client = cr.binding.client;
-                        computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now, cycleReEval);
-
-                        if (shouldSkipDueToCycle(app, client, procState, adj, cycleReEval)) {
-                            continue;
-                        }
-
-                        int clientAdj = client.getCurRawAdj();
-                        int clientProcState = client.getCurRawProcState();
-
-                        if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
-                            // If the other app is cached for any reason, for purposes here
-                            // we are going to consider it empty.  The specific cached state
-                            // doesn't propagate except under certain conditions.
-                            clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
-                        }
-                        String adjType = null;
-                        if ((cr.flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) {
-                            // Not doing bind OOM management, so treat
-                            // this guy more like a started service.
-                            if (app.hasShownUi && !wpc.isHomeProcess()) {
-                                // If this process has shown some UI, let it immediately
-                                // go to the LRU list because it may be pretty heavy with
-                                // UI stuff.  We'll tag it with a label just to help
-                                // debug and understand what is going on.
-                                if (adj > clientAdj) {
-                                    adjType = "cch-bound-ui-services";
-                                }
-                                app.cached = false;
-                                clientAdj = adj;
-                                clientProcState = procState;
-                            } else {
-                                if (now >= (s.lastActivity + mConstants.MAX_SERVICE_INACTIVITY)) {
-                                    // This service has not seen activity within
-                                    // recent memory, so allow it to drop to the
-                                    // LRU list if there is no other reason to keep
-                                    // it around.  We'll also tag it with a label just
-                                    // to help debug and undertand what is going on.
-                                    if (adj > clientAdj) {
-                                        adjType = "cch-bound-services";
-                                    }
-                                    clientAdj = adj;
-                                }
-                            }
-                        }
-                        if (adj > clientAdj) {
-                            // If this process has recently shown UI, and
-                            // the process that is binding to it is less
-                            // important than being visible, then we don't
-                            // care about the binding as much as we care
-                            // about letting this process get into the LRU
-                            // list to be killed and restarted if needed for
-                            // memory.
-                            if (app.hasShownUi && !wpc.isHomeProcess()
-                                    && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
-                                if (adj >= ProcessList.CACHED_APP_MIN_ADJ) {
-                                    adjType = "cch-bound-ui-services";
-                                }
-                            } else {
-                                int newAdj;
-                                if ((cr.flags&(Context.BIND_ABOVE_CLIENT
-                                        |Context.BIND_IMPORTANT)) != 0) {
-                                    if (clientAdj >= ProcessList.PERSISTENT_SERVICE_ADJ) {
-                                        newAdj = clientAdj;
-                                    } else {
-                                        // make this service persistent
-                                        newAdj = ProcessList.PERSISTENT_SERVICE_ADJ;
-                                        schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
-                                        procState = ActivityManager.PROCESS_STATE_PERSISTENT;
-                                        cr.trackProcState(procState, mAdjSeq, now);
-                                        trackedProcState = true;
-                                    }
-                                } else if ((cr.flags & Context.BIND_ADJUST_BELOW_PERCEPTIBLE) != 0
-                                        && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ
-                                        && adj > ProcessList.PERCEPTIBLE_APP_ADJ + 1) {
-                                    newAdj = ProcessList.PERCEPTIBLE_APP_ADJ + 1;
-                                } else if ((cr.flags&Context.BIND_NOT_VISIBLE) != 0
-                                        && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ
-                                        && adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
-                                    newAdj = ProcessList.PERCEPTIBLE_APP_ADJ;
-                                } else if (clientAdj >= ProcessList.PERCEPTIBLE_APP_ADJ) {
-                                    newAdj = clientAdj;
-                                } else {
-                                    if (adj > ProcessList.VISIBLE_APP_ADJ) {
-                                        newAdj = Math.max(clientAdj, ProcessList.VISIBLE_APP_ADJ);
-                                    } else {
-                                        newAdj = adj;
-                                    }
-                                }
-                                if (!client.cached) {
-                                    app.cached = false;
-                                }
-                                if (adj >  newAdj) {
-                                    adj = newAdj;
-                                    app.setCurRawAdj(adj);
-                                    adjType = "service";
-                                }
-                            }
-                        }
-                        if ((cr.flags & (Context.BIND_NOT_FOREGROUND
-                                | Context.BIND_IMPORTANT_BACKGROUND)) == 0) {
-                            // This will treat important bound services identically to
-                            // the top app, which may behave differently than generic
-                            // foreground work.
-                            final int curSchedGroup = client.getCurrentSchedulingGroup();
-                            if (curSchedGroup > schedGroup) {
-                                if ((cr.flags&Context.BIND_IMPORTANT) != 0) {
-                                    schedGroup = curSchedGroup;
-                                } else {
-                                    schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
-                                }
-                            }
-                            if (clientProcState <= ActivityManager.PROCESS_STATE_TOP) {
-                                if (clientProcState == ActivityManager.PROCESS_STATE_TOP) {
-                                    // Special handling of clients who are in the top state.
-                                    // We *may* want to consider this process to be in the
-                                    // top state as well, but only if there is not another
-                                    // reason for it to be running.  Being on the top is a
-                                    // special state, meaning you are specifically running
-                                    // for the current top app.  If the process is already
-                                    // running in the background for some other reason, it
-                                    // is more important to continue considering it to be
-                                    // in the background state.
-                                    mayBeTop = true;
-                                    mayBeTopType = "service";
-                                    mayBeTopSource = cr.binding.client;
-                                    mayBeTopTarget = s.instanceName;
-                                    clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
-                                } else {
-                                    // Special handling for above-top states (persistent
-                                    // processes).  These should not bring the current process
-                                    // into the top state, since they are not on top.  Instead
-                                    // give them the best state after that.
-                                    if ((cr.flags&Context.BIND_FOREGROUND_SERVICE) != 0) {
-                                        clientProcState =
-                                                ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-                                    } else if (mWakefulness
-                                                    == PowerManagerInternal.WAKEFULNESS_AWAKE &&
-                                            (cr.flags&Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE)
-                                                    != 0) {
-                                        clientProcState =
-                                                ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-                                    } else {
-                                        clientProcState =
-                                                ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
-                                    }
-                                }
-                            }
-                        } else if ((cr.flags & Context.BIND_IMPORTANT_BACKGROUND) == 0) {
-                            if (clientProcState <
-                                    ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) {
-                                clientProcState =
-                                        ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
-                            }
-                        } else {
-                            if (clientProcState <
-                                    ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) {
-                                clientProcState =
-                                        ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND;
-                            }
-                        }
-                        if (!trackedProcState) {
-                            cr.trackProcState(clientProcState, mAdjSeq, now);
-                        }
-                        if (procState > clientProcState) {
-                            procState = clientProcState;
-                            app.setCurRawProcState(procState);
-                            if (adjType == null) {
-                                adjType = "service";
-                            }
-                        }
-                        if (procState < ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
-                                && (cr.flags & Context.BIND_SHOWING_UI) != 0) {
-                            app.setPendingUiClean(true);
-                        }
-                        if (adjType != null) {
-                            app.adjType = adjType;
-                            app.adjTypeCode = ActivityManager.RunningAppProcessInfo
-                                    .REASON_SERVICE_IN_USE;
-                            app.adjSource = cr.binding.client;
-                            app.adjSourceProcState = clientProcState;
-                            app.adjTarget = s.instanceName;
-                            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
-                                        + ": " + app + ", due to " + cr.binding.client
-                                        + " adj=" + adj + " procState="
-                                        + ProcessList.makeProcStateString(procState));
-                            }
-                        }
-                    }
-                    if ((cr.flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
-                        app.treatLikeActivity = true;
-                    }
-                    final ActivityServiceConnectionsHolder a = cr.activity;
-                    if ((cr.flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) {
-                        if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ
-                                && a.isActivityVisible()) {
-                            adj = ProcessList.FOREGROUND_APP_ADJ;
-                            app.setCurRawAdj(adj);
-                            if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) {
-                                if ((cr.flags&Context.BIND_IMPORTANT) != 0) {
-                                    schedGroup = ProcessList.SCHED_GROUP_TOP_APP_BOUND;
-                                } else {
-                                    schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
-                                }
-                            }
-                            app.cached = false;
-                            app.adjType = "service";
-                            app.adjTypeCode = ActivityManager.RunningAppProcessInfo
-                                    .REASON_SERVICE_IN_USE;
-                            app.adjSource = a;
-                            app.adjSourceProcState = procState;
-                            app.adjTarget = s.instanceName;
-                            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                                reportOomAdjMessageLocked(TAG_OOM_ADJ,
-                                        "Raise to service w/activity: " + app);
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        for (int provi = app.pubProviders.size()-1;
-                provi >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
-                        || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
-                        || procState > ActivityManager.PROCESS_STATE_TOP);
-                provi--) {
-            ContentProviderRecord cpr = app.pubProviders.valueAt(provi);
-            for (int i = cpr.connections.size()-1;
-                    i >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
-                            || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
-                            || procState > ActivityManager.PROCESS_STATE_TOP);
-                    i--) {
-                ContentProviderConnection conn = cpr.connections.get(i);
-                ProcessRecord client = conn.client;
-                if (client == app) {
-                    // Being our own client is not interesting.
-                    continue;
-                }
-                computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now, cycleReEval);
-
-                if (shouldSkipDueToCycle(app, client, procState, adj, cycleReEval)) {
-                    continue;
-                }
-
-                int clientAdj = client.getCurRawAdj();
-                int clientProcState = client.getCurRawProcState();
-
-                if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
-                    // If the other app is cached for any reason, for purposes here
-                    // we are going to consider it empty.
-                    clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
-                }
-                String adjType = null;
-                if (adj > clientAdj) {
-                    if (app.hasShownUi && !wpc.isHomeProcess()
-                            && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
-                        adjType = "cch-ui-provider";
-                    } else {
-                        adj = clientAdj > ProcessList.FOREGROUND_APP_ADJ
-                                ? clientAdj : ProcessList.FOREGROUND_APP_ADJ;
-                        app.setCurRawAdj(adj);
-                        adjType = "provider";
-                    }
-                    app.cached &= client.cached;
-                }
-                if (clientProcState <= ActivityManager.PROCESS_STATE_TOP) {
-                    if (clientProcState == ActivityManager.PROCESS_STATE_TOP) {
-                        // Special handling of clients who are in the top state.
-                        // We *may* want to consider this process to be in the
-                        // top state as well, but only if there is not another
-                        // reason for it to be running.  Being on the top is a
-                        // special state, meaning you are specifically running
-                        // for the current top app.  If the process is already
-                        // running in the background for some other reason, it
-                        // is more important to continue considering it to be
-                        // in the background state.
-                        mayBeTop = true;
-                        clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
-                        mayBeTopType = adjType = "provider-top";
-                        mayBeTopSource = client;
-                        mayBeTopTarget = cpr.name;
-                    } else {
-                        // Special handling for above-top states (persistent
-                        // processes).  These should not bring the current process
-                        // into the top state, since they are not on top.  Instead
-                        // give them the best state after that.
-                        clientProcState =
-                                ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-                        if (adjType == null) {
-                            adjType = "provider";
-                        }
-                    }
-                }
-                conn.trackProcState(clientProcState, mAdjSeq, now);
-                if (procState > clientProcState) {
-                    procState = clientProcState;
-                    app.setCurRawProcState(procState);
-                }
-                if (client.getCurrentSchedulingGroup() > schedGroup) {
-                    schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
-                }
-                if (adjType != null) {
-                    app.adjType = adjType;
-                    app.adjTypeCode = ActivityManager.RunningAppProcessInfo
-                            .REASON_PROVIDER_IN_USE;
-                    app.adjSource = client;
-                    app.adjSourceProcState = clientProcState;
-                    app.adjTarget = cpr.name;
-                    if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                        reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
-                                + ": " + app + ", due to " + client
-                                + " adj=" + adj + " procState="
-                                + ProcessList.makeProcStateString(procState));
-                    }
-                }
-            }
-            // If the provider has external (non-framework) process
-            // dependencies, ensure that its adjustment is at least
-            // FOREGROUND_APP_ADJ.
-            if (cpr.hasExternalProcessHandles()) {
-                if (adj > ProcessList.FOREGROUND_APP_ADJ) {
-                    adj = ProcessList.FOREGROUND_APP_ADJ;
-                    app.setCurRawAdj(adj);
-                    schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
-                    app.cached = false;
-                    app.adjType = "ext-provider";
-                    app.adjTarget = cpr.name;
-                    if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                        reportOomAdjMessageLocked(TAG_OOM_ADJ,
-                                "Raise adj to external provider: " + app);
-                    }
-                }
-                if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
-                    procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
-                    app.setCurRawProcState(procState);
-                    if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                        reportOomAdjMessageLocked(TAG_OOM_ADJ,
-                                "Raise procstate to external provider: " + app);
-                    }
-                }
-            }
-        }
-
-        if (app.lastProviderTime > 0 &&
-                (app.lastProviderTime+mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) {
-            if (adj > ProcessList.PREVIOUS_APP_ADJ) {
-                adj = ProcessList.PREVIOUS_APP_ADJ;
-                schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
-                app.cached = false;
-                app.adjType = "recent-provider";
-                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ,
-                            "Raise adj to recent provider: " + app);
-                }
-            }
-            if (procState > PROCESS_STATE_LAST_ACTIVITY) {
-                procState = PROCESS_STATE_LAST_ACTIVITY;
-                app.adjType = "recent-provider";
-                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ,
-                            "Raise procstate to recent provider: " + app);
-                }
-            }
-        }
-
-        if (mayBeTop && procState > ActivityManager.PROCESS_STATE_TOP) {
-            // A client of one of our services or providers is in the top state.  We
-            // *may* want to be in the top state, but not if we are already running in
-            // the background for some other reason.  For the decision here, we are going
-            // to pick out a few specific states that we want to remain in when a client
-            // is top (states that tend to be longer-term) and otherwise allow it to go
-            // to the top state.
-            switch (procState) {
-                case ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE:
-                case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
-                    // Something else is keeping it at this level, just leave it.
-                    break;
-                case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND:
-                case ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND:
-                case ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND:
-                case ActivityManager.PROCESS_STATE_SERVICE:
-                    // These all are longer-term states, so pull them up to the top
-                    // of the background states, but not all the way to the top state.
-                    procState = ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-                    app.adjType = mayBeTopType;
-                    app.adjSource = mayBeTopSource;
-                    app.adjTarget = mayBeTopTarget;
-                    if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                        reportOomAdjMessageLocked(TAG_OOM_ADJ, "May be top raise to " + mayBeTopType
-                                + ": " + app + ", due to " + mayBeTopSource
-                                + " adj=" + adj + " procState="
-                                + ProcessList.makeProcStateString(procState));
-                    }
-                    break;
-                default:
-                    // Otherwise, top is a better choice, so take it.
-                    procState = ActivityManager.PROCESS_STATE_TOP;
-                    app.adjType = mayBeTopType;
-                    app.adjSource = mayBeTopSource;
-                    app.adjTarget = mayBeTopTarget;
-                    if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                        reportOomAdjMessageLocked(TAG_OOM_ADJ, "May be top raise to " + mayBeTopType
-                                + ": " + app + ", due to " + mayBeTopSource
-                                + " adj=" + adj + " procState="
-                                + ProcessList.makeProcStateString(procState));
-                    }
-                    break;
-            }
-        }
-
-        if (procState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) {
-            if (app.hasClientActivities()) {
-                // This is a cached process, but with client activities.  Mark it so.
-                procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
-                app.adjType = "cch-client-act";
-            } else if (app.treatLikeActivity) {
-                // This is a cached process, but somebody wants us to treat it like it has
-                // an activity, okay!
-                procState = PROCESS_STATE_CACHED_ACTIVITY;
-                app.adjType = "cch-as-act";
-            }
-        }
-
-        if (adj == ProcessList.SERVICE_ADJ) {
-            if (doingAll) {
-                app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3);
-                mNewNumServiceProcs++;
-                //Slog.i(TAG, "ADJ " + app + " serviceb=" + app.serviceb);
-                if (!app.serviceb) {
-                    // This service isn't far enough down on the LRU list to
-                    // normally be a B service, but if we are low on RAM and it
-                    // is large we want to force it down since we would prefer to
-                    // keep launcher over it.
-                    if (mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL
-                            && app.lastPss >= mProcessList.getCachedRestoreThresholdKb()) {
-                        app.serviceHighRam = true;
-                        app.serviceb = true;
-                        //Slog.i(TAG, "ADJ " + app + " high ram!");
-                    } else {
-                        mNewNumAServiceProcs++;
-                        //Slog.i(TAG, "ADJ " + app + " not high ram!");
-                    }
-                } else {
-                    app.serviceHighRam = false;
-                }
-            }
-            if (app.serviceb) {
-                adj = ProcessList.SERVICE_B_ADJ;
-            }
-        }
-
-        app.setCurRawAdj(adj);
-
-        //Slog.i(TAG, "OOM ADJ " + app + ": pid=" + app.pid +
-        //      " adj=" + adj + " curAdj=" + app.curAdj + " maxAdj=" + app.maxAdj);
-        if (adj > app.maxAdj) {
-            adj = app.maxAdj;
-            if (app.maxAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
-                schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
-            }
-        }
-
-        // Put bound foreground services in a special sched group for additional
-        // restrictions on screen off
-        if (procState >= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE &&
-            mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) {
-            if (schedGroup > ProcessList.SCHED_GROUP_RESTRICTED) {
-                schedGroup = ProcessList.SCHED_GROUP_RESTRICTED;
-            }
-        }
-
-        // Do final modification to adj.  Everything we do between here and applying
-        // the final setAdj must be done in this function, because we will also use
-        // it when computing the final cached adj later.  Note that we don't need to
-        // worry about this for max adj above, since max adj will always be used to
-        // keep it out of the cached vaues.
-        app.curAdj = app.modifyRawOomAdj(adj);
-        app.setCurrentSchedulingGroup(schedGroup);
-        app.setCurProcState(procState);
-        app.setCurRawProcState(procState);
-        app.setHasForegroundActivities(foregroundActivities);
-        app.completedAdjSeq = mAdjSeq;
-
-        // if curAdj or curProcState improved, then this process was promoted
-        return app.curAdj < prevAppAdj || app.getCurProcState() < prevProcState;
-    }
-
-    /**
-     * Checks if for the given app and client, there's a cycle that should skip over the client
-     * for now or use partial values to evaluate the effect of the client binding.
-     * @param app
-     * @param client
-     * @param procState procstate evaluated so far for this app
-     * @param adj oom_adj evaluated so far for this app
-     * @param cycleReEval whether we're currently re-evaluating due to a cycle, and not the first
-     *                    evaluation.
-     * @return whether to skip using the client connection at this time
-     */
-    private boolean shouldSkipDueToCycle(ProcessRecord app, ProcessRecord client,
-            int procState, int adj, boolean cycleReEval) {
-        if (client.containsCycle) {
-            // We've detected a cycle. We should retry computeOomAdjLocked later in
-            // case a later-checked connection from a client  would raise its
-            // priority legitimately.
-            app.containsCycle = true;
-            // If the client has not been completely evaluated, check if it's worth
-            // using the partial values.
-            if (client.completedAdjSeq < mAdjSeq) {
-                if (cycleReEval) {
-                    // If the partial values are no better, skip until the next
-                    // attempt
-                    if (client.getCurRawProcState() >= procState
-                            && client.getCurRawAdj() >= adj) {
-                        return true;
-                    }
-                    // Else use the client's partial procstate and adj to adjust the
-                    // effect of the binding
-                } else {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
     private static final class RecordPssRunnable implements Runnable {
         private final ActivityManagerService mService;
         private final ProcessRecord mProc;
@@ -17016,287 +15886,6 @@
         }
     }
 
-    private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
-            long nowElapsed) {
-        boolean success = true;
-
-        if (app.getCurRawAdj() != app.setRawAdj) {
-            app.setRawAdj = app.getCurRawAdj();
-        }
-
-        int changes = 0;
-
-        if (app.curAdj != app.setAdj) {
-            // don't compact during bootup
-            if (mConstants.USE_COMPACTION && mBooted) {
-                // Perform a minor compaction when a perceptible app becomes the prev/home app
-                // Perform a major compaction when any app enters cached
-                // reminder: here, setAdj is previous state, curAdj is upcoming state
-                if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ &&
-                    (app.curAdj == ProcessList.PREVIOUS_APP_ADJ ||
-                     app.curAdj == ProcessList.HOME_APP_ADJ)) {
-                    mAppCompact.compactAppSome(app);
-                } else if (app.setAdj < ProcessList.CACHED_APP_MIN_ADJ &&
-                           app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ) {
-                    mAppCompact.compactAppFull(app);
-                }
-            }
-            ProcessList.setOomAdj(app.pid, app.uid, app.curAdj);
-            if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mCurOomAdjUid == app.info.uid) {
-                String msg = "Set " + app.pid + " " + app.processName + " adj "
-                        + app.curAdj + ": " + app.adjType;
-                reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
-            }
-            app.setAdj = app.curAdj;
-            app.verifiedAdj = ProcessList.INVALID_ADJ;
-        }
-
-        final int curSchedGroup = app.getCurrentSchedulingGroup();
-        if (app.setSchedGroup != curSchedGroup) {
-            int oldSchedGroup = app.setSchedGroup;
-            app.setSchedGroup = curSchedGroup;
-            if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mCurOomAdjUid == app.uid) {
-                String msg = "Setting sched group of " + app.processName
-                        + " to " + curSchedGroup + ": " + app.adjType;
-                reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
-            }
-            if (app.waitingToKill != null && app.curReceivers.isEmpty()
-                    && app.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND) {
-                app.kill(app.waitingToKill, true);
-                success = false;
-            } else {
-                int processGroup;
-                switch (curSchedGroup) {
-                    case ProcessList.SCHED_GROUP_BACKGROUND:
-                        processGroup = THREAD_GROUP_BG_NONINTERACTIVE;
-                        break;
-                    case ProcessList.SCHED_GROUP_TOP_APP:
-                    case ProcessList.SCHED_GROUP_TOP_APP_BOUND:
-                        processGroup = THREAD_GROUP_TOP_APP;
-                        break;
-                    case ProcessList.SCHED_GROUP_RESTRICTED:
-                        processGroup = THREAD_GROUP_RESTRICTED;
-                        break;
-                    default:
-                        processGroup = THREAD_GROUP_DEFAULT;
-                        break;
-                }
-                long oldId = Binder.clearCallingIdentity();
-                try {
-                    setProcessGroup(app.pid, processGroup);
-                    if (curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) {
-                        // do nothing if we already switched to RT
-                        if (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
-                            app.getWindowProcessController().onTopProcChanged();
-                            if (mUseFifoUiScheduling) {
-                                // Switch UI pipeline for app to SCHED_FIFO
-                                app.savedPriority = Process.getThreadPriority(app.pid);
-                                scheduleAsFifoPriority(app.pid, /* suppressLogs */true);
-                                if (app.renderThreadTid != 0) {
-                                    scheduleAsFifoPriority(app.renderThreadTid,
-                                        /* suppressLogs */true);
-                                    if (DEBUG_OOM_ADJ) {
-                                        Slog.d("UI_FIFO", "Set RenderThread (TID " +
-                                            app.renderThreadTid + ") to FIFO");
-                                    }
-                                } else {
-                                    if (DEBUG_OOM_ADJ) {
-                                        Slog.d("UI_FIFO", "Not setting RenderThread TID");
-                                    }
-                                }
-                            } else {
-                                // Boost priority for top app UI and render threads
-                                setThreadPriority(app.pid, TOP_APP_PRIORITY_BOOST);
-                                if (app.renderThreadTid != 0) {
-                                    try {
-                                        setThreadPriority(app.renderThreadTid,
-                                                TOP_APP_PRIORITY_BOOST);
-                                    } catch (IllegalArgumentException e) {
-                                        // thread died, ignore
-                                    }
-                                }
-                            }
-                        }
-                    } else if (oldSchedGroup == ProcessList.SCHED_GROUP_TOP_APP &&
-                            curSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
-                        app.getWindowProcessController().onTopProcChanged();
-                        if (mUseFifoUiScheduling) {
-                            try {
-                                // Reset UI pipeline to SCHED_OTHER
-                                setThreadScheduler(app.pid, SCHED_OTHER, 0);
-                                setThreadPriority(app.pid, app.savedPriority);
-                                if (app.renderThreadTid != 0) {
-                                    setThreadScheduler(app.renderThreadTid,
-                                        SCHED_OTHER, 0);
-                                    setThreadPriority(app.renderThreadTid, -4);
-                                }
-                            } catch (IllegalArgumentException e) {
-                                Slog.w(TAG,
-                                        "Failed to set scheduling policy, thread does not exist:\n"
-                                                + e);
-                            } catch (SecurityException e) {
-                                Slog.w(TAG, "Failed to set scheduling policy, not allowed:\n" + e);
-                            }
-                        } else {
-                            // Reset priority for top app UI and render threads
-                            setThreadPriority(app.pid, 0);
-                            if (app.renderThreadTid != 0) {
-                                setThreadPriority(app.renderThreadTid, 0);
-                            }
-                        }
-                    }
-                } catch (Exception e) {
-                    if (false) {
-                        Slog.w(TAG, "Failed setting process group of " + app.pid
-                                + " to " + app.getCurrentSchedulingGroup());
-                        Slog.w(TAG, "at location", e);
-                    }
-                } finally {
-                    Binder.restoreCallingIdentity(oldId);
-                }
-            }
-        }
-        if (app.repForegroundActivities != app.hasForegroundActivities()) {
-            app.repForegroundActivities = app.hasForegroundActivities();
-            changes |= ProcessChangeItem.CHANGE_ACTIVITIES;
-        }
-        if (app.getReportedProcState() != app.getCurProcState()) {
-            app.setReportedProcState(app.getCurProcState());
-            if (app.thread != null) {
-                try {
-                    if (false) {
-                        //RuntimeException h = new RuntimeException("here");
-                        Slog.i(TAG, "Sending new process state " + app.getReportedProcState()
-                                + " to " + app /*, h*/);
-                    }
-                    app.thread.setProcessState(app.getReportedProcState());
-                } catch (RemoteException e) {
-                }
-            }
-        }
-        if (app.setProcState == PROCESS_STATE_NONEXISTENT
-                || ProcessList.procStatesDifferForMem(app.getCurProcState(), app.setProcState)) {
-            if (false && mTestPssMode && app.setProcState >= 0 && app.lastStateTime <= (now-200)) {
-                // Experimental code to more aggressively collect pss while
-                // running test...  the problem is that this tends to collect
-                // the data right when a process is transitioning between process
-                // states, which will tend to give noisy data.
-                long start = SystemClock.uptimeMillis();
-                long startTime = SystemClock.currentThreadTimeMillis();
-                long pss = Debug.getPss(app.pid, mTmpLong, null);
-                long endTime = SystemClock.currentThreadTimeMillis();
-                recordPssSampleLocked(app, app.getCurProcState(), pss, mTmpLong[0], mTmpLong[1],
-                        mTmpLong[2], ProcessStats.ADD_PSS_INTERNAL_SINGLE, endTime-startTime, now);
-                mPendingPssProcesses.remove(app);
-                Slog.i(TAG, "Recorded pss for " + app + " state " + app.setProcState
-                        + " to " + app.getCurProcState() + ": "
-                        + (SystemClock.uptimeMillis()-start) + "ms");
-            }
-            app.lastStateTime = now;
-            app.nextPssTime = ProcessList.computeNextPssTime(app.getCurProcState(),
-                    app.procStateMemTracker, mTestPssMode, mAtmInternal.isSleeping(), now);
-            if (DEBUG_PSS) Slog.d(TAG_PSS, "Process state change from "
-                    + ProcessList.makeProcStateString(app.setProcState) + " to "
-                    + ProcessList.makeProcStateString(app.getCurProcState()) + " next pss in "
-                    + (app.nextPssTime-now) + ": " + app);
-        } else {
-            if (now > app.nextPssTime || (now > (app.lastPssTime+ProcessList.PSS_MAX_INTERVAL)
-                    && now > (app.lastStateTime+ProcessList.minTimeFromStateChange(
-                    mTestPssMode)))) {
-                if (requestPssLocked(app, app.setProcState)) {
-                    app.nextPssTime = ProcessList.computeNextPssTime(app.getCurProcState(),
-                            app.procStateMemTracker, mTestPssMode, mAtmInternal.isSleeping(), now);
-                }
-            } else if (false && DEBUG_PSS) Slog.d(TAG_PSS,
-                    "Not requesting pss of " + app + ": next=" + (app.nextPssTime-now));
-        }
-        if (app.setProcState != app.getCurProcState()) {
-            if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mCurOomAdjUid == app.uid) {
-                String msg = "Proc state change of " + app.processName
-                        + " to " + ProcessList.makeProcStateString(app.getCurProcState())
-                        + " (" + app.getCurProcState() + ")" + ": " + app.adjType;
-                reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
-            }
-            boolean setImportant = app.setProcState < ActivityManager.PROCESS_STATE_SERVICE;
-            boolean curImportant = app.getCurProcState() < ActivityManager.PROCESS_STATE_SERVICE;
-            if (setImportant && !curImportant) {
-                // This app is no longer something we consider important enough to allow to use
-                // arbitrary amounts of battery power. Note its current CPU time to later know to
-                // kill it if it is not behaving well.
-                app.setWhenUnimportant(now);
-                app.lastCpuTime = 0;
-            }
-            // Inform UsageStats of important process state change
-            // Must be called before updating setProcState
-            maybeUpdateUsageStatsLocked(app, nowElapsed);
-
-            maybeUpdateLastTopTime(app, now);
-
-            app.setProcState = app.getCurProcState();
-            if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
-                app.notCachedSinceIdle = false;
-            }
-            if (!doingAll) {
-                setProcessTrackerStateLocked(app, mProcessStats.getMemFactorLocked(), now);
-            } else {
-                app.procStateChanged = true;
-            }
-        } else if (app.reportedInteraction && (nowElapsed - app.getInteractionEventTime())
-                > mConstants.USAGE_STATS_INTERACTION_INTERVAL) {
-            // For apps that sit around for a long time in the interactive state, we need
-            // to report this at least once a day so they don't go idle.
-            maybeUpdateUsageStatsLocked(app, nowElapsed);
-        }
-
-        if (changes != 0) {
-            if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
-                    "Changes in " + app + ": " + changes);
-            int i = mPendingProcessChanges.size()-1;
-            ProcessChangeItem item = null;
-            while (i >= 0) {
-                item = mPendingProcessChanges.get(i);
-                if (item.pid == app.pid) {
-                    if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
-                            "Re-using existing item: " + item);
-                    break;
-                }
-                i--;
-            }
-            if (i < 0) {
-                // No existing item in pending changes; need a new one.
-                final int NA = mAvailProcessChanges.size();
-                if (NA > 0) {
-                    item = mAvailProcessChanges.remove(NA-1);
-                    if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
-                            "Retrieving available item: " + item);
-                } else {
-                    item = new ProcessChangeItem();
-                    if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
-                            "Allocating new item: " + item);
-                }
-                item.changes = 0;
-                item.pid = app.pid;
-                item.uid = app.info.uid;
-                if (mPendingProcessChanges.size() == 0) {
-                    if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
-                            "*** Enqueueing dispatch processes changed!");
-                    mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG).sendToTarget();
-                }
-                mPendingProcessChanges.add(item);
-            }
-            item.changes |= changes;
-            item.foregroundActivities = app.repForegroundActivities;
-            if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
-                    "Item " + Integer.toHexString(System.identityHashCode(item))
-                    + " " + app.toShortString() + ": changes=" + item.changes
-                    + " foreground=" + item.foregroundActivities
-                    + " type=" + app.adjType + " source=" + app.adjSource
-                    + " target=" + app.adjTarget);
-        }
-
-        return success;
-    }
-
     private boolean isEphemeralLocked(int uid) {
         String packages[] = mContext.getPackageManager().getPackagesForUid(uid);
         if (packages == null || packages.length != 1) { // Ephemeral apps cannot share uid
@@ -17410,78 +15999,13 @@
         }
     }
 
-    private void maybeUpdateUsageStatsLocked(ProcessRecord app, long nowElapsed) {
-        if (DEBUG_USAGE_STATS) {
-            Slog.d(TAG, "Checking proc [" + Arrays.toString(app.getPackageList())
-                    + "] state changes: old = " + app.setProcState + ", new = "
-                    + app.getCurProcState());
-        }
-        if (mUsageStatsService == null) {
-            return;
-        }
-        boolean isInteraction;
-        // To avoid some abuse patterns, we are going to be careful about what we consider
-        // to be an app interaction.  Being the top activity doesn't count while the display
-        // is sleeping, nor do short foreground services.
-        if (app.getCurProcState() <= ActivityManager.PROCESS_STATE_TOP) {
-            isInteraction = true;
-            app.setFgInteractionTime(0);
-        } else if (app.getCurProcState() <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
-            if (app.getFgInteractionTime() == 0) {
-                app.setFgInteractionTime(nowElapsed);
-                isInteraction = false;
-            } else {
-                isInteraction = nowElapsed > app.getFgInteractionTime()
-                        + mConstants.SERVICE_USAGE_INTERACTION_TIME;
-            }
-        } else {
-            isInteraction =
-                    app.getCurProcState() <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
-            app.setFgInteractionTime(0);
-        }
-        if (isInteraction
-                && (!app.reportedInteraction || (nowElapsed - app.getInteractionEventTime())
-                > mConstants.USAGE_STATS_INTERACTION_INTERVAL)) {
-            app.setInteractionEventTime(nowElapsed);
-            String[] packages = app.getPackageList();
-            if (packages != null) {
-                for (int i = 0; i < packages.length; i++) {
-                    mUsageStatsService.reportEvent(packages[i], app.userId,
-                            UsageEvents.Event.SYSTEM_INTERACTION);
-                }
-            }
-        }
-        app.reportedInteraction = isInteraction;
-        if (!isInteraction) {
-            app.setInteractionEventTime(0);
-        }
-    }
-
-    private void maybeUpdateLastTopTime(ProcessRecord app, long nowUptime) {
-        if (app.setProcState <= ActivityManager.PROCESS_STATE_TOP
-                && app.getCurProcState() > ActivityManager.PROCESS_STATE_TOP) {
-            app.lastTopTime = nowUptime;
-        }
-    }
-
-    private final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) {
+    final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) {
         if (proc.thread != null && proc.baseProcessTracker != null) {
             proc.baseProcessTracker.setState(
                     proc.getReportedProcState(), memFactor, now, proc.pkgList.mPkgList);
         }
     }
 
-    private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj,
-            ProcessRecord TOP_APP, boolean doingAll, long now) {
-        if (app.thread == null) {
-            return false;
-        }
-
-        computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now, false);
-
-        return applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime());
-    }
-
     @GuardedBy("this")
     final void updateProcessForegroundLocked(ProcessRecord proc, boolean isForeground,
             boolean oomAdj) {
@@ -17564,29 +16088,10 @@
      */
     @GuardedBy("this")
     final boolean updateOomAdjLocked(ProcessRecord app, boolean oomAdjAll) {
-        final ProcessRecord TOP_APP = getTopAppLocked();
-        final boolean wasCached = app.cached;
-
-        mAdjSeq++;
-
-        // This is the desired cached adjusment we want to tell it to use.
-        // If our app is currently cached, we know it, and that is it.  Otherwise,
-        // we don't know it yet, and it needs to now be cached we will then
-        // need to do a complete oom adj.
-        final int cachedAdj = app.getCurRawAdj() >= ProcessList.CACHED_APP_MIN_ADJ
-                ? app.getCurRawAdj() : ProcessList.UNKNOWN_ADJ;
-        boolean success = updateOomAdjLocked(app, cachedAdj, TOP_APP, false,
-                SystemClock.uptimeMillis());
-        if (oomAdjAll
-                && (wasCached != app.cached || app.getCurRawAdj() == ProcessList.UNKNOWN_ADJ)) {
-            // Changed to/from cached state, so apps after it in the LRU
-            // list may also be changed.
-            updateOomAdjLocked();
-        }
-        return success;
+        return mOomAdjuster.updateOomAdjLocked(app, oomAdjAll);
     }
 
-    private static final class ProcStatsRunnable implements Runnable {
+    static final class ProcStatsRunnable implements Runnable {
         private final ActivityManagerService mService;
         private final ProcessStatsService mProcessStats;
 
@@ -17777,387 +16282,7 @@
 
     @GuardedBy("this")
     final void updateOomAdjLocked() {
-        mOomAdjProfiler.oomAdjStarted();
-        final ProcessRecord TOP_APP = getTopAppLocked();
-        final long now = SystemClock.uptimeMillis();
-        final long nowElapsed = SystemClock.elapsedRealtime();
-        final long oldTime = now - ProcessList.MAX_EMPTY_TIME;
-        final int N = mProcessList.getLruSizeLocked();
-
-        // Reset state in all uid records.
-        for (int i=mActiveUids.size()-1; i>=0; i--) {
-            final UidRecord uidRec = mActiveUids.valueAt(i);
-            if (false && DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                    "Starting update of " + uidRec);
-            uidRec.reset();
-        }
-
-        if (mAtmInternal != null) {
-            mAtmInternal.rankTaskLayersIfNeeded();
-        }
-
-        mAdjSeq++;
-        mNewNumServiceProcs = 0;
-        mNewNumAServiceProcs = 0;
-
-        final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;
-        final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES - emptyProcessLimit;
-
-        // Let's determine how many processes we have running vs.
-        // how many slots we have for background processes; we may want
-        // to put multiple processes in a slot of there are enough of
-        // them.
-        final int numSlots = (ProcessList.CACHED_APP_MAX_ADJ
-                - ProcessList.CACHED_APP_MIN_ADJ + 1) / 2
-                / ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
-        int numEmptyProcs = N - mNumNonCachedProcs - mNumCachedHiddenProcs;
-        if (numEmptyProcs > cachedProcessLimit) {
-            // If there are more empty processes than our limit on cached
-            // processes, then use the cached process limit for the factor.
-            // This ensures that the really old empty processes get pushed
-            // down to the bottom, so if we are running low on memory we will
-            // have a better chance at keeping around more cached processes
-            // instead of a gazillion empty processes.
-            numEmptyProcs = cachedProcessLimit;
-        }
-        int emptyFactor = (numEmptyProcs + numSlots - 1) / numSlots;
-        if (emptyFactor < 1) emptyFactor = 1;
-        int cachedFactor = (mNumCachedHiddenProcs > 0 ? (mNumCachedHiddenProcs + numSlots - 1) : 1)
-                / numSlots;
-        if (cachedFactor < 1) cachedFactor = 1;
-        int stepCached = -1;
-        int stepEmpty = -1;
-        int numCached = 0;
-        int numCachedExtraGroup = 0;
-        int numEmpty = 0;
-        int numTrimming = 0;
-        int lastCachedGroup = 0;
-        int lastCachedGroupImportance = 0;
-        int lastCachedGroupUid = 0;
-
-        mNumNonCachedProcs = 0;
-        mNumCachedHiddenProcs = 0;
-
-        // First update the OOM adjustment for each of the
-        // application processes based on their current state.
-        int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ;
-        int nextCachedAdj = curCachedAdj + (ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2);
-        int curCachedImpAdj = 0;
-        int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
-        int nextEmptyAdj = curEmptyAdj + (ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2);
-
-        boolean retryCycles = false;
-
-        // need to reset cycle state before calling computeOomAdjLocked because of service connections
-        for (int i=N-1; i>=0; i--) {
-            ProcessRecord app = mProcessList.mLruProcesses.get(i);
-            app.containsCycle = false;
-            app.setCurRawProcState(PROCESS_STATE_CACHED_EMPTY);
-            app.setCurRawAdj(ProcessList.UNKNOWN_ADJ);
-        }
-        for (int i=N-1; i>=0; i--) {
-            ProcessRecord app = mProcessList.mLruProcesses.get(i);
-            if (!app.killedByAm && app.thread != null) {
-                app.procStateChanged = false;
-                computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now, false);
-
-                // if any app encountered a cycle, we need to perform an additional loop later
-                retryCycles |= app.containsCycle;
-
-                // If we haven't yet assigned the final cached adj
-                // to the process, do that now.
-                if (app.curAdj >= ProcessList.UNKNOWN_ADJ) {
-                    switch (app.getCurProcState()) {
-                        case PROCESS_STATE_CACHED_ACTIVITY:
-                        case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
-                        case ActivityManager.PROCESS_STATE_CACHED_RECENT:
-                            // Figure out the next cached level, taking into account groups.
-                            boolean inGroup = false;
-                            if (app.connectionGroup != 0) {
-                                if (lastCachedGroupUid == app.uid
-                                        && lastCachedGroup == app.connectionGroup) {
-                                    // This is in the same group as the last process, just tweak
-                                    // adjustment by importance.
-                                    if (app.connectionImportance > lastCachedGroupImportance) {
-                                        lastCachedGroupImportance = app.connectionImportance;
-                                        if (curCachedAdj < nextCachedAdj
-                                                && curCachedAdj < ProcessList.CACHED_APP_MAX_ADJ) {
-                                            curCachedImpAdj++;
-                                        }
-                                    }
-                                    inGroup = true;
-                                } else {
-                                    lastCachedGroupUid = app.uid;
-                                    lastCachedGroup = app.connectionGroup;
-                                    lastCachedGroupImportance = app.connectionImportance;
-                                }
-                            }
-                            if (!inGroup && curCachedAdj != nextCachedAdj) {
-                                stepCached++;
-                                curCachedImpAdj = 0;
-                                if (stepCached >= cachedFactor) {
-                                    stepCached = 0;
-                                    curCachedAdj = nextCachedAdj;
-                                    nextCachedAdj += ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
-                                    if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) {
-                                        nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ;
-                                    }
-                                }
-                            }
-                            // This process is a cached process holding activities...
-                            // assign it the next cached value for that type, and then
-                            // step that cached level.
-                            app.setCurRawAdj(curCachedAdj + curCachedImpAdj);
-                            app.curAdj = app.modifyRawOomAdj(curCachedAdj + curCachedImpAdj);
-                            if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning activity LRU #" + i
-                                    + " adj: " + app.curAdj + " (curCachedAdj=" + curCachedAdj
-                                    + " curCachedImpAdj=" + curCachedImpAdj + ")");
-                            break;
-                        default:
-                            // Figure out the next cached level.
-                            if (curEmptyAdj != nextEmptyAdj) {
-                                stepEmpty++;
-                                if (stepEmpty >= emptyFactor) {
-                                    stepEmpty = 0;
-                                    curEmptyAdj = nextEmptyAdj;
-                                    nextEmptyAdj += ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
-                                    if (nextEmptyAdj > ProcessList.CACHED_APP_MAX_ADJ) {
-                                        nextEmptyAdj = ProcessList.CACHED_APP_MAX_ADJ;
-                                    }
-                                }
-                            }
-                            // For everything else, assign next empty cached process
-                            // level and bump that up.  Note that this means that
-                            // long-running services that have dropped down to the
-                            // cached level will be treated as empty (since their process
-                            // state is still as a service), which is what we want.
-                            app.setCurRawAdj(curEmptyAdj);
-                            app.curAdj = app.modifyRawOomAdj(curEmptyAdj);
-                            if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning empty LRU #" + i
-                                    + " adj: " + app.curAdj + " (curEmptyAdj=" + curEmptyAdj
-                                    + ")");
-                            break;
-                    }
-                }
-            }
-        }
-
-        // Cycle strategy:
-        // - Retry computing any process that has encountered a cycle.
-        // - Continue retrying until no process was promoted.
-        // - Iterate from least important to most important.
-        int cycleCount = 0;
-        while (retryCycles && cycleCount < 10) {
-            cycleCount++;
-            retryCycles = false;
-
-            for (int i=0; i<N; i++) {
-                ProcessRecord app = mProcessList.mLruProcesses.get(i);
-                if (!app.killedByAm && app.thread != null && app.containsCycle == true) {
-                    app.adjSeq--;
-                    app.completedAdjSeq--;
-                }
-            }
-
-            for (int i=0; i<N; i++) {
-                ProcessRecord app = mProcessList.mLruProcesses.get(i);
-                if (!app.killedByAm && app.thread != null && app.containsCycle == true) {
-                    if (computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now,
-                            true)) {
-                        retryCycles = true;
-                    }
-                }
-            }
-        }
-
-        lastCachedGroup = lastCachedGroupUid = 0;
-
-        for (int i=N-1; i>=0; i--) {
-            ProcessRecord app = mProcessList.mLruProcesses.get(i);
-            if (!app.killedByAm && app.thread != null) {
-                applyOomAdjLocked(app, true, now, nowElapsed);
-
-                // Count the number of process types.
-                switch (app.getCurProcState()) {
-                    case PROCESS_STATE_CACHED_ACTIVITY:
-                    case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
-                        mNumCachedHiddenProcs++;
-                        numCached++;
-                        if (app.connectionGroup != 0) {
-                            if (lastCachedGroupUid == app.uid
-                                    && lastCachedGroup == app.connectionGroup) {
-                                // If this process is the next in the same group, we don't
-                                // want it to count against our limit of the number of cached
-                                // processes, so bump up the group count to account for it.
-                                numCachedExtraGroup++;
-                            } else {
-                                lastCachedGroupUid = app.uid;
-                                lastCachedGroup = app.connectionGroup;
-                            }
-                        } else {
-                            lastCachedGroupUid = lastCachedGroup = 0;
-                        }
-                        if ((numCached - numCachedExtraGroup) > cachedProcessLimit) {
-                            app.kill("cached #" + numCached, true);
-                        }
-                        break;
-                    case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
-                        if (numEmpty > mConstants.CUR_TRIM_EMPTY_PROCESSES
-                                && app.lastActivityTime < oldTime) {
-                            app.kill("empty for "
-                                    + ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime)
-                                    / 1000) + "s", true);
-                        } else {
-                            numEmpty++;
-                            if (numEmpty > emptyProcessLimit) {
-                                app.kill("empty #" + numEmpty, true);
-                            }
-                        }
-                        break;
-                    default:
-                        mNumNonCachedProcs++;
-                        break;
-                }
-
-                if (app.isolated && app.services.size() <= 0 && app.isolatedEntryPoint == null) {
-                    // If this is an isolated process, there are no services
-                    // running in it, and it's not a special process with a
-                    // custom entry point, then the process is no longer
-                    // needed.  We agressively kill these because we can by
-                    // definition not re-use the same process again, and it is
-                    // good to avoid having whatever code was running in them
-                    // left sitting around after no longer needed.
-                    app.kill("isolated not needed", true);
-                } else {
-                    // Keeping this process, update its uid.
-                    final UidRecord uidRec = app.uidRecord;
-                    if (uidRec != null) {
-                        uidRec.ephemeral = app.info.isInstantApp();
-                        if (uidRec.getCurProcState() > app.getCurProcState()) {
-                            uidRec.setCurProcState(app.getCurProcState());
-                        }
-                        if (app.hasForegroundServices()) {
-                            uidRec.foregroundServices = true;
-                        }
-                    }
-                }
-
-                if (app.getCurProcState() >= ActivityManager.PROCESS_STATE_HOME
-                        && !app.killedByAm) {
-                    numTrimming++;
-                }
-            }
-        }
-
-        incrementProcStateSeqAndNotifyAppsLocked();
-
-        mNumServiceProcs = mNewNumServiceProcs;
-
-        boolean allChanged = updateLowMemStateLocked(numCached, numEmpty, numTrimming);
-
-        if (mAlwaysFinishActivities) {
-            // Need to do this on its own message because the stack may not
-            // be in a consistent state at this point.
-            mAtmInternal.scheduleDestroyAllActivities("always-finish");
-        }
-
-        if (allChanged) {
-            requestPssAllProcsLocked(now, false, mProcessStats.isMemFactorLowered());
-        }
-
-        ArrayList<UidRecord> becameIdle = null;
-
-        // Update from any uid changes.
-        if (mLocalPowerManager != null) {
-            mLocalPowerManager.startUidChanges();
-        }
-        for (int i=mActiveUids.size()-1; i>=0; i--) {
-            final UidRecord uidRec = mActiveUids.valueAt(i);
-            int uidChange = UidRecord.CHANGE_PROCSTATE;
-            if (uidRec.getCurProcState() != PROCESS_STATE_NONEXISTENT
-                    && (uidRec.setProcState != uidRec.getCurProcState()
-                           || uidRec.setWhitelist != uidRec.curWhitelist)) {
-                if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, "Changes in " + uidRec
-                        + ": proc state from " + uidRec.setProcState + " to "
-                        + uidRec.getCurProcState() + ", whitelist from " + uidRec.setWhitelist
-                        + " to " + uidRec.curWhitelist);
-                if (ActivityManager.isProcStateBackground(uidRec.getCurProcState())
-                        && !uidRec.curWhitelist) {
-                    // UID is now in the background (and not on the temp whitelist).  Was it
-                    // previously in the foreground (or on the temp whitelist)?
-                    if (!ActivityManager.isProcStateBackground(uidRec.setProcState)
-                            || uidRec.setWhitelist) {
-                        uidRec.lastBackgroundTime = nowElapsed;
-                        if (!mHandler.hasMessages(IDLE_UIDS_MSG)) {
-                            // Note: the background settle time is in elapsed realtime, while
-                            // the handler time base is uptime.  All this means is that we may
-                            // stop background uids later than we had intended, but that only
-                            // happens because the device was sleeping so we are okay anyway.
-                            mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
-                                    mConstants.BACKGROUND_SETTLE_TIME);
-                        }
-                    }
-                    if (uidRec.idle && !uidRec.setIdle) {
-                        uidChange = UidRecord.CHANGE_IDLE;
-                        if (becameIdle == null) {
-                            becameIdle = new ArrayList<>();
-                        }
-                        becameIdle.add(uidRec);
-                    }
-                } else {
-                    if (uidRec.idle) {
-                        uidChange = UidRecord.CHANGE_ACTIVE;
-                        EventLogTags.writeAmUidActive(uidRec.uid);
-                        uidRec.idle = false;
-                    }
-                    uidRec.lastBackgroundTime = 0;
-                }
-                final boolean wasCached = uidRec.setProcState
-                        > ActivityManager.PROCESS_STATE_RECEIVER;
-                final boolean isCached = uidRec.getCurProcState()
-                        > ActivityManager.PROCESS_STATE_RECEIVER;
-                if (wasCached != isCached || uidRec.setProcState == PROCESS_STATE_NONEXISTENT) {
-                    uidChange |= isCached ? UidRecord.CHANGE_CACHED : UidRecord.CHANGE_UNCACHED;
-                }
-                uidRec.setProcState = uidRec.getCurProcState();
-                uidRec.setWhitelist = uidRec.curWhitelist;
-                uidRec.setIdle = uidRec.idle;
-                enqueueUidChangeLocked(uidRec, -1, uidChange);
-                noteUidProcessState(uidRec.uid, uidRec.getCurProcState());
-                if (uidRec.foregroundServices) {
-                    mServices.foregroundServiceProcStateChangedLocked(uidRec);
-                }
-            }
-        }
-        if (mLocalPowerManager != null) {
-            mLocalPowerManager.finishUidChanges();
-        }
-
-        if (becameIdle != null) {
-            // If we have any new uids that became idle this time, we need to make sure
-            // they aren't left with running services.
-            for (int i = becameIdle.size() - 1; i >= 0; i--) {
-                mServices.stopInBackgroundLocked(becameIdle.get(i).uid);
-            }
-        }
-
-        if (mProcessStats.shouldWriteNowLocked(now)) {
-            mHandler.post(new ProcStatsRunnable(ActivityManagerService.this, mProcessStats));
-        }
-
-        // Run this after making sure all procstates are updated.
-        mProcessStats.updateTrackingAssociationsLocked(mAdjSeq, now);
-
-        if (DEBUG_OOM_ADJ) {
-            final long duration = SystemClock.uptimeMillis() - now;
-            if (false) {
-                Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms",
-                        new RuntimeException("here").fillInStackTrace());
-            } else {
-                Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms");
-            }
-        }
-        mOomAdjProfiler.oomAdjEnded();
+        mOomAdjuster.updateOomAdjLocked();
     }
 
     @Override
@@ -18192,9 +16317,9 @@
                     mLocalPowerManager.startUidChanges();
                 }
                 final int appId = UserHandle.getAppId(pkgUid);
-                final int N = mActiveUids.size();
-                for (int i=N-1; i>=0; i--) {
-                    final UidRecord uidRec = mActiveUids.valueAt(i);
+                final int N = mProcessList.mActiveUids.size();
+                for (int i = N - 1; i >= 0; i--) {
+                    final UidRecord uidRec = mProcessList.mActiveUids.valueAt(i);
                     final long bgTime = uidRec.lastBackgroundTime;
                     if (bgTime > 0 && !uidRec.idle) {
                         if (UserHandle.getAppId(uidRec.uid) == appId) {
@@ -18219,49 +16344,17 @@
         }
     }
 
+    /** Make the currently active UIDs idle after a certain grace period. */
     final void idleUids() {
         synchronized (this) {
-            final int N = mActiveUids.size();
-            if (N <= 0) {
-                return;
-            }
-            final long nowElapsed = SystemClock.elapsedRealtime();
-            final long maxBgTime = nowElapsed - mConstants.BACKGROUND_SETTLE_TIME;
-            long nextTime = 0;
-            if (mLocalPowerManager != null) {
-                mLocalPowerManager.startUidChanges();
-            }
-            for (int i=N-1; i>=0; i--) {
-                final UidRecord uidRec = mActiveUids.valueAt(i);
-                final long bgTime = uidRec.lastBackgroundTime;
-                if (bgTime > 0 && !uidRec.idle) {
-                    if (bgTime <= maxBgTime) {
-                        EventLogTags.writeAmUidIdle(uidRec.uid);
-                        uidRec.idle = true;
-                        uidRec.setIdle = true;
-                        doStopUidLocked(uidRec.uid, uidRec);
-                    } else {
-                        if (nextTime == 0 || nextTime > bgTime) {
-                            nextTime = bgTime;
-                        }
-                    }
-                }
-            }
-            if (mLocalPowerManager != null) {
-                mLocalPowerManager.finishUidChanges();
-            }
-            if (nextTime > 0) {
-                mHandler.removeMessages(IDLE_UIDS_MSG);
-                mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
-                        nextTime + mConstants.BACKGROUND_SETTLE_TIME - nowElapsed);
-            }
+            mOomAdjuster.idleUidsLocked();
         }
     }
 
     /**
      * Checks if any uid is coming from background to foreground or vice versa and if so, increments
      * the {@link UidRecord#curProcStateSeq} corresponding to that uid using global seq counter
-     * {@link #mProcStateSeqCounter} and notifies the app if it needs to block.
+     * {@link ProcessList#mProcStateSeqCounter} and notifies the app if it needs to block.
      */
     @VisibleForTesting
     @GuardedBy("this")
@@ -18271,8 +16364,8 @@
         }
         // Used for identifying which uids need to block for network.
         ArrayList<Integer> blockingUids = null;
-        for (int i = mActiveUids.size() - 1; i >= 0; --i) {
-            final UidRecord uidRec = mActiveUids.valueAt(i);
+        for (int i = mProcessList.mActiveUids.size() - 1; i >= 0; --i) {
+            final UidRecord uidRec = mProcessList.mActiveUids.valueAt(i);
             // If the network is not restricted for uid, then nothing to do here.
             if (!mInjector.isNetworkRestrictedForUid(uidRec.uid)) {
                 continue;
@@ -18320,13 +16413,15 @@
                 continue;
             }
             if (!app.killedByAm && app.thread != null) {
-                final UidRecord uidRec = mActiveUids.get(app.uid);
+                final UidRecord uidRec = mProcessList.getUidRecordLocked(app.uid);
                 try {
                     if (DEBUG_NETWORK) {
                         Slog.d(TAG_NETWORK, "Informing app thread that it needs to block: "
                                 + uidRec);
                     }
-                    app.thread.setNetworkBlockSeq(uidRec.curProcStateSeq);
+                    if (uidRec != null) {
+                        app.thread.setNetworkBlockSeq(uidRec.curProcStateSeq);
+                    }
                 } catch (RemoteException ignored) {
                 }
             }
@@ -18367,7 +16462,7 @@
 
     final void runInBackgroundDisabled(int uid) {
         synchronized (this) {
-            UidRecord uidRec = mActiveUids.get(uid);
+            UidRecord uidRec = mProcessList.getUidRecordLocked(uid);
             if (uidRec != null) {
                 // This uid is actually running...  should it be considered background now?
                 if (uidRec.idle) {
@@ -18380,24 +16475,7 @@
         }
     }
 
-    /**
-     * Call {@link #doStopUidLocked} (which will also stop background services) for all idle UIDs.
-     */
-    void doStopUidForIdleUidsLocked() {
-        final int size = mActiveUids.size();
-        for (int i = 0; i < size; i++) {
-            final int uid = mActiveUids.keyAt(i);
-            if (UserHandle.isCore(uid)) {
-                continue;
-            }
-            final UidRecord uidRec = mActiveUids.valueAt(i);
-            if (!uidRec.idle) {
-                continue;
-            }
-            doStopUidLocked(uidRec.uid, uidRec);
-        }
-    }
-
+    @GuardedBy("this")
     final void doStopUidLocked(int uid, final UidRecord uidRec) {
         mServices.stopInBackgroundLocked(uid);
         enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
@@ -18481,27 +16559,12 @@
 
     @GuardedBy("this")
     final void setAppIdTempWhitelistStateLocked(int appId, boolean onWhitelist) {
-        boolean changed = false;
-        for (int i=mActiveUids.size()-1; i>=0; i--) {
-            final UidRecord uidRec = mActiveUids.valueAt(i);
-            if (UserHandle.getAppId(uidRec.uid) == appId && uidRec.curWhitelist != onWhitelist) {
-                uidRec.curWhitelist = onWhitelist;
-                changed = true;
-            }
-        }
-        if (changed) {
-            updateOomAdjLocked();
-        }
+        mOomAdjuster.setAppIdTempWhitelistStateLocked(appId, onWhitelist);
     }
 
     @GuardedBy("this")
     final void setUidTempWhitelistStateLocked(int uid, boolean onWhitelist) {
-        boolean changed = false;
-        final UidRecord uidRec = mActiveUids.get(uid);
-        if (uidRec != null && uidRec.curWhitelist != onWhitelist) {
-            uidRec.curWhitelist = onWhitelist;
-            updateOomAdjLocked();
-        }
+        mOomAdjuster.setUidTempWhitelistStateLocked(uid, onWhitelist);
     }
 
     final void trimApplications() {
@@ -19164,7 +17227,7 @@
             }
             UidRecord record;
             synchronized (ActivityManagerService.this) {
-                record = mActiveUids.get(uid);
+                record = mProcessList.getUidRecordLocked(uid);
                 if (record == null) {
                     if (DEBUG_NETWORK) {
                         Slog.d(TAG_NETWORK, "No active uidRecord for uid: " + uid
@@ -19783,7 +17846,7 @@
         }
         UidRecord record;
         synchronized (this) {
-            record = mActiveUids.get(callingUid);
+            record = mProcessList.getUidRecordLocked(callingUid);
             if (record == null) {
                 return;
             }
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
new file mode 100644
index 0000000..cb4cac9
--- /dev/null
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -0,0 +1,2100 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.os.Process.SCHED_OTHER;
+import static android.os.Process.THREAD_GROUP_BG_NONINTERACTIVE;
+import static android.os.Process.THREAD_GROUP_DEFAULT;
+import static android.os.Process.THREAD_GROUP_RESTRICTED;
+import static android.os.Process.THREAD_GROUP_TOP_APP;
+import static android.os.Process.setProcessGroup;
+import static android.os.Process.setThreadPriority;
+import static android.os.Process.setThreadScheduler;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ_REASON;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESS_OBSERVERS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_USAGE_STATS;
+import static com.android.server.am.ActivityManagerService.DISPATCH_OOM_ADJ_OBSERVER_MSG;
+import static com.android.server.am.ActivityManagerService.DISPATCH_PROCESSES_CHANGED_UI_MSG;
+import static com.android.server.am.ActivityManagerService.IDLE_UIDS_MSG;
+import static com.android.server.am.ActivityManagerService.TAG_BACKUP;
+import static com.android.server.am.ActivityManagerService.TAG_LRU;
+import static com.android.server.am.ActivityManagerService.TAG_OOM_ADJ;
+import static com.android.server.am.ActivityManagerService.TAG_PROCESS_OBSERVERS;
+import static com.android.server.am.ActivityManagerService.TAG_PSS;
+import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS;
+import static com.android.server.am.ActivityManagerService.TOP_APP_PRIORITY_BOOST;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
+
+import android.app.ActivityManager;
+import android.app.usage.UsageEvents;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Debug;
+import android.os.PowerManagerInternal;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.procstats.ProcessStats;
+import com.android.server.LocalServices;
+import com.android.server.wm.ActivityServiceConnectionsHolder;
+import com.android.server.wm.WindowProcessController;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * All of the code required to compute proc states and oom_adj values.
+ */
+public final class OomAdjuster {
+    private static final String TAG = "OomAdjuster";
+
+    /**
+     * For some direct access we need to power manager.
+     */
+    PowerManagerInternal mLocalPowerManager;
+
+    /**
+     * Service for compacting background apps.
+     */
+    AppCompactor mAppCompact;
+
+    ActivityManagerConstants mConstants;
+
+    final long[] mTmpLong = new long[3];
+
+    /**
+     * Current sequence id for oom_adj computation traversal.
+     */
+    int mAdjSeq = 0;
+
+    /**
+     * Keep track of the number of service processes we last found, to
+     * determine on the next iteration which should be B services.
+     */
+    int mNumServiceProcs = 0;
+    int mNewNumAServiceProcs = 0;
+    int mNewNumServiceProcs = 0;
+
+    /**
+     * Keep track of the non-cached/empty process we last found, to help
+     * determine how to distribute cached/empty processes next time.
+     */
+    int mNumNonCachedProcs = 0;
+
+    /**
+     * Keep track of the number of cached hidden procs, to balance oom adj
+     * distribution between those and empty procs.
+     */
+    int mNumCachedHiddenProcs = 0;
+
+    /** Track all uids that have actively running processes. */
+    ActiveUids mActiveUids;
+
+    private final ArraySet<BroadcastQueue> mTmpBroadcastQueue = new ArraySet();
+
+    private final ActivityManagerService mService;
+    private final ProcessList mProcessList;
+
+    OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids) {
+        mService = service;
+        mProcessList = processList;
+        mActiveUids = activeUids;
+
+        mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class);
+        mAppCompact = new AppCompactor(mService);
+        mConstants = mService.mConstants;
+    }
+
+    /**
+     * Update OomAdj for a specific process.
+     * @param app The process to update
+     * @param oomAdjAll If it's ok to call updateOomAdjLocked() for all running apps
+     *                  if necessary, or skip.
+     * @return whether updateOomAdjLocked(app) was successful.
+     */
+    @GuardedBy("mService")
+    final boolean updateOomAdjLocked(ProcessRecord app, boolean oomAdjAll) {
+        final ProcessRecord TOP_APP = mService.getTopAppLocked();
+        final boolean wasCached = app.cached;
+
+        mAdjSeq++;
+
+        // This is the desired cached adjusment we want to tell it to use.
+        // If our app is currently cached, we know it, and that is it.  Otherwise,
+        // we don't know it yet, and it needs to now be cached we will then
+        // need to do a complete oom adj.
+        final int cachedAdj = app.getCurRawAdj() >= ProcessList.CACHED_APP_MIN_ADJ
+                ? app.getCurRawAdj() : ProcessList.UNKNOWN_ADJ;
+        boolean success = updateOomAdjLocked(app, cachedAdj, TOP_APP, false,
+                SystemClock.uptimeMillis());
+        if (oomAdjAll
+                && (wasCached != app.cached || app.getCurRawAdj() == ProcessList.UNKNOWN_ADJ)) {
+            // Changed to/from cached state, so apps after it in the LRU
+            // list may also be changed.
+            updateOomAdjLocked();
+        }
+        return success;
+    }
+
+    @GuardedBy("mService")
+    private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj,
+            ProcessRecord TOP_APP, boolean doingAll, long now) {
+        if (app.thread == null) {
+            return false;
+        }
+
+        computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now, false);
+
+        return applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime());
+    }
+
+    @GuardedBy("mService")
+    final void updateOomAdjLocked() {
+        mService.mOomAdjProfiler.oomAdjStarted();
+        final ProcessRecord TOP_APP = mService.getTopAppLocked();
+        final long now = SystemClock.uptimeMillis();
+        final long nowElapsed = SystemClock.elapsedRealtime();
+        final long oldTime = now - ProcessList.MAX_EMPTY_TIME;
+        final int N = mProcessList.getLruSizeLocked();
+
+        // Reset state in all uid records.
+        for (int  i = mActiveUids.size() - 1; i >= 0; i--) {
+            final UidRecord uidRec = mActiveUids.valueAt(i);
+            if (false && DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+                    "Starting update of " + uidRec);
+            uidRec.reset();
+        }
+
+        if (mService.mAtmInternal != null) {
+            mService.mAtmInternal.rankTaskLayersIfNeeded();
+        }
+
+        mAdjSeq++;
+        mNewNumServiceProcs = 0;
+        mNewNumAServiceProcs = 0;
+
+        final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;
+        final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES
+                - emptyProcessLimit;
+
+        // Let's determine how many processes we have running vs.
+        // how many slots we have for background processes; we may want
+        // to put multiple processes in a slot of there are enough of
+        // them.
+        final int numSlots = (ProcessList.CACHED_APP_MAX_ADJ
+                - ProcessList.CACHED_APP_MIN_ADJ + 1) / 2
+                / ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
+        int numEmptyProcs = N - mNumNonCachedProcs - mNumCachedHiddenProcs;
+        if (numEmptyProcs > cachedProcessLimit) {
+            // If there are more empty processes than our limit on cached
+            // processes, then use the cached process limit for the factor.
+            // This ensures that the really old empty processes get pushed
+            // down to the bottom, so if we are running low on memory we will
+            // have a better chance at keeping around more cached processes
+            // instead of a gazillion empty processes.
+            numEmptyProcs = cachedProcessLimit;
+        }
+        int emptyFactor = (numEmptyProcs + numSlots - 1) / numSlots;
+        if (emptyFactor < 1) emptyFactor = 1;
+        int cachedFactor = (mNumCachedHiddenProcs > 0 ? (mNumCachedHiddenProcs + numSlots - 1) : 1)
+                / numSlots;
+        if (cachedFactor < 1) cachedFactor = 1;
+        int stepCached = -1;
+        int stepEmpty = -1;
+        int numCached = 0;
+        int numCachedExtraGroup = 0;
+        int numEmpty = 0;
+        int numTrimming = 0;
+        int lastCachedGroup = 0;
+        int lastCachedGroupImportance = 0;
+        int lastCachedGroupUid = 0;
+
+        mNumNonCachedProcs = 0;
+        mNumCachedHiddenProcs = 0;
+
+        // First update the OOM adjustment for each of the
+        // application processes based on their current state.
+        int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ;
+        int nextCachedAdj = curCachedAdj + (ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2);
+        int curCachedImpAdj = 0;
+        int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
+        int nextEmptyAdj = curEmptyAdj + (ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2);
+
+        boolean retryCycles = false;
+
+        // need to reset cycle state before calling computeOomAdjLocked because of service conns
+        for (int i = N - 1; i >= 0; i--) {
+            ProcessRecord app = mProcessList.mLruProcesses.get(i);
+            app.containsCycle = false;
+            app.setCurRawProcState(PROCESS_STATE_CACHED_EMPTY);
+            app.setCurRawAdj(ProcessList.UNKNOWN_ADJ);
+        }
+        for (int i = N - 1; i >= 0; i--) {
+            ProcessRecord app = mProcessList.mLruProcesses.get(i);
+            if (!app.killedByAm && app.thread != null) {
+                app.procStateChanged = false;
+                computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now, false);
+
+                // if any app encountered a cycle, we need to perform an additional loop later
+                retryCycles |= app.containsCycle;
+
+                // If we haven't yet assigned the final cached adj
+                // to the process, do that now.
+                if (app.curAdj >= ProcessList.UNKNOWN_ADJ) {
+                    switch (app.getCurProcState()) {
+                        case PROCESS_STATE_CACHED_ACTIVITY:
+                        case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
+                        case ActivityManager.PROCESS_STATE_CACHED_RECENT:
+                            // Figure out the next cached level, taking into account groups.
+                            boolean inGroup = false;
+                            if (app.connectionGroup != 0) {
+                                if (lastCachedGroupUid == app.uid
+                                        && lastCachedGroup == app.connectionGroup) {
+                                    // This is in the same group as the last process, just tweak
+                                    // adjustment by importance.
+                                    if (app.connectionImportance > lastCachedGroupImportance) {
+                                        lastCachedGroupImportance = app.connectionImportance;
+                                        if (curCachedAdj < nextCachedAdj
+                                                && curCachedAdj < ProcessList.CACHED_APP_MAX_ADJ) {
+                                            curCachedImpAdj++;
+                                        }
+                                    }
+                                    inGroup = true;
+                                } else {
+                                    lastCachedGroupUid = app.uid;
+                                    lastCachedGroup = app.connectionGroup;
+                                    lastCachedGroupImportance = app.connectionImportance;
+                                }
+                            }
+                            if (!inGroup && curCachedAdj != nextCachedAdj) {
+                                stepCached++;
+                                curCachedImpAdj = 0;
+                                if (stepCached >= cachedFactor) {
+                                    stepCached = 0;
+                                    curCachedAdj = nextCachedAdj;
+                                    nextCachedAdj += ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
+                                    if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) {
+                                        nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ;
+                                    }
+                                }
+                            }
+                            // This process is a cached process holding activities...
+                            // assign it the next cached value for that type, and then
+                            // step that cached level.
+                            app.setCurRawAdj(curCachedAdj + curCachedImpAdj);
+                            app.curAdj = app.modifyRawOomAdj(curCachedAdj + curCachedImpAdj);
+                            if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning activity LRU #" + i
+                                    + " adj: " + app.curAdj + " (curCachedAdj=" + curCachedAdj
+                                    + " curCachedImpAdj=" + curCachedImpAdj + ")");
+                            break;
+                        default:
+                            // Figure out the next cached level.
+                            if (curEmptyAdj != nextEmptyAdj) {
+                                stepEmpty++;
+                                if (stepEmpty >= emptyFactor) {
+                                    stepEmpty = 0;
+                                    curEmptyAdj = nextEmptyAdj;
+                                    nextEmptyAdj += ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
+                                    if (nextEmptyAdj > ProcessList.CACHED_APP_MAX_ADJ) {
+                                        nextEmptyAdj = ProcessList.CACHED_APP_MAX_ADJ;
+                                    }
+                                }
+                            }
+                            // For everything else, assign next empty cached process
+                            // level and bump that up.  Note that this means that
+                            // long-running services that have dropped down to the
+                            // cached level will be treated as empty (since their process
+                            // state is still as a service), which is what we want.
+                            app.setCurRawAdj(curEmptyAdj);
+                            app.curAdj = app.modifyRawOomAdj(curEmptyAdj);
+                            if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning empty LRU #" + i
+                                    + " adj: " + app.curAdj + " (curEmptyAdj=" + curEmptyAdj
+                                    + ")");
+                            break;
+                    }
+                }
+            }
+        }
+
+        // Cycle strategy:
+        // - Retry computing any process that has encountered a cycle.
+        // - Continue retrying until no process was promoted.
+        // - Iterate from least important to most important.
+        int cycleCount = 0;
+        while (retryCycles && cycleCount < 10) {
+            cycleCount++;
+            retryCycles = false;
+
+            for (int i = 0; i < N; i++) {
+                ProcessRecord app = mProcessList.mLruProcesses.get(i);
+                if (!app.killedByAm && app.thread != null && app.containsCycle == true) {
+                    app.adjSeq--;
+                    app.completedAdjSeq--;
+                }
+            }
+
+            for (int i = 0; i < N; i++) {
+                ProcessRecord app = mProcessList.mLruProcesses.get(i);
+                if (!app.killedByAm && app.thread != null && app.containsCycle == true) {
+                    if (computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now,
+                            true)) {
+                        retryCycles = true;
+                    }
+                }
+            }
+        }
+
+        lastCachedGroup = lastCachedGroupUid = 0;
+
+        for (int i = N - 1; i >= 0; i--) {
+            ProcessRecord app = mProcessList.mLruProcesses.get(i);
+            if (!app.killedByAm && app.thread != null) {
+                applyOomAdjLocked(app, true, now, nowElapsed);
+
+                // Count the number of process types.
+                switch (app.getCurProcState()) {
+                    case PROCESS_STATE_CACHED_ACTIVITY:
+                    case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
+                        mNumCachedHiddenProcs++;
+                        numCached++;
+                        if (app.connectionGroup != 0) {
+                            if (lastCachedGroupUid == app.uid
+                                    && lastCachedGroup == app.connectionGroup) {
+                                // If this process is the next in the same group, we don't
+                                // want it to count against our limit of the number of cached
+                                // processes, so bump up the group count to account for it.
+                                numCachedExtraGroup++;
+                            } else {
+                                lastCachedGroupUid = app.uid;
+                                lastCachedGroup = app.connectionGroup;
+                            }
+                        } else {
+                            lastCachedGroupUid = lastCachedGroup = 0;
+                        }
+                        if ((numCached - numCachedExtraGroup) > cachedProcessLimit) {
+                            app.kill("cached #" + numCached, true);
+                        }
+                        break;
+                    case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
+                        if (numEmpty > mConstants.CUR_TRIM_EMPTY_PROCESSES
+                                && app.lastActivityTime < oldTime) {
+                            app.kill("empty for "
+                                    + ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime)
+                                    / 1000) + "s", true);
+                        } else {
+                            numEmpty++;
+                            if (numEmpty > emptyProcessLimit) {
+                                app.kill("empty #" + numEmpty, true);
+                            }
+                        }
+                        break;
+                    default:
+                        mNumNonCachedProcs++;
+                        break;
+                }
+
+                if (app.isolated && app.services.size() <= 0 && app.isolatedEntryPoint == null) {
+                    // If this is an isolated process, there are no services
+                    // running in it, and it's not a special process with a
+                    // custom entry point, then the process is no longer
+                    // needed.  We agressively kill these because we can by
+                    // definition not re-use the same process again, and it is
+                    // good to avoid having whatever code was running in them
+                    // left sitting around after no longer needed.
+                    app.kill("isolated not needed", true);
+                } else {
+                    // Keeping this process, update its uid.
+                    final UidRecord uidRec = app.uidRecord;
+                    if (uidRec != null) {
+                        uidRec.ephemeral = app.info.isInstantApp();
+                        if (uidRec.getCurProcState() > app.getCurProcState()) {
+                            uidRec.setCurProcState(app.getCurProcState());
+                        }
+                        if (app.hasForegroundServices()) {
+                            uidRec.foregroundServices = true;
+                        }
+                    }
+                }
+
+                if (app.getCurProcState() >= ActivityManager.PROCESS_STATE_HOME
+                        && !app.killedByAm) {
+                    numTrimming++;
+                }
+            }
+        }
+
+        mService.incrementProcStateSeqAndNotifyAppsLocked();
+
+        mNumServiceProcs = mNewNumServiceProcs;
+
+        boolean allChanged = mService.updateLowMemStateLocked(numCached, numEmpty, numTrimming);
+
+        if (mService.mAlwaysFinishActivities) {
+            // Need to do this on its own message because the stack may not
+            // be in a consistent state at this point.
+            mService.mAtmInternal.scheduleDestroyAllActivities("always-finish");
+        }
+
+        if (allChanged) {
+            mService.requestPssAllProcsLocked(now, false,
+                    mService.mProcessStats.isMemFactorLowered());
+        }
+
+        ArrayList<UidRecord> becameIdle = null;
+
+        // Update from any uid changes.
+        if (mLocalPowerManager != null) {
+            mLocalPowerManager.startUidChanges();
+        }
+        for (int i = mActiveUids.size() - 1; i >= 0; i--) {
+            final UidRecord uidRec = mActiveUids.valueAt(i);
+            int uidChange = UidRecord.CHANGE_PROCSTATE;
+            if (uidRec.getCurProcState() != PROCESS_STATE_NONEXISTENT
+                    && (uidRec.setProcState != uidRec.getCurProcState()
+                    || uidRec.setWhitelist != uidRec.curWhitelist)) {
+                if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, "Changes in " + uidRec
+                        + ": proc state from " + uidRec.setProcState + " to "
+                        + uidRec.getCurProcState() + ", whitelist from " + uidRec.setWhitelist
+                        + " to " + uidRec.curWhitelist);
+                if (ActivityManager.isProcStateBackground(uidRec.getCurProcState())
+                        && !uidRec.curWhitelist) {
+                    // UID is now in the background (and not on the temp whitelist).  Was it
+                    // previously in the foreground (or on the temp whitelist)?
+                    if (!ActivityManager.isProcStateBackground(uidRec.setProcState)
+                            || uidRec.setWhitelist) {
+                        uidRec.lastBackgroundTime = nowElapsed;
+                        if (!mService.mHandler.hasMessages(IDLE_UIDS_MSG)) {
+                            // Note: the background settle time is in elapsed realtime, while
+                            // the handler time base is uptime.  All this means is that we may
+                            // stop background uids later than we had intended, but that only
+                            // happens because the device was sleeping so we are okay anyway.
+                            mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
+                                    mConstants.BACKGROUND_SETTLE_TIME);
+                        }
+                    }
+                    if (uidRec.idle && !uidRec.setIdle) {
+                        uidChange = UidRecord.CHANGE_IDLE;
+                        if (becameIdle == null) {
+                            becameIdle = new ArrayList<>();
+                        }
+                        becameIdle.add(uidRec);
+                    }
+                } else {
+                    if (uidRec.idle) {
+                        uidChange = UidRecord.CHANGE_ACTIVE;
+                        EventLogTags.writeAmUidActive(uidRec.uid);
+                        uidRec.idle = false;
+                    }
+                    uidRec.lastBackgroundTime = 0;
+                }
+                final boolean wasCached = uidRec.setProcState
+                        > ActivityManager.PROCESS_STATE_RECEIVER;
+                final boolean isCached = uidRec.getCurProcState()
+                        > ActivityManager.PROCESS_STATE_RECEIVER;
+                if (wasCached != isCached || uidRec.setProcState == PROCESS_STATE_NONEXISTENT) {
+                    uidChange |= isCached ? UidRecord.CHANGE_CACHED : UidRecord.CHANGE_UNCACHED;
+                }
+                uidRec.setProcState = uidRec.getCurProcState();
+                uidRec.setWhitelist = uidRec.curWhitelist;
+                uidRec.setIdle = uidRec.idle;
+                mService.enqueueUidChangeLocked(uidRec, -1, uidChange);
+                mService.noteUidProcessState(uidRec.uid, uidRec.getCurProcState());
+                if (uidRec.foregroundServices) {
+                    mService.mServices.foregroundServiceProcStateChangedLocked(uidRec);
+                }
+            }
+        }
+        if (mLocalPowerManager != null) {
+            mLocalPowerManager.finishUidChanges();
+        }
+
+        if (becameIdle != null) {
+            // If we have any new uids that became idle this time, we need to make sure
+            // they aren't left with running services.
+            for (int i = becameIdle.size() - 1; i >= 0; i--) {
+                mService.mServices.stopInBackgroundLocked(becameIdle.get(i).uid);
+            }
+        }
+
+        if (mService.mProcessStats.shouldWriteNowLocked(now)) {
+            mService.mHandler.post(new ActivityManagerService.ProcStatsRunnable(mService,
+                    mService.mProcessStats));
+        }
+
+        // Run this after making sure all procstates are updated.
+        mService.mProcessStats.updateTrackingAssociationsLocked(mAdjSeq, now);
+
+        if (DEBUG_OOM_ADJ) {
+            final long duration = SystemClock.uptimeMillis() - now;
+            if (false) {
+                Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms",
+                        new RuntimeException("here").fillInStackTrace());
+            } else {
+                Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms");
+            }
+        }
+        mService.mOomAdjProfiler.oomAdjEnded();
+    }
+
+    private final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback =
+            new ComputeOomAdjWindowCallback();
+
+    /** These methods are called inline during computeOomAdjLocked(), on the same thread */
+    private final class ComputeOomAdjWindowCallback
+            implements WindowProcessController.ComputeOomAdjCallback {
+
+        ProcessRecord app;
+        int adj;
+        boolean foregroundActivities;
+        int procState;
+        int schedGroup;
+        int appUid;
+        int logUid;
+        int processStateCurTop;
+
+        void initialize(ProcessRecord app, int adj, boolean foregroundActivities,
+                int procState, int schedGroup, int appUid, int logUid, int processStateCurTop) {
+            this.app = app;
+            this.adj = adj;
+            this.foregroundActivities = foregroundActivities;
+            this.procState = procState;
+            this.schedGroup = schedGroup;
+            this.appUid = appUid;
+            this.logUid = logUid;
+            this.processStateCurTop = processStateCurTop;
+        }
+
+        @Override
+        public void onVisibleActivity() {
+            // App has a visible activity; only upgrade adjustment.
+            if (adj > ProcessList.VISIBLE_APP_ADJ) {
+                adj = ProcessList.VISIBLE_APP_ADJ;
+                app.adjType = "vis-activity";
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to vis-activity: " + app);
+                }
+            }
+            if (procState > processStateCurTop) {
+                procState = processStateCurTop;
+                app.adjType = "vis-activity";
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ,
+                            "Raise procstate to vis-activity (top): " + app);
+                }
+            }
+            if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) {
+                schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+            }
+            app.cached = false;
+            app.empty = false;
+            foregroundActivities = true;
+        }
+
+        @Override
+        public void onPausedActivity() {
+            if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+                adj = ProcessList.PERCEPTIBLE_APP_ADJ;
+                app.adjType = "pause-activity";
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to pause-activity: "  + app);
+                }
+            }
+            if (procState > processStateCurTop) {
+                procState = processStateCurTop;
+                app.adjType = "pause-activity";
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ,
+                            "Raise procstate to pause-activity (top): "  + app);
+                }
+            }
+            if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) {
+                schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+            }
+            app.cached = false;
+            app.empty = false;
+            foregroundActivities = true;
+        }
+
+        @Override
+        public void onStoppingActivity(boolean finishing) {
+            if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+                adj = ProcessList.PERCEPTIBLE_APP_ADJ;
+                app.adjType = "stop-activity";
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ,
+                            "Raise adj to stop-activity: "  + app);
+                }
+            }
+
+            // For the process state, we will at this point consider the process to be cached. It
+            // will be cached either as an activity or empty depending on whether the activity is
+            // finishing. We do this so that we can treat the process as cached for purposes of
+            // memory trimming (determining current memory level, trim command to send to process)
+            // since there can be an arbitrary number of stopping processes and they should soon all
+            // go into the cached state.
+            if (!finishing) {
+                if (procState > PROCESS_STATE_LAST_ACTIVITY) {
+                    procState = PROCESS_STATE_LAST_ACTIVITY;
+                    app.adjType = "stop-activity";
+                    if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                        reportOomAdjMessageLocked(TAG_OOM_ADJ,
+                                "Raise procstate to stop-activity: " + app);
+                    }
+                }
+            }
+            app.cached = false;
+            app.empty = false;
+            foregroundActivities = true;
+        }
+
+        @Override
+        public void onOtherActivity() {
+            if (procState > PROCESS_STATE_CACHED_ACTIVITY) {
+                procState = PROCESS_STATE_CACHED_ACTIVITY;
+                app.adjType = "cch-act";
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ,
+                            "Raise procstate to cached activity: " + app);
+                }
+            }
+        }
+    }
+
+    private final boolean computeOomAdjLocked(ProcessRecord app, int cachedAdj,
+            ProcessRecord TOP_APP, boolean doingAll, long now, boolean cycleReEval) {
+        if (mAdjSeq == app.adjSeq) {
+            if (app.adjSeq == app.completedAdjSeq) {
+                // This adjustment has already been computed successfully.
+                return false;
+            } else {
+                // The process is being computed, so there is a cycle. We cannot
+                // rely on this process's state.
+                app.containsCycle = true;
+
+                return false;
+            }
+        }
+
+        if (app.thread == null) {
+            app.adjSeq = mAdjSeq;
+            app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_BACKGROUND);
+            app.setCurProcState(ActivityManager.PROCESS_STATE_CACHED_EMPTY);
+            app.curAdj = ProcessList.CACHED_APP_MAX_ADJ;
+            app.setCurRawAdj(ProcessList.CACHED_APP_MAX_ADJ);
+            app.completedAdjSeq = app.adjSeq;
+            return false;
+        }
+
+        app.adjTypeCode = ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN;
+        app.adjSource = null;
+        app.adjTarget = null;
+        app.empty = false;
+        app.cached = false;
+
+        final WindowProcessController wpc = app.getWindowProcessController();
+        final int appUid = app.info.uid;
+        final int logUid = mService.mCurOomAdjUid;
+
+        int prevAppAdj = app.curAdj;
+        int prevProcState = app.getCurProcState();
+
+        if (app.maxAdj <= ProcessList.FOREGROUND_APP_ADJ) {
+            // The max adjustment doesn't allow this app to be anything
+            // below foreground, so it is not worth doing work for it.
+            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                mService.reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making fixed: " + app);
+            }
+            app.adjType = "fixed";
+            app.adjSeq = mAdjSeq;
+            app.setCurRawAdj(app.maxAdj);
+            app.setHasForegroundActivities(false);
+            app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_DEFAULT);
+            app.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT);
+            // System processes can do UI, and when they do we want to have
+            // them trim their memory after the user leaves the UI.  To
+            // facilitate this, here we need to determine whether or not it
+            // is currently showing UI.
+            app.systemNoUi = true;
+            if (app == TOP_APP) {
+                app.systemNoUi = false;
+                app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP);
+                app.adjType = "pers-top-activity";
+            } else if (app.hasTopUi()) {
+                // sched group/proc state adjustment is below
+                app.systemNoUi = false;
+                app.adjType = "pers-top-ui";
+            } else if (wpc.hasVisibleActivities()) {
+                app.systemNoUi = false;
+            }
+            if (!app.systemNoUi) {
+                if (mService.mWakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE) {
+                    // screen on, promote UI
+                    app.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT_UI);
+                    app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP);
+                } else {
+                    // screen off, restrict UI scheduling
+                    app.setCurProcState(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
+                    app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_RESTRICTED);
+                }
+            }
+            app.setCurRawProcState(app.getCurProcState());
+            app.curAdj = app.maxAdj;
+            app.completedAdjSeq = app.adjSeq;
+            // if curAdj is less than prevAppAdj, then this process was promoted
+            return app.curAdj < prevAppAdj || app.getCurProcState() < prevProcState;
+        }
+
+        app.systemNoUi = false;
+
+        final int PROCESS_STATE_CUR_TOP = mService.mAtmInternal.getTopProcessState();
+
+        // Determine the importance of the process, starting with most
+        // important to least, and assign an appropriate OOM adjustment.
+        int adj;
+        int schedGroup;
+        int procState;
+        int cachedAdjSeq;
+
+        boolean foregroundActivities = false;
+        mTmpBroadcastQueue.clear();
+        if (PROCESS_STATE_CUR_TOP == ActivityManager.PROCESS_STATE_TOP && app == TOP_APP) {
+            // The last app on the list is the foreground app.
+            adj = ProcessList.FOREGROUND_APP_ADJ;
+            schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
+            app.adjType = "top-activity";
+            foregroundActivities = true;
+            procState = PROCESS_STATE_CUR_TOP;
+            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making top: " + app);
+            }
+        } else if (app.runningRemoteAnimation) {
+            adj = ProcessList.VISIBLE_APP_ADJ;
+            schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
+            app.adjType = "running-remote-anim";
+            procState = PROCESS_STATE_CUR_TOP;
+            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making running remote anim: " + app);
+            }
+        } else if (app.getActiveInstrumentation() != null) {
+            // Don't want to kill running instrumentation.
+            adj = ProcessList.FOREGROUND_APP_ADJ;
+            schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+            app.adjType = "instrumentation";
+            procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making instrumentation: " + app);
+            }
+        } else if (mService.isReceivingBroadcastLocked(app, mTmpBroadcastQueue)) {
+            // An app that is currently receiving a broadcast also
+            // counts as being in the foreground for OOM killer purposes.
+            // It's placed in a sched group based on the nature of the
+            // broadcast as reflected by which queue it's active in.
+            adj = ProcessList.FOREGROUND_APP_ADJ;
+            schedGroup = (mTmpBroadcastQueue.contains(mService.mFgBroadcastQueue))
+                    ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
+            app.adjType = "broadcast";
+            procState = ActivityManager.PROCESS_STATE_RECEIVER;
+            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making broadcast: " + app);
+            }
+        } else if (app.executingServices.size() > 0) {
+            // An app that is currently executing a service callback also
+            // counts as being in the foreground.
+            adj = ProcessList.FOREGROUND_APP_ADJ;
+            schedGroup = app.execServicesFg ?
+                    ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
+            app.adjType = "exec-service";
+            procState = ActivityManager.PROCESS_STATE_SERVICE;
+            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making exec-service: " + app);
+            }
+            //Slog.i(TAG, "EXEC " + (app.execServicesFg ? "FG" : "BG") + ": " + app);
+        } else if (app == TOP_APP) {
+            adj = ProcessList.FOREGROUND_APP_ADJ;
+            schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+            app.adjType = "top-sleeping";
+            foregroundActivities = true;
+            procState = PROCESS_STATE_CUR_TOP;
+            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making top (sleeping): " + app);
+            }
+        } else {
+            // As far as we know the process is empty.  We may change our mind later.
+            schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+            // At this point we don't actually know the adjustment.  Use the cached adj
+            // value that the caller wants us to.
+            adj = cachedAdj;
+            procState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+            app.cached = true;
+            app.empty = true;
+            app.adjType = "cch-empty";
+            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making empty: " + app);
+            }
+        }
+
+        // Examine all activities if not already foreground.
+        if (!foregroundActivities && wpc.hasActivities()) {
+            mTmpComputeOomAdjWindowCallback.initialize(app, adj, foregroundActivities, procState,
+                    schedGroup, appUid, logUid, PROCESS_STATE_CUR_TOP);
+            final int minLayer = wpc.computeOomAdjFromActivities(
+                    ProcessList.VISIBLE_APP_LAYER_MAX, mTmpComputeOomAdjWindowCallback);
+
+            adj = mTmpComputeOomAdjWindowCallback.adj;
+            foregroundActivities = mTmpComputeOomAdjWindowCallback.foregroundActivities;
+            procState = mTmpComputeOomAdjWindowCallback.procState;
+            schedGroup = mTmpComputeOomAdjWindowCallback.schedGroup;
+
+            if (adj == ProcessList.VISIBLE_APP_ADJ) {
+                adj += minLayer;
+            }
+        }
+
+        if (procState > ActivityManager.PROCESS_STATE_CACHED_RECENT && app.hasRecentTasks()) {
+            procState = ActivityManager.PROCESS_STATE_CACHED_RECENT;
+            app.adjType = "cch-rec";
+            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to cached recent: " + app);
+            }
+        }
+
+        if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
+                || procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+            if (app.hasForegroundServices()) {
+                // The user is aware of this app, so make it visible.
+                adj = ProcessList.PERCEPTIBLE_APP_ADJ;
+                procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+                app.cached = false;
+                app.adjType = "fg-service";
+                schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to fg service: " + app);
+                }
+            } else if (app.hasOverlayUi()) {
+                // The process is display an overlay UI.
+                adj = ProcessList.PERCEPTIBLE_APP_ADJ;
+                procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+                app.cached = false;
+                app.adjType = "has-overlay-ui";
+                schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to overlay ui: " + app);
+                }
+            }
+        }
+
+        // If the app was recently in the foreground and moved to a foreground service status,
+        // allow it to get a higher rank in memory for some time, compared to other foreground
+        // services so that it can finish performing any persistence/processing of in-memory state.
+        if (app.hasForegroundServices() && adj > ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ
+                && (app.lastTopTime + mConstants.TOP_TO_FGS_GRACE_DURATION > now
+                || app.setProcState <= ActivityManager.PROCESS_STATE_TOP)) {
+            adj = ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ;
+            app.adjType = "fg-service-act";
+            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to recent fg: " + app);
+            }
+        }
+
+        if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
+                || procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) {
+            if (app.forcingToImportant != null) {
+                // This is currently used for toasts...  they are not interactive, and
+                // we don't want them to cause the app to become fully foreground (and
+                // thus out of background check), so we yes the best background level we can.
+                adj = ProcessList.PERCEPTIBLE_APP_ADJ;
+                procState = ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+                app.cached = false;
+                app.adjType = "force-imp";
+                app.adjSource = app.forcingToImportant;
+                schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to force imp: " + app);
+                }
+            }
+        }
+
+        if (mService.mAtmInternal.isHeavyWeightProcess(app.getWindowProcessController())) {
+            if (adj > ProcessList.HEAVY_WEIGHT_APP_ADJ) {
+                // We don't want to kill the current heavy-weight process.
+                adj = ProcessList.HEAVY_WEIGHT_APP_ADJ;
+                schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+                app.cached = false;
+                app.adjType = "heavy";
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to heavy: " + app);
+                }
+            }
+            if (procState > ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
+                procState = ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
+                app.adjType = "heavy";
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to heavy: " + app);
+                }
+            }
+        }
+
+        if (wpc.isHomeProcess()) {
+            if (adj > ProcessList.HOME_APP_ADJ) {
+                // This process is hosting what we currently consider to be the
+                // home app, so we don't want to let it go into the background.
+                adj = ProcessList.HOME_APP_ADJ;
+                schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+                app.cached = false;
+                app.adjType = "home";
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to home: " + app);
+                }
+            }
+            if (procState > ActivityManager.PROCESS_STATE_HOME) {
+                procState = ActivityManager.PROCESS_STATE_HOME;
+                app.adjType = "home";
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to home: " + app);
+                }
+            }
+        }
+
+        if (wpc.isPreviousProcess() && app.hasActivities()) {
+            if (adj > ProcessList.PREVIOUS_APP_ADJ) {
+                // This was the previous process that showed UI to the user.
+                // We want to try to keep it around more aggressively, to give
+                // a good experience around switching between two apps.
+                adj = ProcessList.PREVIOUS_APP_ADJ;
+                schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+                app.cached = false;
+                app.adjType = "previous";
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to prev: " + app);
+                }
+            }
+            if (procState > PROCESS_STATE_LAST_ACTIVITY) {
+                procState = PROCESS_STATE_LAST_ACTIVITY;
+                app.adjType = "previous";
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to prev: " + app);
+                }
+            }
+        }
+
+        if (false) Slog.i(TAG, "OOM " + app + ": initial adj=" + adj
+                + " reason=" + app.adjType);
+
+        // By default, we use the computed adjustment.  It may be changed if
+        // there are applications dependent on our services or providers, but
+        // this gives us a baseline and makes sure we don't get into an
+        // infinite recursion. If we're re-evaluating due to cycles, use the previously computed
+        // values.
+        app.setCurRawAdj(!cycleReEval ? adj : Math.min(adj, app.getCurRawAdj()));
+        app.setCurRawProcState(!cycleReEval
+                ? procState
+                : Math.min(procState, app.getCurRawProcState()));
+
+        app.hasStartedServices = false;
+        app.adjSeq = mAdjSeq;
+
+        if (mService.mBackupTarget != null && app == mService.mBackupTarget.app) {
+            // If possible we want to avoid killing apps while they're being backed up
+            if (adj > ProcessList.BACKUP_APP_ADJ) {
+                if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "oom BACKUP_APP_ADJ for " + app);
+                adj = ProcessList.BACKUP_APP_ADJ;
+                if (procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) {
+                    procState = ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+                }
+                app.adjType = "backup";
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to backup: " + app);
+                }
+                app.cached = false;
+            }
+            if (procState > ActivityManager.PROCESS_STATE_BACKUP) {
+                procState = ActivityManager.PROCESS_STATE_BACKUP;
+                app.adjType = "backup";
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to backup: " + app);
+                }
+            }
+        }
+
+        boolean mayBeTop = false;
+        String mayBeTopType = null;
+        Object mayBeTopSource = null;
+        Object mayBeTopTarget = null;
+
+        for (int is = app.services.size() - 1;
+                is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
+                        || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
+                        || procState > ActivityManager.PROCESS_STATE_TOP);
+                is--) {
+            ServiceRecord s = app.services.valueAt(is);
+            if (s.startRequested) {
+                app.hasStartedServices = true;
+                if (procState > ActivityManager.PROCESS_STATE_SERVICE) {
+                    procState = ActivityManager.PROCESS_STATE_SERVICE;
+                    app.adjType = "started-services";
+                    if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                        reportOomAdjMessageLocked(TAG_OOM_ADJ,
+                                "Raise procstate to started service: " + app);
+                    }
+                }
+                if (app.hasShownUi && !wpc.isHomeProcess()) {
+                    // If this process has shown some UI, let it immediately
+                    // go to the LRU list because it may be pretty heavy with
+                    // UI stuff.  We'll tag it with a label just to help
+                    // debug and understand what is going on.
+                    if (adj > ProcessList.SERVICE_ADJ) {
+                        app.adjType = "cch-started-ui-services";
+                    }
+                } else {
+                    if (now < (s.lastActivity + mConstants.MAX_SERVICE_INACTIVITY)) {
+                        // This service has seen some activity within
+                        // recent memory, so we will keep its process ahead
+                        // of the background processes.
+                        if (adj > ProcessList.SERVICE_ADJ) {
+                            adj = ProcessList.SERVICE_ADJ;
+                            app.adjType = "started-services";
+                            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                                reportOomAdjMessageLocked(TAG_OOM_ADJ,
+                                        "Raise adj to started service: " + app);
+                            }
+                            app.cached = false;
+                        }
+                    }
+                    // If we have let the service slide into the background
+                    // state, still have some text describing what it is doing
+                    // even though the service no longer has an impact.
+                    if (adj > ProcessList.SERVICE_ADJ) {
+                        app.adjType = "cch-started-services";
+                    }
+                }
+            }
+
+            for (int conni = s.connections.size() - 1;
+                    conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
+                            || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
+                            || procState > ActivityManager.PROCESS_STATE_TOP);
+                    conni--) {
+                ArrayList<ConnectionRecord> clist = s.connections.valueAt(conni);
+                for (int i = 0;
+                        i < clist.size() && (adj > ProcessList.FOREGROUND_APP_ADJ
+                                || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
+                                || procState > ActivityManager.PROCESS_STATE_TOP);
+                        i++) {
+                    // XXX should compute this based on the max of
+                    // all connected clients.
+                    ConnectionRecord cr = clist.get(i);
+                    if (cr.binding.client == app) {
+                        // Binding to oneself is not interesting.
+                        continue;
+                    }
+
+                    boolean trackedProcState = false;
+                    if ((cr.flags& Context.BIND_WAIVE_PRIORITY) == 0) {
+                        ProcessRecord client = cr.binding.client;
+                        computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now, cycleReEval);
+
+                        if (shouldSkipDueToCycle(app, client, procState, adj, cycleReEval)) {
+                            continue;
+                        }
+
+                        int clientAdj = client.getCurRawAdj();
+                        int clientProcState = client.getCurRawProcState();
+
+                        if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
+                            // If the other app is cached for any reason, for purposes here
+                            // we are going to consider it empty.  The specific cached state
+                            // doesn't propagate except under certain conditions.
+                            clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+                        }
+                        String adjType = null;
+                        if ((cr.flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) {
+                            // Not doing bind OOM management, so treat
+                            // this guy more like a started service.
+                            if (app.hasShownUi && !wpc.isHomeProcess()) {
+                                // If this process has shown some UI, let it immediately
+                                // go to the LRU list because it may be pretty heavy with
+                                // UI stuff.  We'll tag it with a label just to help
+                                // debug and understand what is going on.
+                                if (adj > clientAdj) {
+                                    adjType = "cch-bound-ui-services";
+                                }
+                                app.cached = false;
+                                clientAdj = adj;
+                                clientProcState = procState;
+                            } else {
+                                if (now >= (s.lastActivity
+                                        + mConstants.MAX_SERVICE_INACTIVITY)) {
+                                    // This service has not seen activity within
+                                    // recent memory, so allow it to drop to the
+                                    // LRU list if there is no other reason to keep
+                                    // it around.  We'll also tag it with a label just
+                                    // to help debug and undertand what is going on.
+                                    if (adj > clientAdj) {
+                                        adjType = "cch-bound-services";
+                                    }
+                                    clientAdj = adj;
+                                }
+                            }
+                        }
+                        if (adj > clientAdj) {
+                            // If this process has recently shown UI, and
+                            // the process that is binding to it is less
+                            // important than being visible, then we don't
+                            // care about the binding as much as we care
+                            // about letting this process get into the LRU
+                            // list to be killed and restarted if needed for
+                            // memory.
+                            if (app.hasShownUi && !wpc.isHomeProcess()
+                                    && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+                                if (adj >= ProcessList.CACHED_APP_MIN_ADJ) {
+                                    adjType = "cch-bound-ui-services";
+                                }
+                            } else {
+                                int newAdj;
+                                if ((cr.flags&(Context.BIND_ABOVE_CLIENT
+                                        |Context.BIND_IMPORTANT)) != 0) {
+                                    if (clientAdj >= ProcessList.PERSISTENT_SERVICE_ADJ) {
+                                        newAdj = clientAdj;
+                                    } else {
+                                        // make this service persistent
+                                        newAdj = ProcessList.PERSISTENT_SERVICE_ADJ;
+                                        schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+                                        procState = ActivityManager.PROCESS_STATE_PERSISTENT;
+                                        cr.trackProcState(procState, mAdjSeq, now);
+                                        trackedProcState = true;
+                                    }
+                                } else if ((cr.flags & Context.BIND_ADJUST_BELOW_PERCEPTIBLE) != 0
+                                        && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ
+                                        && adj > ProcessList.PERCEPTIBLE_APP_ADJ + 1) {
+                                    newAdj = ProcessList.PERCEPTIBLE_APP_ADJ + 1;
+                                } else if ((cr.flags&Context.BIND_NOT_VISIBLE) != 0
+                                        && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ
+                                        && adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+                                    newAdj = ProcessList.PERCEPTIBLE_APP_ADJ;
+                                } else if (clientAdj >= ProcessList.PERCEPTIBLE_APP_ADJ) {
+                                    newAdj = clientAdj;
+                                } else {
+                                    if (adj > ProcessList.VISIBLE_APP_ADJ) {
+                                        newAdj = Math.max(clientAdj, ProcessList.VISIBLE_APP_ADJ);
+                                    } else {
+                                        newAdj = adj;
+                                    }
+                                }
+                                if (!client.cached) {
+                                    app.cached = false;
+                                }
+                                if (adj >  newAdj) {
+                                    adj = newAdj;
+                                    app.setCurRawAdj(adj);
+                                    adjType = "service";
+                                }
+                            }
+                        }
+                        if ((cr.flags & (Context.BIND_NOT_FOREGROUND
+                                | Context.BIND_IMPORTANT_BACKGROUND)) == 0) {
+                            // This will treat important bound services identically to
+                            // the top app, which may behave differently than generic
+                            // foreground work.
+                            final int curSchedGroup = client.getCurrentSchedulingGroup();
+                            if (curSchedGroup > schedGroup) {
+                                if ((cr.flags&Context.BIND_IMPORTANT) != 0) {
+                                    schedGroup = curSchedGroup;
+                                } else {
+                                    schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+                                }
+                            }
+                            if (clientProcState <= ActivityManager.PROCESS_STATE_TOP) {
+                                if (clientProcState == ActivityManager.PROCESS_STATE_TOP) {
+                                    // Special handling of clients who are in the top state.
+                                    // We *may* want to consider this process to be in the
+                                    // top state as well, but only if there is not another
+                                    // reason for it to be running.  Being on the top is a
+                                    // special state, meaning you are specifically running
+                                    // for the current top app.  If the process is already
+                                    // running in the background for some other reason, it
+                                    // is more important to continue considering it to be
+                                    // in the background state.
+                                    mayBeTop = true;
+                                    mayBeTopType = "service";
+                                    mayBeTopSource = cr.binding.client;
+                                    mayBeTopTarget = s.instanceName;
+                                    clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+                                } else {
+                                    // Special handling for above-top states (persistent
+                                    // processes).  These should not bring the current process
+                                    // into the top state, since they are not on top.  Instead
+                                    // give them the best state after that.
+                                    if ((cr.flags&Context.BIND_FOREGROUND_SERVICE) != 0) {
+                                        clientProcState =
+                                                ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+                                    } else if (mService.mWakefulness
+                                            == PowerManagerInternal.WAKEFULNESS_AWAKE &&
+                                            (cr.flags&Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE)
+                                                    != 0) {
+                                        clientProcState =
+                                                ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+                                    } else {
+                                        clientProcState =
+                                                ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+                                    }
+                                }
+                            }
+                        } else if ((cr.flags & Context.BIND_IMPORTANT_BACKGROUND) == 0) {
+                            if (clientProcState <
+                                    ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) {
+                                clientProcState =
+                                        ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+                            }
+                        } else {
+                            if (clientProcState <
+                                    ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) {
+                                clientProcState =
+                                        ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND;
+                            }
+                        }
+                        if (!trackedProcState) {
+                            cr.trackProcState(clientProcState, mAdjSeq, now);
+                        }
+                        if (procState > clientProcState) {
+                            procState = clientProcState;
+                            app.setCurRawProcState(procState);
+                            if (adjType == null) {
+                                adjType = "service";
+                            }
+                        }
+                        if (procState < ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+                                && (cr.flags & Context.BIND_SHOWING_UI) != 0) {
+                            app.setPendingUiClean(true);
+                        }
+                        if (adjType != null) {
+                            app.adjType = adjType;
+                            app.adjTypeCode = ActivityManager.RunningAppProcessInfo
+                                    .REASON_SERVICE_IN_USE;
+                            app.adjSource = cr.binding.client;
+                            app.adjSourceProcState = clientProcState;
+                            app.adjTarget = s.instanceName;
+                            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
+                                        + ": " + app + ", due to " + cr.binding.client
+                                        + " adj=" + adj + " procState="
+                                        + ProcessList.makeProcStateString(procState));
+                            }
+                        }
+                    }
+                    if ((cr.flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
+                        app.treatLikeActivity = true;
+                    }
+                    final ActivityServiceConnectionsHolder a = cr.activity;
+                    if ((cr.flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) {
+                        if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ
+                                && a.isActivityVisible()) {
+                            adj = ProcessList.FOREGROUND_APP_ADJ;
+                            app.setCurRawAdj(adj);
+                            if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) {
+                                if ((cr.flags&Context.BIND_IMPORTANT) != 0) {
+                                    schedGroup = ProcessList.SCHED_GROUP_TOP_APP_BOUND;
+                                } else {
+                                    schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+                                }
+                            }
+                            app.cached = false;
+                            app.adjType = "service";
+                            app.adjTypeCode = ActivityManager.RunningAppProcessInfo
+                                    .REASON_SERVICE_IN_USE;
+                            app.adjSource = a;
+                            app.adjSourceProcState = procState;
+                            app.adjTarget = s.instanceName;
+                            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                                reportOomAdjMessageLocked(TAG_OOM_ADJ,
+                                        "Raise to service w/activity: " + app);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        for (int provi = app.pubProviders.size() - 1;
+                provi >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
+                        || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
+                        || procState > ActivityManager.PROCESS_STATE_TOP);
+                provi--) {
+            ContentProviderRecord cpr = app.pubProviders.valueAt(provi);
+            for (int i = cpr.connections.size() - 1;
+                    i >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
+                            || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
+                            || procState > ActivityManager.PROCESS_STATE_TOP);
+                    i--) {
+                ContentProviderConnection conn = cpr.connections.get(i);
+                ProcessRecord client = conn.client;
+                if (client == app) {
+                    // Being our own client is not interesting.
+                    continue;
+                }
+                computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now, cycleReEval);
+
+                if (shouldSkipDueToCycle(app, client, procState, adj, cycleReEval)) {
+                    continue;
+                }
+
+                int clientAdj = client.getCurRawAdj();
+                int clientProcState = client.getCurRawProcState();
+
+                if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
+                    // If the other app is cached for any reason, for purposes here
+                    // we are going to consider it empty.
+                    clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+                }
+                String adjType = null;
+                if (adj > clientAdj) {
+                    if (app.hasShownUi && !wpc.isHomeProcess()
+                            && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+                        adjType = "cch-ui-provider";
+                    } else {
+                        adj = clientAdj > ProcessList.FOREGROUND_APP_ADJ
+                                ? clientAdj : ProcessList.FOREGROUND_APP_ADJ;
+                        app.setCurRawAdj(adj);
+                        adjType = "provider";
+                    }
+                    app.cached &= client.cached;
+                }
+                if (clientProcState <= ActivityManager.PROCESS_STATE_TOP) {
+                    if (clientProcState == ActivityManager.PROCESS_STATE_TOP) {
+                        // Special handling of clients who are in the top state.
+                        // We *may* want to consider this process to be in the
+                        // top state as well, but only if there is not another
+                        // reason for it to be running.  Being on the top is a
+                        // special state, meaning you are specifically running
+                        // for the current top app.  If the process is already
+                        // running in the background for some other reason, it
+                        // is more important to continue considering it to be
+                        // in the background state.
+                        mayBeTop = true;
+                        clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+                        mayBeTopType = adjType = "provider-top";
+                        mayBeTopSource = client;
+                        mayBeTopTarget = cpr.name;
+                    } else {
+                        // Special handling for above-top states (persistent
+                        // processes).  These should not bring the current process
+                        // into the top state, since they are not on top.  Instead
+                        // give them the best state after that.
+                        clientProcState =
+                                ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+                        if (adjType == null) {
+                            adjType = "provider";
+                        }
+                    }
+                }
+                conn.trackProcState(clientProcState, mAdjSeq, now);
+                if (procState > clientProcState) {
+                    procState = clientProcState;
+                    app.setCurRawProcState(procState);
+                }
+                if (client.getCurrentSchedulingGroup() > schedGroup) {
+                    schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+                }
+                if (adjType != null) {
+                    app.adjType = adjType;
+                    app.adjTypeCode = ActivityManager.RunningAppProcessInfo
+                            .REASON_PROVIDER_IN_USE;
+                    app.adjSource = client;
+                    app.adjSourceProcState = clientProcState;
+                    app.adjTarget = cpr.name;
+                    if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                        reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
+                                + ": " + app + ", due to " + client
+                                + " adj=" + adj + " procState="
+                                + ProcessList.makeProcStateString(procState));
+                    }
+                }
+            }
+            // If the provider has external (non-framework) process
+            // dependencies, ensure that its adjustment is at least
+            // FOREGROUND_APP_ADJ.
+            if (cpr.hasExternalProcessHandles()) {
+                if (adj > ProcessList.FOREGROUND_APP_ADJ) {
+                    adj = ProcessList.FOREGROUND_APP_ADJ;
+                    app.setCurRawAdj(adj);
+                    schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+                    app.cached = false;
+                    app.adjType = "ext-provider";
+                    app.adjTarget = cpr.name;
+                    if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                        reportOomAdjMessageLocked(TAG_OOM_ADJ,
+                                "Raise adj to external provider: " + app);
+                    }
+                }
+                if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+                    procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+                    app.setCurRawProcState(procState);
+                    if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                        reportOomAdjMessageLocked(TAG_OOM_ADJ,
+                                "Raise procstate to external provider: " + app);
+                    }
+                }
+            }
+        }
+
+        if (app.lastProviderTime > 0 &&
+                (app.lastProviderTime + mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) {
+            if (adj > ProcessList.PREVIOUS_APP_ADJ) {
+                adj = ProcessList.PREVIOUS_APP_ADJ;
+                schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+                app.cached = false;
+                app.adjType = "recent-provider";
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ,
+                            "Raise adj to recent provider: " + app);
+                }
+            }
+            if (procState > PROCESS_STATE_LAST_ACTIVITY) {
+                procState = PROCESS_STATE_LAST_ACTIVITY;
+                app.adjType = "recent-provider";
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ,
+                            "Raise procstate to recent provider: " + app);
+                }
+            }
+        }
+
+        if (mayBeTop && procState > ActivityManager.PROCESS_STATE_TOP) {
+            // A client of one of our services or providers is in the top state.  We
+            // *may* want to be in the top state, but not if we are already running in
+            // the background for some other reason.  For the decision here, we are going
+            // to pick out a few specific states that we want to remain in when a client
+            // is top (states that tend to be longer-term) and otherwise allow it to go
+            // to the top state.
+            switch (procState) {
+                case ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE:
+                case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
+                    // Something else is keeping it at this level, just leave it.
+                    break;
+                case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND:
+                case ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND:
+                case ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND:
+                case ActivityManager.PROCESS_STATE_SERVICE:
+                    // These all are longer-term states, so pull them up to the top
+                    // of the background states, but not all the way to the top state.
+                    procState = ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+                    app.adjType = mayBeTopType;
+                    app.adjSource = mayBeTopSource;
+                    app.adjTarget = mayBeTopTarget;
+                    if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                        reportOomAdjMessageLocked(TAG_OOM_ADJ, "May be top raise to " + mayBeTopType
+                                + ": " + app + ", due to " + mayBeTopSource
+                                + " adj=" + adj + " procState="
+                                + ProcessList.makeProcStateString(procState));
+                    }
+                    break;
+                default:
+                    // Otherwise, top is a better choice, so take it.
+                    procState = ActivityManager.PROCESS_STATE_TOP;
+                    app.adjType = mayBeTopType;
+                    app.adjSource = mayBeTopSource;
+                    app.adjTarget = mayBeTopTarget;
+                    if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                        reportOomAdjMessageLocked(TAG_OOM_ADJ, "May be top raise to " + mayBeTopType
+                                + ": " + app + ", due to " + mayBeTopSource
+                                + " adj=" + adj + " procState="
+                                + ProcessList.makeProcStateString(procState));
+                    }
+                    break;
+            }
+        }
+
+        if (procState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) {
+            if (app.hasClientActivities()) {
+                // This is a cached process, but with client activities.  Mark it so.
+                procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
+                app.adjType = "cch-client-act";
+            } else if (app.treatLikeActivity) {
+                // This is a cached process, but somebody wants us to treat it like it has
+                // an activity, okay!
+                procState = PROCESS_STATE_CACHED_ACTIVITY;
+                app.adjType = "cch-as-act";
+            }
+        }
+
+        if (adj == ProcessList.SERVICE_ADJ) {
+            if (doingAll) {
+                app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3);
+                mNewNumServiceProcs++;
+                //Slog.i(TAG, "ADJ " + app + " serviceb=" + app.serviceb);
+                if (!app.serviceb) {
+                    // This service isn't far enough down on the LRU list to
+                    // normally be a B service, but if we are low on RAM and it
+                    // is large we want to force it down since we would prefer to
+                    // keep launcher over it.
+                    if (mService.mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL
+                            && app.lastPss >= mProcessList.getCachedRestoreThresholdKb()) {
+                        app.serviceHighRam = true;
+                        app.serviceb = true;
+                        //Slog.i(TAG, "ADJ " + app + " high ram!");
+                    } else {
+                        mNewNumAServiceProcs++;
+                        //Slog.i(TAG, "ADJ " + app + " not high ram!");
+                    }
+                } else {
+                    app.serviceHighRam = false;
+                }
+            }
+            if (app.serviceb) {
+                adj = ProcessList.SERVICE_B_ADJ;
+            }
+        }
+
+        app.setCurRawAdj(adj);
+
+        //Slog.i(TAG, "OOM ADJ " + app + ": pid=" + app.pid +
+        //      " adj=" + adj + " curAdj=" + app.curAdj + " maxAdj=" + app.maxAdj);
+        if (adj > app.maxAdj) {
+            adj = app.maxAdj;
+            if (app.maxAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
+                schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+            }
+        }
+
+        // Put bound foreground services in a special sched group for additional
+        // restrictions on screen off
+        if (procState >= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE &&
+                mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) {
+            if (schedGroup > ProcessList.SCHED_GROUP_RESTRICTED) {
+                schedGroup = ProcessList.SCHED_GROUP_RESTRICTED;
+            }
+        }
+
+        // Do final modification to adj.  Everything we do between here and applying
+        // the final setAdj must be done in this function, because we will also use
+        // it when computing the final cached adj later.  Note that we don't need to
+        // worry about this for max adj above, since max adj will always be used to
+        // keep it out of the cached vaues.
+        app.curAdj = app.modifyRawOomAdj(adj);
+        app.setCurrentSchedulingGroup(schedGroup);
+        app.setCurProcState(procState);
+        app.setCurRawProcState(procState);
+        app.setHasForegroundActivities(foregroundActivities);
+        app.completedAdjSeq = mAdjSeq;
+
+        // if curAdj or curProcState improved, then this process was promoted
+        return app.curAdj < prevAppAdj || app.getCurProcState() < prevProcState;
+    }
+
+    /**
+     * Checks if for the given app and client, there's a cycle that should skip over the client
+     * for now or use partial values to evaluate the effect of the client binding.
+     * @param app
+     * @param client
+     * @param procState procstate evaluated so far for this app
+     * @param adj oom_adj evaluated so far for this app
+     * @param cycleReEval whether we're currently re-evaluating due to a cycle, and not the first
+     *                    evaluation.
+     * @return whether to skip using the client connection at this time
+     */
+    private boolean shouldSkipDueToCycle(ProcessRecord app, ProcessRecord client,
+            int procState, int adj, boolean cycleReEval) {
+        if (client.containsCycle) {
+            // We've detected a cycle. We should retry computeOomAdjLocked later in
+            // case a later-checked connection from a client  would raise its
+            // priority legitimately.
+            app.containsCycle = true;
+            // If the client has not been completely evaluated, check if it's worth
+            // using the partial values.
+            if (client.completedAdjSeq < mAdjSeq) {
+                if (cycleReEval) {
+                    // If the partial values are no better, skip until the next
+                    // attempt
+                    if (client.getCurRawProcState() >= procState
+                            && client.getCurRawAdj() >= adj) {
+                        return true;
+                    }
+                    // Else use the client's partial procstate and adj to adjust the
+                    // effect of the binding
+                } else {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /** Inform the oomadj observer of changes to oomadj. Used by tests. */
+    @GuardedBy("mService")
+    void reportOomAdjMessageLocked(String tag, String msg) {
+        Slog.d(tag, msg);
+        if (mService.mCurOomAdjObserver != null) {
+            mService.mUiHandler.obtainMessage(DISPATCH_OOM_ADJ_OBSERVER_MSG, msg).sendToTarget();
+        }
+    }
+
+    /** Applies the computed oomadj, procstate and sched group values and freezes them in set* */
+    @GuardedBy("mService")
+    private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
+            long nowElapsed) {
+        boolean success = true;
+
+        if (app.getCurRawAdj() != app.setRawAdj) {
+            app.setRawAdj = app.getCurRawAdj();
+        }
+
+        int changes = 0;
+
+        if (app.curAdj != app.setAdj) {
+            // don't compact during bootup
+            if (mConstants.USE_COMPACTION && mService.mBooted) {
+                // Perform a minor compaction when a perceptible app becomes the prev/home app
+                // Perform a major compaction when any app enters cached
+                // reminder: here, setAdj is previous state, curAdj is upcoming state
+                if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ &&
+                        (app.curAdj == ProcessList.PREVIOUS_APP_ADJ ||
+                                app.curAdj == ProcessList.HOME_APP_ADJ)) {
+                    mAppCompact.compactAppSome(app);
+                } else if (app.setAdj < ProcessList.CACHED_APP_MIN_ADJ &&
+                        app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ) {
+                    mAppCompact.compactAppFull(app);
+                }
+            }
+            ProcessList.setOomAdj(app.pid, app.uid, app.curAdj);
+            if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.info.uid) {
+                String msg = "Set " + app.pid + " " + app.processName + " adj "
+                        + app.curAdj + ": " + app.adjType;
+                reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
+            }
+            app.setAdj = app.curAdj;
+            app.verifiedAdj = ProcessList.INVALID_ADJ;
+        }
+
+        final int curSchedGroup = app.getCurrentSchedulingGroup();
+        if (app.setSchedGroup != curSchedGroup) {
+            int oldSchedGroup = app.setSchedGroup;
+            app.setSchedGroup = curSchedGroup;
+            if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) {
+                String msg = "Setting sched group of " + app.processName
+                        + " to " + curSchedGroup + ": " + app.adjType;
+                reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
+            }
+            if (app.waitingToKill != null && app.curReceivers.isEmpty()
+                    && app.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND) {
+                app.kill(app.waitingToKill, true);
+                success = false;
+            } else {
+                int processGroup;
+                switch (curSchedGroup) {
+                    case ProcessList.SCHED_GROUP_BACKGROUND:
+                        processGroup = THREAD_GROUP_BG_NONINTERACTIVE;
+                        break;
+                    case ProcessList.SCHED_GROUP_TOP_APP:
+                    case ProcessList.SCHED_GROUP_TOP_APP_BOUND:
+                        processGroup = THREAD_GROUP_TOP_APP;
+                        break;
+                    case ProcessList.SCHED_GROUP_RESTRICTED:
+                        processGroup = THREAD_GROUP_RESTRICTED;
+                        break;
+                    default:
+                        processGroup = THREAD_GROUP_DEFAULT;
+                        break;
+                }
+                long oldId = Binder.clearCallingIdentity();
+                try {
+                    setProcessGroup(app.pid, processGroup);
+                    if (curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) {
+                        // do nothing if we already switched to RT
+                        if (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
+                            app.getWindowProcessController().onTopProcChanged();
+                            if (mService.mUseFifoUiScheduling) {
+                                // Switch UI pipeline for app to SCHED_FIFO
+                                app.savedPriority = Process.getThreadPriority(app.pid);
+                                mService.scheduleAsFifoPriority(app.pid, /* suppressLogs */true);
+                                if (app.renderThreadTid != 0) {
+                                    mService.scheduleAsFifoPriority(app.renderThreadTid,
+                                            /* suppressLogs */true);
+                                    if (DEBUG_OOM_ADJ) {
+                                        Slog.d("UI_FIFO", "Set RenderThread (TID " +
+                                                app.renderThreadTid + ") to FIFO");
+                                    }
+                                } else {
+                                    if (DEBUG_OOM_ADJ) {
+                                        Slog.d("UI_FIFO", "Not setting RenderThread TID");
+                                    }
+                                }
+                            } else {
+                                // Boost priority for top app UI and render threads
+                                setThreadPriority(app.pid, TOP_APP_PRIORITY_BOOST);
+                                if (app.renderThreadTid != 0) {
+                                    try {
+                                        setThreadPriority(app.renderThreadTid,
+                                                TOP_APP_PRIORITY_BOOST);
+                                    } catch (IllegalArgumentException e) {
+                                        // thread died, ignore
+                                    }
+                                }
+                            }
+                        }
+                    } else if (oldSchedGroup == ProcessList.SCHED_GROUP_TOP_APP &&
+                            curSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
+                        app.getWindowProcessController().onTopProcChanged();
+                        if (mService.mUseFifoUiScheduling) {
+                            try {
+                                // Reset UI pipeline to SCHED_OTHER
+                                setThreadScheduler(app.pid, SCHED_OTHER, 0);
+                                setThreadPriority(app.pid, app.savedPriority);
+                                if (app.renderThreadTid != 0) {
+                                    setThreadScheduler(app.renderThreadTid,
+                                            SCHED_OTHER, 0);
+                                    setThreadPriority(app.renderThreadTid, -4);
+                                }
+                            } catch (IllegalArgumentException e) {
+                                Slog.w(TAG,
+                                        "Failed to set scheduling policy, thread does not exist:\n"
+                                                + e);
+                            } catch (SecurityException e) {
+                                Slog.w(TAG, "Failed to set scheduling policy, not allowed:\n" + e);
+                            }
+                        } else {
+                            // Reset priority for top app UI and render threads
+                            setThreadPriority(app.pid, 0);
+                            if (app.renderThreadTid != 0) {
+                                setThreadPriority(app.renderThreadTid, 0);
+                            }
+                        }
+                    }
+                } catch (Exception e) {
+                    if (false) {
+                        Slog.w(TAG, "Failed setting process group of " + app.pid
+                                + " to " + app.getCurrentSchedulingGroup());
+                        Slog.w(TAG, "at location", e);
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(oldId);
+                }
+            }
+        }
+        if (app.repForegroundActivities != app.hasForegroundActivities()) {
+            app.repForegroundActivities = app.hasForegroundActivities();
+            changes |= ActivityManagerService.ProcessChangeItem.CHANGE_ACTIVITIES;
+        }
+        if (app.getReportedProcState() != app.getCurProcState()) {
+            app.setReportedProcState(app.getCurProcState());
+            if (app.thread != null) {
+                try {
+                    if (false) {
+                        //RuntimeException h = new RuntimeException("here");
+                        Slog.i(TAG, "Sending new process state " + app.getReportedProcState()
+                                + " to " + app /*, h*/);
+                    }
+                    app.thread.setProcessState(app.getReportedProcState());
+                } catch (RemoteException e) {
+                }
+            }
+        }
+        if (app.setProcState == PROCESS_STATE_NONEXISTENT
+                || ProcessList.procStatesDifferForMem(app.getCurProcState(), app.setProcState)) {
+            if (false && mService.mTestPssMode
+                    && app.setProcState >= 0 && app.lastStateTime <= (now-200)) {
+                // Experimental code to more aggressively collect pss while
+                // running test...  the problem is that this tends to collect
+                // the data right when a process is transitioning between process
+                // states, which will tend to give noisy data.
+                long start = SystemClock.uptimeMillis();
+                long startTime = SystemClock.currentThreadTimeMillis();
+                long pss = Debug.getPss(app.pid, mTmpLong, null);
+                long endTime = SystemClock.currentThreadTimeMillis();
+                mService.recordPssSampleLocked(app, app.getCurProcState(), pss,
+                        mTmpLong[0], mTmpLong[1], mTmpLong[2],
+                        ProcessStats.ADD_PSS_INTERNAL_SINGLE, endTime-startTime, now);
+                mService.mPendingPssProcesses.remove(app);
+                Slog.i(TAG, "Recorded pss for " + app + " state " + app.setProcState
+                        + " to " + app.getCurProcState() + ": "
+                        + (SystemClock.uptimeMillis()-start) + "ms");
+            }
+            app.lastStateTime = now;
+            app.nextPssTime = ProcessList.computeNextPssTime(app.getCurProcState(),
+                    app.procStateMemTracker, mService.mTestPssMode,
+                    mService.mAtmInternal.isSleeping(), now);
+            if (DEBUG_PSS) Slog.d(TAG_PSS, "Process state change from "
+                    + ProcessList.makeProcStateString(app.setProcState) + " to "
+                    + ProcessList.makeProcStateString(app.getCurProcState()) + " next pss in "
+                    + (app.nextPssTime-now) + ": " + app);
+        } else {
+            if (now > app.nextPssTime || (now > (app.lastPssTime+ProcessList.PSS_MAX_INTERVAL)
+                    && now > (app.lastStateTime+ProcessList.minTimeFromStateChange(
+                    mService.mTestPssMode)))) {
+                if (mService.requestPssLocked(app, app.setProcState)) {
+                    app.nextPssTime = ProcessList.computeNextPssTime(app.getCurProcState(),
+                            app.procStateMemTracker, mService.mTestPssMode,
+                            mService.mAtmInternal.isSleeping(), now);
+                }
+            } else if (false && DEBUG_PSS) {
+                Slog.d(TAG_PSS,
+                        "Not requesting pss of " + app + ": next=" + (app.nextPssTime-now));
+            }
+        }
+        if (app.setProcState != app.getCurProcState()) {
+            if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) {
+                String msg = "Proc state change of " + app.processName
+                        + " to " + ProcessList.makeProcStateString(app.getCurProcState())
+                        + " (" + app.getCurProcState() + ")" + ": " + app.adjType;
+                reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
+            }
+            boolean setImportant = app.setProcState < ActivityManager.PROCESS_STATE_SERVICE;
+            boolean curImportant = app.getCurProcState() < ActivityManager.PROCESS_STATE_SERVICE;
+            if (setImportant && !curImportant) {
+                // This app is no longer something we consider important enough to allow to use
+                // arbitrary amounts of battery power. Note its current CPU time to later know to
+                // kill it if it is not behaving well.
+                app.setWhenUnimportant(now);
+                app.lastCpuTime = 0;
+            }
+            // Inform UsageStats of important process state change
+            // Must be called before updating setProcState
+            maybeUpdateUsageStatsLocked(app, nowElapsed);
+
+            maybeUpdateLastTopTime(app, now);
+
+            app.setProcState = app.getCurProcState();
+            if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
+                app.notCachedSinceIdle = false;
+            }
+            if (!doingAll) {
+                mService.setProcessTrackerStateLocked(app,
+                        mService.mProcessStats.getMemFactorLocked(), now);
+            } else {
+                app.procStateChanged = true;
+            }
+        } else if (app.reportedInteraction && (nowElapsed - app.getInteractionEventTime())
+                > mConstants.USAGE_STATS_INTERACTION_INTERVAL) {
+            // For apps that sit around for a long time in the interactive state, we need
+            // to report this at least once a day so they don't go idle.
+            maybeUpdateUsageStatsLocked(app, nowElapsed);
+        }
+
+        if (changes != 0) {
+            if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+                    "Changes in " + app + ": " + changes);
+            int i = mService.mPendingProcessChanges.size()-1;
+            ActivityManagerService.ProcessChangeItem item = null;
+            while (i >= 0) {
+                item = mService.mPendingProcessChanges.get(i);
+                if (item.pid == app.pid) {
+                    if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+                            "Re-using existing item: " + item);
+                    break;
+                }
+                i--;
+            }
+            if (i < 0) {
+                // No existing item in pending changes; need a new one.
+                final int NA = mService.mAvailProcessChanges.size();
+                if (NA > 0) {
+                    item = mService.mAvailProcessChanges.remove(NA-1);
+                    if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+                            "Retrieving available item: " + item);
+                } else {
+                    item = new ActivityManagerService.ProcessChangeItem();
+                    if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+                            "Allocating new item: " + item);
+                }
+                item.changes = 0;
+                item.pid = app.pid;
+                item.uid = app.info.uid;
+                if (mService.mPendingProcessChanges.size() == 0) {
+                    if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+                            "*** Enqueueing dispatch processes changed!");
+                    mService.mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG)
+                            .sendToTarget();
+                }
+                mService.mPendingProcessChanges.add(item);
+            }
+            item.changes |= changes;
+            item.foregroundActivities = app.repForegroundActivities;
+            if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+                    "Item " + Integer.toHexString(System.identityHashCode(item))
+                            + " " + app.toShortString() + ": changes=" + item.changes
+                            + " foreground=" + item.foregroundActivities
+                            + " type=" + app.adjType + " source=" + app.adjSource
+                            + " target=" + app.adjTarget);
+        }
+
+        return success;
+    }
+
+    @GuardedBy("mService")
+    private void maybeUpdateUsageStatsLocked(ProcessRecord app, long nowElapsed) {
+        if (DEBUG_USAGE_STATS) {
+            Slog.d(TAG, "Checking proc [" + Arrays.toString(app.getPackageList())
+                    + "] state changes: old = " + app.setProcState + ", new = "
+                    + app.getCurProcState());
+        }
+        if (mService.mUsageStatsService == null) {
+            return;
+        }
+        boolean isInteraction;
+        // To avoid some abuse patterns, we are going to be careful about what we consider
+        // to be an app interaction.  Being the top activity doesn't count while the display
+        // is sleeping, nor do short foreground services.
+        if (app.getCurProcState() <= ActivityManager.PROCESS_STATE_TOP) {
+            isInteraction = true;
+            app.setFgInteractionTime(0);
+        } else if (app.getCurProcState() <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+            if (app.getFgInteractionTime() == 0) {
+                app.setFgInteractionTime(nowElapsed);
+                isInteraction = false;
+            } else {
+                isInteraction = nowElapsed > app.getFgInteractionTime()
+                        + mConstants.SERVICE_USAGE_INTERACTION_TIME;
+            }
+        } else {
+            isInteraction =
+                    app.getCurProcState() <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+            app.setFgInteractionTime(0);
+        }
+        if (isInteraction
+                && (!app.reportedInteraction || (nowElapsed - app.getInteractionEventTime())
+                > mConstants.USAGE_STATS_INTERACTION_INTERVAL)) {
+            app.setInteractionEventTime(nowElapsed);
+            String[] packages = app.getPackageList();
+            if (packages != null) {
+                for (int i = 0; i < packages.length; i++) {
+                    mService.mUsageStatsService.reportEvent(packages[i], app.userId,
+                            UsageEvents.Event.SYSTEM_INTERACTION);
+                }
+            }
+        }
+        app.reportedInteraction = isInteraction;
+        if (!isInteraction) {
+            app.setInteractionEventTime(0);
+        }
+    }
+
+    private void maybeUpdateLastTopTime(ProcessRecord app, long nowUptime) {
+        if (app.setProcState <= ActivityManager.PROCESS_STATE_TOP
+                && app.getCurProcState() > ActivityManager.PROCESS_STATE_TOP) {
+            app.lastTopTime = nowUptime;
+        }
+    }
+
+    /**
+     * Look for recently inactive apps and mark them idle after a grace period. If idled, stop
+     * any background services and inform listeners.
+     */
+    @GuardedBy("mService")
+    void idleUidsLocked() {
+        final int N = mActiveUids.size();
+        if (N <= 0) {
+            return;
+        }
+        final long nowElapsed = SystemClock.elapsedRealtime();
+        final long maxBgTime = nowElapsed - mConstants.BACKGROUND_SETTLE_TIME;
+        long nextTime = 0;
+        if (mLocalPowerManager != null) {
+            mLocalPowerManager.startUidChanges();
+        }
+        for (int i = N - 1; i >= 0; i--) {
+            final UidRecord uidRec = mActiveUids.valueAt(i);
+            final long bgTime = uidRec.lastBackgroundTime;
+            if (bgTime > 0 && !uidRec.idle) {
+                if (bgTime <= maxBgTime) {
+                    EventLogTags.writeAmUidIdle(uidRec.uid);
+                    uidRec.idle = true;
+                    uidRec.setIdle = true;
+                    mService.doStopUidLocked(uidRec.uid, uidRec);
+                } else {
+                    if (nextTime == 0 || nextTime > bgTime) {
+                        nextTime = bgTime;
+                    }
+                }
+            }
+        }
+        if (mLocalPowerManager != null) {
+            mLocalPowerManager.finishUidChanges();
+        }
+        if (nextTime > 0) {
+            mService.mHandler.removeMessages(IDLE_UIDS_MSG);
+            mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
+                    nextTime + mConstants.BACKGROUND_SETTLE_TIME - nowElapsed);
+        }
+    }
+
+    @GuardedBy("mService")
+    final void setAppIdTempWhitelistStateLocked(int appId, boolean onWhitelist) {
+        boolean changed = false;
+        for (int i = mActiveUids.size() - 1; i >= 0; i--) {
+            final UidRecord uidRec = mActiveUids.valueAt(i);
+            if (UserHandle.getAppId(uidRec.uid) == appId && uidRec.curWhitelist != onWhitelist) {
+                uidRec.curWhitelist = onWhitelist;
+                changed = true;
+            }
+        }
+        if (changed) {
+            updateOomAdjLocked();
+        }
+    }
+
+    @GuardedBy("mService")
+    final void setUidTempWhitelistStateLocked(int uid, boolean onWhitelist) {
+        boolean changed = false;
+        final UidRecord uidRec = mActiveUids.get(uid);
+        if (uidRec != null && uidRec.curWhitelist != onWhitelist) {
+            uidRec.curWhitelist = onWhitelist;
+            updateOomAdjLocked();
+        }
+    }
+
+    @GuardedBy("mService")
+    void dumpProcessListVariablesLocked(ProtoOutputStream proto) {
+        proto.write(ActivityManagerServiceDumpProcessesProto.ADJ_SEQ, mAdjSeq);
+        proto.write(ActivityManagerServiceDumpProcessesProto.LRU_SEQ, mProcessList.mLruSeq);
+        proto.write(ActivityManagerServiceDumpProcessesProto.NUM_NON_CACHED_PROCS,
+                mNumNonCachedProcs);
+        proto.write(ActivityManagerServiceDumpProcessesProto.NUM_SERVICE_PROCS, mNumServiceProcs);
+        proto.write(ActivityManagerServiceDumpProcessesProto.NEW_NUM_SERVICE_PROCS,
+                mNewNumServiceProcs);
+
+    }
+
+    @GuardedBy("mService")
+    void dumpSequenceNumbersLocked(PrintWriter pw) {
+        pw.println("  mAdjSeq=" + mAdjSeq + " mLruSeq=" + mProcessList.mLruSeq);
+    }
+
+    @GuardedBy("mService")
+    void dumpProcCountsLocked(PrintWriter pw) {
+        pw.println("  mNumNonCachedProcs=" + mNumNonCachedProcs
+                + " (" + mProcessList.getLruSizeLocked() + " total)"
+                + " mNumCachedHiddenProcs=" + mNumCachedHiddenProcs
+                + " mNumServiceProcs=" + mNumServiceProcs
+                + " mNewNumServiceProcs=" + mNewNumServiceProcs);
+    }
+
+}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 9898d06..5bc8845 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -17,6 +17,7 @@
 package com.android.server.am;
 
 import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
 import static android.app.ActivityThread.PROC_START_SEQ_IDENT;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
 import static android.os.Process.SYSTEM_UID;
@@ -123,7 +124,7 @@
  * </ul>
  */
 public final class ProcessList {
-    private static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM;
+    static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM;
 
     // The minimum time we allow between crashes, for us to consider this
     // application to be bad and stop and its services and reject broadcasts.
@@ -352,6 +353,8 @@
      */
     int mLruSeq = 0;
 
+    ActiveUids mActiveUids;
+
     /**
      * The currently running isolated processes.
      */
@@ -549,8 +552,10 @@
         updateOomLevels(0, 0, false);
     }
 
-    void init(ActivityManagerService service) {
+    void init(ActivityManagerService service, ActiveUids activeUids) {
         mService = service;
+        mActiveUids = activeUids;
+
         if (sKillHandler == null) {
             sKillThread = new ServiceThread(TAG + ":kill",
                     THREAD_PRIORITY_BACKGROUND, true /* allowIo */);
@@ -2176,7 +2181,7 @@
         } else if (old != null) {
             Slog.wtf(TAG, "Already have existing proc " + old + " when adding " + proc);
         }
-        UidRecord uidRec = mService.mActiveUids.get(proc.uid);
+        UidRecord uidRec = mActiveUids.get(proc.uid);
         if (uidRec == null) {
             uidRec = new UidRecord(proc.uid, mService.mAtmInternal);
             // This is the first appearance of the uid, report it now!
@@ -2188,7 +2193,7 @@
                 uidRec.setWhitelist = uidRec.curWhitelist = true;
             }
             uidRec.updateHasInternetPermission();
-            mService.mActiveUids.put(proc.uid, uidRec);
+            mActiveUids.put(proc.uid, uidRec);
             EventLogTags.writeAmUidRunning(uidRec.uid);
             mService.noteUidProcessState(uidRec.uid, uidRec.getCurProcState());
         }
@@ -2290,7 +2295,7 @@
                         "No more processes in " + old.uidRecord);
                 mService.enqueueUidChangeLocked(old.uidRecord, -1, UidRecord.CHANGE_GONE);
                 EventLogTags.writeAmUidStopped(uid);
-                mService.mActiveUids.remove(uid);
+                mActiveUids.remove(uid);
                 mService.noteUidProcessState(uid, ActivityManager.PROCESS_STATE_NONEXISTENT);
             }
             old.uidRecord = null;
@@ -3050,4 +3055,37 @@
             }
         }
     }
+
+    /** Returns the uid's process state or PROCESS_STATE_NONEXISTENT if not running */
+    @GuardedBy("mService")
+    int getUidProcStateLocked(int uid) {
+        UidRecord uidRec = mActiveUids.get(uid);
+        return uidRec == null ? PROCESS_STATE_NONEXISTENT : uidRec.getCurProcState();
+    }
+
+    /** Returns the UidRecord for the given uid, if it exists. */
+    @GuardedBy("mService")
+    UidRecord getUidRecordLocked(int uid) {
+        return mActiveUids.get(uid);
+    }
+
+    /**
+     * Call {@link ActivityManagerService#doStopUidLocked}
+     * (which will also stop background services) for all idle UIDs.
+     */
+    @GuardedBy("mService")
+    void doStopUidForIdleUidsLocked() {
+        final int size = mActiveUids.size();
+        for (int i = 0; i < size; i++) {
+            final int uid = mActiveUids.keyAt(i);
+            if (UserHandle.isCore(uid)) {
+                continue;
+            }
+            final UidRecord uidRec = mActiveUids.valueAt(i);
+            if (!uidRec.idle) {
+                continue;
+            }
+            mService.doStopUidLocked(uidRec.uid, uidRec);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index d5ede5b..c2117a7 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -77,6 +77,7 @@
     // permission in the corresponding .te file your feature belongs to.
     @VisibleForTesting
     static final String[] sDeviceConfigScopes = new String[] {
+        DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT,
     };
 
     private final String[] mGlobalSettings;
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 6f5a196..ff029c1 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -343,14 +343,6 @@
     static final String PROPERTY_HDMI_CEC_NEVER_ASSIGN_LOGICAL_ADDRESSES =
             "ro.hdmi.property_hdmi_cec_never_assign_logical_addresses";
 
-    /**
-     * Property to indicate if the current device is a cec switch device.
-     *
-     * <p> Default is false.
-     */
-    static final String PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH =
-            "ro.hdmi.property_is_device_hdmi_cec_switch";
-
     // Set to false to allow playback device to go to suspend mode even
     // when it's an active source. True by default.
     static final String PROPERTY_KEEP_AWAKE = "persist.sys.hdmi.keep_awake";
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index b75e75f..ba21b78 100755
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -28,7 +28,7 @@
 
 /**
  * Feature action that handles device discovery sequences.
- * Device discovery is launched when TV device is woken from "Standby" state
+ * Device discovery is launched when device is woken from "Standby" state
  * or enabled "Control for Hdmi" from disabled state.
  *
  * <p>Device discovery goes through the following steps.
@@ -89,6 +89,7 @@
     private final DeviceDiscoveryCallback mCallback;
     private int mProcessedDeviceCount = 0;
     private int mTimeoutRetry = 0;
+    private boolean mIsTvDevice = source().mService.isTvDevice();
 
     /**
      * Constructor.
@@ -266,15 +267,19 @@
         current.mPortId = getPortId(current.mPhysicalAddress);
         current.mDeviceType = params[2] & 0xFF;
 
-        tv().updateCecSwitchInfo(current.mLogicalAddress, current.mDeviceType,
+        // TODO(amyjojo): check if non-TV device needs to update cec switch info.
+        // This is to manager CEC device separately in case they don't have address.
+        if (mIsTvDevice) {
+            tv().updateCecSwitchInfo(current.mLogicalAddress, current.mDeviceType,
                     current.mPhysicalAddress);
-
+        }
         increaseProcessedDeviceCount();
         checkAndProceedStage();
     }
 
     private int getPortId(int physicalAddress) {
-        return tv().getPortId(physicalAddress);
+        return mIsTvDevice ? tv().getPortId(physicalAddress)
+            : source().getPortId(physicalAddress);
     }
 
     private void handleSetOsdName(HdmiCecMessage cmd) {
@@ -345,7 +350,9 @@
         mCallback.onDeviceDiscoveryDone(result);
         finish();
         // Process any commands buffered while device discovery action was in progress.
-        tv().processAllDelayedMessages();
+        if (mIsTvDevice) {
+            tv().processAllDelayedMessages();
+        }
     }
 
     private void checkAndProceedStage() {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 6e1b018..32dc0261 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -916,6 +916,11 @@
         setActivePath(mService.portIdToPath(portId));
     }
 
+    // Returns the id of the port that the target device is connected to.
+    int getPortId(int physicalAddress) {
+        return mService.pathToPortId(physicalAddress);
+    }
+
     @ServiceThreadOnly
     HdmiCecMessageCache getCecMessageCache() {
         assertRunOnServiceThread();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 8cfe47f..0e4e334 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -23,19 +23,25 @@
 import android.content.Intent;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiPortInfo;
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.media.tv.TvContract;
 import android.os.SystemProperties;
+import android.util.Slog;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.hdmi.Constants.AudioCodec;
+import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
 
+import java.io.UnsupportedEncodingException;
 import java.util.HashMap;
+import java.util.List;
 
 /**
  * Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android
@@ -71,6 +77,10 @@
     // processing.
     private final HashMap<Integer, String> mTvInputs = new HashMap<>();
 
+    // Map-like container of all cec devices.
+    // device id is used as key of container.
+    private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>();
+
     protected HdmiCecLocalDeviceAudioSystem(HdmiControlService service) {
         super(service, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
         mSystemAudioControlFeatureEnabled = true;
@@ -86,6 +96,132 @@
                 "com.droidlogic.tvinput/.services.Hdmi3InputService/HW7");
     }
 
+    /**
+     * Called when a device is newly added or a new device is detected or
+     * an existing device is updated.
+     *
+     * @param info device info of a new device.
+     */
+    @ServiceThreadOnly
+    final void addCecDevice(HdmiDeviceInfo info) {
+        assertRunOnServiceThread();
+        HdmiDeviceInfo old = addDeviceInfo(info);
+        if (info.getPhysicalAddress() == mService.getPhysicalAddress()) {
+            // The addition of the device itself should not be notified.
+            // Note that different logical address could still be the same local device.
+            return;
+        }
+        if (old == null) {
+            invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+        } else if (!old.equals(info)) {
+            invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
+            invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+        }
+    }
+
+    /**
+     * Called when a device is removed or removal of device is detected.
+     *
+     * @param address a logical address of a device to be removed
+     */
+    @ServiceThreadOnly
+    final void removeCecDevice(int address) {
+        assertRunOnServiceThread();
+        HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
+
+        mCecMessageCache.flushMessagesFrom(address);
+        invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
+    }
+
+    /**
+     * Called when a device is updated.
+     *
+     * @param info device info of the updating device.
+     */
+    @ServiceThreadOnly
+    final void updateCecDevice(HdmiDeviceInfo info) {
+        assertRunOnServiceThread();
+        HdmiDeviceInfo old = addDeviceInfo(info);
+
+        if (old == null) {
+            invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+        } else if (!old.equals(info)) {
+            invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
+        }
+    }
+
+    /**
+    * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same
+     * logical address as new device info's.
+     *
+     * @param deviceInfo a new {@link HdmiDeviceInfo} to be added.
+     * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo}
+     *         that has the same logical address as new one has.
+     */
+    @ServiceThreadOnly
+    @VisibleForTesting
+    protected HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) {
+        assertRunOnServiceThread();
+        HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress());
+        if (oldDeviceInfo != null) {
+            removeDeviceInfo(deviceInfo.getId());
+        }
+        mDeviceInfos.append(deviceInfo.getId(), deviceInfo);
+        return oldDeviceInfo;
+    }
+
+    /**
+     * Remove a device info corresponding to the given {@code logicalAddress}.
+     * It returns removed {@link HdmiDeviceInfo} if exists.
+     *
+     * @param id id of device to be removed
+     * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null}
+     */
+    @ServiceThreadOnly
+    private HdmiDeviceInfo removeDeviceInfo(int id) {
+        assertRunOnServiceThread();
+        HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id);
+        if (deviceInfo != null) {
+            mDeviceInfos.remove(id);
+        }
+        return deviceInfo;
+    }
+
+    /**
+     * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}.
+     *
+     * @param logicalAddress logical address of the device to be retrieved
+     * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
+     *         Returns null if no logical address matched
+     */
+    @ServiceThreadOnly
+    HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) {
+        assertRunOnServiceThread();
+        return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress));
+    }
+
+    private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) {
+        mService.invokeDeviceEventListeners(info, status);
+    }
+
+    @Override
+    @ServiceThreadOnly
+    void onHotplug(int portId, boolean connected) {
+        assertRunOnServiceThread();
+        if (connected) {
+            mService.wakeUp();
+        }
+        if (mService.getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
+            mCecMessageCache.flushAll();
+        } else {
+            if (connected) {
+                launchDeviceDiscovery();
+            } else {
+                // TODO(amyjojo): remove device from mDeviceInfo
+            }
+        }
+    }
+
     @Override
     @ServiceThreadOnly
     protected void onStandby(boolean initiatedByCec, int standbyAction) {
@@ -116,6 +252,8 @@
         boolean lastSystemAudioControlStatus =
                 SystemProperties.getBoolean(Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL, true);
         systemAudioControlOnPowerOn(systemAudioControlOnPowerOnProp, lastSystemAudioControlStatus);
+        clearDeviceInfoList();
+        launchDeviceDiscovery();
         startQueuedActions();
     }
 
@@ -152,6 +290,78 @@
 
     @Override
     @ServiceThreadOnly
+    protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        int path = HdmiUtils.twoBytesToInt(message.getParams());
+        int address = message.getSource();
+        int type = message.getParams()[2];
+
+        // Ignore if [Device Discovery Action] is going on.
+        if (hasAction(DeviceDiscoveryAction.class)) {
+            Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message);
+            return true;
+        }
+
+        // Update the device info with TIF, note that the same device info could have added in
+        // device discovery and we do not want to override it with default OSD name. Therefore we
+        // need the following check to skip redundant device info updating.
+        HdmiDeviceInfo oldDevice = getCecDeviceInfo(address);
+        if (oldDevice == null || oldDevice.getPhysicalAddress() != path) {
+            addCecDevice(new HdmiDeviceInfo(
+                    address, path, mService.pathToPortId(path), type,
+                    Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(address)));
+            // if we are adding a new device info, send out a give osd name command
+            // to update the name of the device in TIF
+            mService.sendCecCommand(
+                    HdmiCecMessageBuilder.buildGiveOsdNameCommand(mAddress, address));
+            return true;
+        }
+
+        Slog.w(TAG, "Device info exists. Not updating on Physical Address.");
+        return true;
+    }
+
+    @Override
+    protected boolean handleReportPowerStatus(HdmiCecMessage command) {
+        int newStatus = command.getParams()[0] & 0xFF;
+        updateDevicePowerStatus(command.getSource(), newStatus);
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleSetOsdName(HdmiCecMessage message) {
+        int source = message.getSource();
+        String osdName;
+        HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source);
+        // If the device is not in device list, ignore it.
+        if (deviceInfo == null) {
+            Slog.i(TAG, "No source device info for <Set Osd Name>." + message);
+            return true;
+        }
+        try {
+            osdName = new String(message.getParams(), "US-ASCII");
+        } catch (UnsupportedEncodingException e) {
+            Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e);
+            return true;
+        }
+
+        if (deviceInfo.getDisplayName().equals(osdName)) {
+            Slog.d(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
+            return true;
+        }
+
+        Slog.d(TAG, "Updating device OSD name from "
+                + deviceInfo.getDisplayName()
+                + " to " + osdName);
+        updateCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
+                deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(),
+                deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName));
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
     protected boolean handleReportAudioStatus(HdmiCecMessage message) {
         assertRunOnServiceThread();
         // TODO(amyjojo): implement report audio status handler
@@ -407,8 +617,8 @@
         HdmiLogger.debug(
                 "System Audio Mode change[old:%b new:%b]",
                 mSystemAudioActivated, newSystemAudioMode);
-        // Wake up device if System Audio Control is turned on but device is still on standby
-        if (newSystemAudioMode && mService.isPowerStandbyOrTransient()) {
+        // Wake up device if System Audio Control is turned on
+        if (newSystemAudioMode) {
             mService.wakeUp();
         }
         setSystemAudioMode(newSystemAudioMode);
@@ -494,19 +704,20 @@
             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
             return;
         }
-        getActiveSource().invalidate();
         if (!mService.isControlEnabled()) {
+            setRoutingPort(portId);
             setLocalActivePort(portId);
             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
             return;
         }
-        int oldPath = getLocalActivePort() != Constants.CEC_SWITCH_HOME
-                ? getActivePathOnSwitchFromActivePortId(getLocalActivePort())
+        int oldPath = getRoutingPort() != Constants.CEC_SWITCH_HOME
+                ? mService.portIdToPath(getRoutingPort())
                 : getDeviceInfo().getPhysicalAddress();
-        int newPath = getActivePathOnSwitchFromActivePortId(portId);
+        int newPath = mService.portIdToPath(portId);
         if (oldPath == newPath) {
             return;
         }
+        setRoutingPort(portId);
         setLocalActivePort(portId);
         HdmiCecMessage routingChange =
                 HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath);
@@ -575,10 +786,8 @@
             mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
             return;
         }
-        // Wake up device if it is still on standby
-        if (mService.isPowerStandbyOrTransient()) {
-            mService.wakeUp();
-        }
+        // Wake up device
+        mService.wakeUp();
         // Check if TV supports System Audio Control.
         // Handle broadcasting setSystemAudioMode on or aborting message on callback.
         queryTvSystemAudioModeSupport(new TvSystemAudioModeSupportedCallback() {
@@ -635,9 +844,7 @@
             return;
         }
         // Wake up if the current device if ready to route.
-        if (mService.isPowerStandbyOrTransient()) {
-            mService.wakeUp();
-        }
+        mService.wakeUp();
         if (portId == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) {
             switchToHomeTvInput();
         } else if (portId == Constants.CEC_SWITCH_ARC) {
@@ -725,4 +932,50 @@
                 mAddress, routingInformationPath));
         routeToInputFromPortId(getRoutingPort());
     }
+
+    protected void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
+        HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
+        if (info == null) {
+            Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress);
+            return;
+        }
+
+        if (info.getDevicePowerStatus() == newPowerStatus) {
+            return;
+        }
+
+        HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus);
+        // addDeviceInfo replaces old device info with new one if exists.
+        addDeviceInfo(newInfo);
+
+        invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
+    }
+
+    @ServiceThreadOnly
+    private void launchDeviceDiscovery() {
+        assertRunOnServiceThread();
+        DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
+                new DeviceDiscoveryCallback() {
+                    @Override
+                    public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
+                        for (HdmiDeviceInfo info : deviceInfos) {
+                            addCecDevice(info);
+                        }
+                    }
+                });
+        addAndStartAction(action);
+    }
+
+    // Clear all device info.
+    @ServiceThreadOnly
+    private void clearDeviceInfoList() {
+        assertRunOnServiceThread();
+        for (HdmiDeviceInfo info : HdmiUtils.sparseArrayToList(mDeviceInfos)) {
+            if (info.getPhysicalAddress() == mService.getPhysicalAddress()) {
+                continue;
+            }
+            invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
+        }
+        mDeviceInfos.clear();
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index 6532e16..a95f7f1 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -16,7 +16,10 @@
 
 package com.android.server.hdmi;
 
+import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH;
+
 import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiPortInfo;
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.os.SystemProperties;
 import android.util.Slog;
@@ -42,7 +45,7 @@
     // Device has cec switch functionality or not.
     // Default is false.
     protected boolean mIsSwitchDevice = SystemProperties.getBoolean(
-            Constants.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false);
+            PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false);
 
     // Routing port number used for Routing Control.
     // This records the default routing port or the previous valid routing port.
@@ -71,9 +74,11 @@
     @ServiceThreadOnly
     void onHotplug(int portId, boolean connected) {
         assertRunOnServiceThread();
-        mCecMessageCache.flushAll();
+        if (mService.getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
+            mCecMessageCache.flushAll();
+        }
         // We'll not clear mIsActiveSource on the hotplug event to pass CETC 11.2.2-2 ~ 3.
-        if (mService.isPowerStandbyOrTransient()) {
+        if (connected) {
             mService.wakeUp();
         }
     }
@@ -117,6 +122,7 @@
             setActiveSource(activeSource);
         }
         setIsActiveSource(physicalAddress == mService.getPhysicalAddress());
+        updateDevicePowerStatus(logicalAddress, HdmiControlManager.POWER_STATUS_ON);
         switchInputOnReceivingNewActivePath(physicalAddress);
         return true;
     }
@@ -185,6 +191,13 @@
         // do nothing
     }
 
+    // Update the power status of the devices connected to the current device.
+    // This only works if the current device is a switch and keeps tracking the device info
+    // of the device connected to it.
+    protected void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
+        // do nothing
+    }
+
     // Active source claiming needs to be handled in Service
     // since service can decide who will be the active source when the device supports
     // multiple device types in this method.
@@ -204,10 +217,8 @@
         if (!mIsActiveSource) {
             return;
         }
-        // Wake up the device if the power is in standby mode
-        if (mService.isPowerStandbyOrTransient()) {
-            mService.wakeUp();
-        }
+        // Wake up the device
+        mService.wakeUp();
         return;
     }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index b91d8c6..a8c4350 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -346,10 +346,6 @@
         }
     }
 
-    int getPortId(int physicalAddress) {
-        return mService.pathToPortId(physicalAddress);
-    }
-
     /**
      * Returns the previous port id kept to handle input switching on <Inactive Source>.
      */
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 833091d..2d6e762 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -19,6 +19,7 @@
 import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE;
 import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE;
 
+import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH;
 import static com.android.server.hdmi.Constants.DISABLED;
 import static com.android.server.hdmi.Constants.ENABLED;
 import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE;
@@ -754,6 +755,9 @@
         mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
         mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
 
+        if (mMhlController == null) {
+            return;
+        }
         HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
         ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
         for (HdmiPortInfo info : mhlPortInfo) {
@@ -808,13 +812,31 @@
     }
 
     /**
-     * Returns the id of HDMI port located at the top of the hierarchy of
-     * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
-     * the port id to be returned is the ID associated with the port address
-     * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
+     * Returns the id of HDMI port located at the current device that runs this method.
+     *
+     * For TV with physical address 0x0000, target device 0x1120, we want port physical address
+     * 0x1000 to get the correct port id from {@link #mPortIdMap}. For device with Physical Address
+     * 0x2000, target device 0x2420, we want port address 0x24000 to get the port id.
+     *
+     * <p>Return {@link Constants#INVALID_PORT_ID} if target device does not connect to.
+     *
+     * @param path the target device's physical address.
+     * @return the id of the port that the target device eventually connects to
+     * on the current device.
      */
     int pathToPortId(int path) {
-        int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
+        int mask = 0xF000;
+        int finalMask = 0xF000;
+        int physicalAddress = getPhysicalAddress();
+        int maskedAddress = physicalAddress;
+
+        while (maskedAddress != 0) {
+            maskedAddress = physicalAddress & mask;
+            finalMask |= mask;
+            mask >>= 4;
+        }
+
+        int portAddress = path & finalMask;
         return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
     }
 
@@ -1007,8 +1029,9 @@
     void onHotplug(int portId, boolean connected) {
         assertRunOnServiceThread();
 
-        if (connected && !isTvDevice()) {
-            if (getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT && isSwitchDevice()) {
+        if (connected && !isTvDevice()
+                && getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
+            if (isSwitchDevice()) {
                 initPortInfo();
                 HdmiLogger.debug("initPortInfo for switch device when onHotplug from tx.");
             }
@@ -2130,7 +2153,7 @@
 
     boolean isSwitchDevice() {
         return SystemProperties.getBoolean(
-            Constants.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false);
+            PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false);
     }
 
     boolean isTvDeviceEnabled() {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index df28f30..979de66 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1761,12 +1761,15 @@
 
     // Native callback
     private void notifyFocusChanged(IBinder oldToken, IBinder newToken) {
-        if (mFocusedWindow.asBinder() == newToken) {
-            Log.w(TAG, "notifyFocusChanged called with unchanged mFocusedWindow=" + mFocusedWindow);
-            return;
+        if (mFocusedWindow != null) {
+            if (mFocusedWindow.asBinder() == newToken) {
+                Slog.w(TAG, "notifyFocusChanged called with unchanged mFocusedWindow="
+                        + mFocusedWindow);
+                return;
+            }
+            setPointerCapture(false);
         }
 
-        setPointerCapture(false);
         mFocusedWindow = IWindow.Stub.asInterface(newToken);
     }
 
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index ce0e72b..ba7b87e 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.media;
 
+import static android.os.UserHandle.USER_ALL;
+
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.INotificationManager;
@@ -48,6 +50,7 @@
 import android.media.session.IOnMediaKeyListener;
 import android.media.session.IOnVolumeKeyLongPressListener;
 import android.media.session.ISession;
+import android.media.session.ISession2TokensListener;
 import android.media.session.ISessionManager;
 import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager;
@@ -119,6 +122,9 @@
     //       one place.
     @GuardedBy("mLock")
     private final SparseArray<List<Session2Token>> mSession2TokensPerUser = new SparseArray<>();
+    @GuardedBy("mLock")
+    private final List<Session2TokensListenerRecord> mSession2TokensListenerRecords =
+            new ArrayList<>();
 
     private KeyguardManager mKeyguardManager;
     private IAudioService mAudioService;
@@ -235,7 +241,7 @@
 
     private List<MediaSessionRecord> getActiveSessionsLocked(int userId) {
         List<MediaSessionRecord> records = new ArrayList<>();
-        if (userId == UserHandle.USER_ALL) {
+        if (userId == USER_ALL) {
             int size = mUserRecords.size();
             for (int i = 0; i < size; i++) {
                 records.addAll(mUserRecords.valueAt(i).mPriorityStack.getActiveSessions(userId));
@@ -251,13 +257,24 @@
 
         // Return global priority session at the first whenever it's asked.
         if (isGlobalPriorityActiveLocked()
-                && (userId == UserHandle.USER_ALL
-                    || userId == mGlobalPrioritySession.getUserId())) {
+                && (userId == USER_ALL || userId == mGlobalPrioritySession.getUserId())) {
             records.add(0, mGlobalPrioritySession);
         }
         return records;
     }
 
+    List<Session2Token> getSession2TokensLocked(int userId) {
+        List<Session2Token> list = new ArrayList<>();
+        if (userId == USER_ALL) {
+            for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
+                list.addAll(mSession2TokensPerUser.valueAt(i));
+            }
+        } else {
+            list.addAll(mSession2TokensPerUser.get(userId));
+        }
+        return list;
+    }
+
     /**
      * Tells the system UI that volume has changed on an active remote session.
      */
@@ -316,7 +333,7 @@
             FullUserRecord user = getFullUserRecordLocked(userId);
             if (user != null) {
                 if (user.mFullUserId == userId) {
-                    user.destroySessionsForUserLocked(UserHandle.USER_ALL);
+                    user.destroySessionsForUserLocked(USER_ALL);
                     mUserRecords.remove(userId);
                 } else {
                     user.destroySessionsForUserLocked(userId);
@@ -393,14 +410,14 @@
             for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
                 SessionsListenerRecord listener = mSessionsListeners.get(i);
                 try {
-                    enforceMediaPermissions(listener.mComponentName, listener.mPid, listener.mUid,
-                            listener.mUserId);
+                    enforceMediaPermissions(listener.componentName, listener.pid, listener.uid,
+                            listener.userId);
                 } catch (SecurityException e) {
-                    Log.i(TAG, "ActiveSessionsListener " + listener.mComponentName
+                    Log.i(TAG, "ActiveSessionsListener " + listener.componentName
                             + " is no longer authorized. Disconnecting.");
                     mSessionsListeners.remove(i);
                     try {
-                        listener.mListener
+                        listener.listener
                                 .onActiveSessionsChanged(new ArrayList<MediaSession.Token>());
                     } catch (Exception e1) {
                         // ignore
@@ -562,13 +579,23 @@
 
     private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
         for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
-            if (mSessionsListeners.get(i).mListener.asBinder() == listener.asBinder()) {
+            if (mSessionsListeners.get(i).listener.asBinder() == listener.asBinder()) {
                 return i;
             }
         }
         return -1;
     }
 
+    private int findIndexOfSession2TokensListenerLocked(ISession2TokensListener listener) {
+        for (int i = mSession2TokensListenerRecords.size() - 1; i >= 0; i--) {
+            if (mSession2TokensListenerRecords.get(i).listener.asBinder() == listener.asBinder()) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+
     private void pushSessionsChanged(int userId) {
         synchronized (mLock) {
             FullUserRecord user = getFullUserRecordLocked(userId);
@@ -585,9 +612,9 @@
             pushRemoteVolumeUpdateLocked(userId);
             for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
                 SessionsListenerRecord record = mSessionsListeners.get(i);
-                if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
+                if (record.userId == USER_ALL || record.userId == userId) {
                     try {
-                        record.mListener.onActiveSessionsChanged(tokens);
+                        record.listener.onActiveSessionsChanged(tokens);
                     } catch (RemoteException e) {
                         Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
                                 e);
@@ -614,6 +641,25 @@
         }
     }
 
+    void pushSession2TokensChangedLocked(int userId) {
+        List<Session2Token> allSession2Tokens = getSession2TokensLocked(USER_ALL);
+        List<Session2Token> session2Tokens = getSession2TokensLocked(userId);
+
+        for (int i = mSession2TokensListenerRecords.size() - 1; i >= 0; i--) {
+            Session2TokensListenerRecord listenerRecord = mSession2TokensListenerRecords.get(i);
+            try {
+                if (listenerRecord.userId == USER_ALL) {
+                    listenerRecord.listener.onSession2TokensChanged(allSession2Tokens);
+                } else if (listenerRecord.userId == userId) {
+                    listenerRecord.listener.onSession2TokensChanged(session2Tokens);
+                }
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to notify Session2Token change. Removing listener.", e);
+                mSession2TokensListenerRecords.remove(i);
+            }
+        }
+    }
+
     /**
      * Called when the media button receiver for the {@param record} is changed.
      *
@@ -855,20 +901,20 @@
     }
 
     final class SessionsListenerRecord implements IBinder.DeathRecipient {
-        private final IActiveSessionsListener mListener;
-        private final ComponentName mComponentName;
-        private final int mUserId;
-        private final int mPid;
-        private final int mUid;
+        public final IActiveSessionsListener listener;
+        public final ComponentName componentName;
+        public final int userId;
+        public final int pid;
+        public final int uid;
 
         public SessionsListenerRecord(IActiveSessionsListener listener,
                 ComponentName componentName,
                 int userId, int pid, int uid) {
-            mListener = listener;
-            mComponentName = componentName;
-            mUserId = userId;
-            mPid = pid;
-            mUid = uid;
+            this.listener = listener;
+            this.componentName = componentName;
+            this.userId = userId;
+            this.pid = pid;
+            this.uid = uid;
         }
 
         @Override
@@ -879,6 +925,24 @@
         }
     }
 
+    final class Session2TokensListenerRecord implements IBinder.DeathRecipient {
+        public final ISession2TokensListener listener;
+        public final int userId;
+
+        Session2TokensListenerRecord(ISession2TokensListener listener,
+                int userId) {
+            this.listener = listener;
+            this.userId = userId;
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (mLock) {
+                mSession2TokensListenerRecords.remove(this);
+            }
+        }
+    }
+
     final class SettingsObserver extends ContentObserver {
         private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(
                 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
@@ -889,7 +953,7 @@
 
         private void observe() {
             mContentResolver.registerContentObserver(mSecureSettingsUri,
-                    false, this, UserHandle.USER_ALL);
+                    false, this, USER_ALL);
         }
 
         @Override
@@ -984,15 +1048,9 @@
                 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
                         true /* allowAll */, true /* requireFull */, "getSession2Tokens",
                         null /* optional packageName */);
-                List<Session2Token> result = new ArrayList<>();
+                List<Session2Token> result;
                 synchronized (mLock) {
-                    if (resolvedUserId == UserHandle.USER_ALL) {
-                        for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
-                            result.addAll(mSession2TokensPerUser.valueAt(i));
-                        }
-                    } else {
-                        result.addAll(mSession2TokensPerUser.get(userId));
-                    }
+                    result = getSession2TokensLocked(resolvedUserId);
                 }
                 return result;
             } finally {
@@ -1038,7 +1096,7 @@
                 if (index != -1) {
                     SessionsListenerRecord record = mSessionsListeners.remove(index);
                     try {
-                        record.mListener.asBinder().unlinkToDeath(record, 0);
+                        record.listener.asBinder().unlinkToDeath(record, 0);
                     } catch (Exception e) {
                         // ignore exceptions, the record is being removed
                     }
@@ -1046,6 +1104,56 @@
             }
         }
 
+        @Override
+        public void addSession2TokensListener(ISession2TokensListener listener,
+                int userId) {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+
+            try {
+                // Check that they can make calls on behalf of the user and get the final user id.
+                int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
+                        true /* allowAll */, true /* requireFull */, "addSession2TokensListener",
+                        null /* optional packageName */);
+                synchronized (mLock) {
+                    int index = findIndexOfSession2TokensListenerLocked(listener);
+                    if (index >= 0) {
+                        Log.w(TAG, "addSession2TokensListener is already added, ignoring");
+                        return;
+                    }
+                    mSession2TokensListenerRecords.add(
+                            new Session2TokensListenerRecord(listener, resolvedUserId));
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void removeSession2TokensListener(ISession2TokensListener listener) {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+
+            try {
+                synchronized (mLock) {
+                    int index = findIndexOfSession2TokensListenerLocked(listener);
+                    if (index >= 0) {
+                        Session2TokensListenerRecord listenerRecord =
+                                mSession2TokensListenerRecords.remove(index);
+                        try {
+                            listenerRecord.listener.asBinder().unlinkToDeath(listenerRecord, 0);
+                        } catch (Exception e) {
+                            // Ignore exception.
+                        }
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
         /**
          * Handles the dispatching of the media button events to one of the
          * registered listeners, or if there was none, broadcast an
@@ -2012,6 +2120,7 @@
             synchronized (mLock) {
                 int userId = UserHandle.getUserId(mToken.getUid());
                 mSession2TokensPerUser.get(userId).add(mToken);
+                pushSession2TokensChangedLocked(userId);
             }
         }
 
@@ -2020,6 +2129,7 @@
             synchronized (mLock) {
                 int userId = UserHandle.getUserId(mToken.getUid());
                 mSession2TokensPerUser.get(userId).remove(mToken);
+                pushSession2TokensChangedLocked(userId);
             }
         }
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 7ecdad2..20eebe7 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1581,6 +1581,9 @@
 
         mIsAutomotive =
                 mPackageManagerClient.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0);
+
+        mPreferencesHelper.lockChannelsForOEM(getContext().getResources().getStringArray(
+                com.android.internal.R.array.config_nonBlockableNotificationPackages));
     }
 
     @Override
@@ -2314,8 +2317,8 @@
 
         @Override
         public boolean areAppOverlaysAllowedForPackage(String pkg, int uid) {
-            checkCallerIsSystemOrSameApp(pkg);
-
+            enforceSystemOrSystemUIOrSamePackage("Caller not system or systemui or same package",
+                    pkg);
             return mPreferencesHelper.areAppOverlaysAllowed(pkg, uid);
         }
 
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 9942f59..5598741 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -91,7 +91,6 @@
 public final class NotificationRecord {
     static final String TAG = "NotificationRecord";
     static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
-    private static final int MAX_LOGTAG_LENGTH = 35;
     // the period after which a notification is updated where it can make sound
     private static final int MAX_SOUND_DELAY_MS = 2000;
     final StatusBarNotification sbn;
@@ -162,10 +161,7 @@
     private ArrayList<String> mPeopleOverride;
     private ArrayList<SnoozeCriterion> mSnoozeCriteria;
     private boolean mShowBadge;
-    private LogMaker mLogMaker;
     private Light mLight;
-    private String mGroupLogTag;
-    private String mChannelIdLogTag;
     /**
      * This list contains system generated smart actions from NAS, app-generated smart actions are
      * stored in Notification.actions with isContextual() set to true.
@@ -789,7 +785,8 @@
             mImportanceExplanation = "user";
         }
         if (!getChannel().hasUserSetImportance()
-                && mAssistantImportance != IMPORTANCE_UNSPECIFIED) {
+                && mAssistantImportance != IMPORTANCE_UNSPECIFIED
+                && !getChannel().isImportanceLockedByOEM()) {
             mImportance = mAssistantImportance;
             mImportanceExplanation = "asst";
         }
@@ -969,33 +966,6 @@
 
     public void setOverrideGroupKey(String overrideGroupKey) {
         sbn.setOverrideGroupKey(overrideGroupKey);
-        mGroupLogTag = null;
-    }
-
-    private String getGroupLogTag() {
-        if (mGroupLogTag == null) {
-            mGroupLogTag = shortenTag(sbn.getGroup());
-        }
-        return mGroupLogTag;
-    }
-
-    private String getChannelIdLogTag() {
-        if (mChannelIdLogTag == null) {
-            mChannelIdLogTag = shortenTag(mChannel.getId());
-        }
-        return mChannelIdLogTag;
-    }
-
-    private String shortenTag(String longTag) {
-        if (longTag == null) {
-            return null;
-        }
-        if (longTag.length() < MAX_LOGTAG_LENGTH) {
-            return longTag;
-        } else {
-            return longTag.substring(0, MAX_LOGTAG_LENGTH - 8) + "-" +
-                    Integer.toHexString(longTag.hashCode());
-        }
     }
 
     public NotificationChannel getChannel() {
@@ -1272,24 +1242,9 @@
     }
 
     public LogMaker getLogMaker(long now) {
-        if (mLogMaker == null) {
-            // initialize fields that only change on update (so a new record)
-            mLogMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN)
-                    .setPackageName(sbn.getPackageName())
-                    .addTaggedData(MetricsEvent.NOTIFICATION_ID, sbn.getId())
-                    .addTaggedData(MetricsEvent.NOTIFICATION_TAG, sbn.getTag())
-                    .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID, getChannelIdLogTag());
-        }
-        // reset fields that can change between updates, or are used by multiple logs
-        return mLogMaker
-                .clearCategory()
-                .clearType()
-                .clearSubtype()
+        return sbn.getLogMaker()
                 .clearTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX)
                 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE, mImportance)
-                .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID, getGroupLogTag())
-                .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_SUMMARY,
-                        sbn.getNotification().isGroupSummary() ? 1 : 0)
                 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS, getLifespanMs(now))
                 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS, getFreshnessMs(now))
                 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS, getExposureMs(now))
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 7c0e0b0..28f6972 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -68,6 +68,7 @@
     private static final String TAG = "NotificationPrefHelper";
     private static final int XML_VERSION = 1;
     private static final int UNKNOWN_UID = UserHandle.USER_NULL;
+    private static final String NON_BLOCKABLE_CHANNEL_DELIM = ":";
 
     @VisibleForTesting
     static final String TAG_RANKING = "ranking";
@@ -94,6 +95,7 @@
     private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
     private static final boolean DEFAULT_SHOW_BADGE = true;
     private static final boolean DEFAULT_ALLOW_APP_OVERLAY = true;
+    private static final boolean DEFAULT_OEM_LOCKED_IMPORTANCE  = false;
     /**
      * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
      * fields.
@@ -621,6 +623,12 @@
             channel.setLockscreenVisibility(r.visibility);
         }
         clearLockedFields(channel);
+        channel.setImportanceLockedByOEM(r.oemLockedImportance);
+        if (!channel.isImportanceLockedByOEM()) {
+            if (r.futureOemLockedChannels.remove(channel.getId())) {
+                channel.setImportanceLockedByOEM(true);
+            }
+        }
         if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
             channel.setLockscreenVisibility(
                     NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
@@ -664,6 +672,12 @@
         } else {
             updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
         }
+        // no importance updates are allowed if OEM blocked it
+        updatedChannel.setImportanceLockedByOEM(channel.isImportanceLockedByOEM());
+        if (updatedChannel.isImportanceLockedByOEM()) {
+            updatedChannel.setImportance(channel.getImportance());
+        }
+
         r.channels.put(updatedChannel.getId(), updatedChannel);
 
         if (onlyHasDefaultChannel(pkg, uid)) {
@@ -753,6 +767,44 @@
         }
     }
 
+    public void lockChannelsForOEM(String[] appOrChannelList) {
+        if (appOrChannelList == null) {
+            return;
+        }
+        for (String appOrChannel : appOrChannelList) {
+            if (!TextUtils.isEmpty(appOrChannel)) {
+                String[] appSplit = appOrChannel.split(NON_BLOCKABLE_CHANNEL_DELIM);
+                if (appSplit != null && appSplit.length > 0) {
+                    String appName = appSplit[0];
+                    String channelId = appSplit.length == 2 ? appSplit[1] : null;
+
+                    synchronized (mPackagePreferences) {
+                        for (PackagePreferences r : mPackagePreferences.values()) {
+                            if (r.pkg.equals(appName)) {
+                                if (channelId == null) {
+                                    // lock all channels for the app
+                                    r.oemLockedImportance = true;
+                                    for (NotificationChannel channel : r.channels.values()) {
+                                        channel.setImportanceLockedByOEM(true);
+                                    }
+                                } else {
+                                    NotificationChannel channel = r.channels.get(channelId);
+                                    if (channel != null) {
+                                        channel.setImportanceLockedByOEM(true);
+                                    } else {
+                                        // if this channel shows up in the future, make sure it'll
+                                        // be locked immediately
+                                        r.futureOemLockedChannels.add(channelId);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
             int uid, String groupId, boolean includeDeleted) {
         Preconditions.checkNotNull(pkg);
@@ -1604,6 +1656,8 @@
         boolean showBadge = DEFAULT_SHOW_BADGE;
         boolean appOverlay = DEFAULT_ALLOW_APP_OVERLAY;
         int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
+        boolean oemLockedImportance = DEFAULT_OEM_LOCKED_IMPORTANCE;
+        List<String> futureOemLockedChannels = new ArrayList<>();
 
         Delegate delegate = null;
         ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index b1e9144..b145e1e 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -106,9 +106,7 @@
 
         synchronized (mProxyByGroupTmp) {
             // record individual ranking result and nominate proxies for each group
-            // Note: iteration is done backwards such that the index can be used as a sort key
-            // in a string compare below
-            for (int i = N - 1; i >= 0; i--) {
+            for (int i = 0; i < N; i++) {
                 final NotificationRecord record = notificationList.get(i);
                 record.setAuthoritativeRank(i);
                 final String groupKey = record.getGroupKey();
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 7f1fb6c..eb3017d 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -24,6 +24,7 @@
 import android.app.AppGlobals;
 import android.app.IApplicationThread;
 import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -115,6 +116,7 @@
         private final ShortcutServiceInternal mShortcutServiceInternal;
         private final PackageCallbackList<IOnAppsChangedListener> mListeners
                 = new PackageCallbackList<IOnAppsChangedListener>();
+        private final DevicePolicyManager mDpm;
 
         private final MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
 
@@ -133,6 +135,7 @@
                     LocalServices.getService(ShortcutServiceInternal.class));
             mShortcutServiceInternal.addListener(mPackageMonitor);
             mCallbackHandler = BackgroundThread.getHandler();
+            mDpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
         }
 
         @VisibleForTesting
@@ -313,30 +316,42 @@
                     Settings.Global.SHOW_HIDDEN_LAUNCHER_ICON_APPS_ENABLED, 1) == 0) {
                 return launcherActivities;
             }
-
-            final int callingUid = injectBinderCallingUid();
-            final ArrayList<ResolveInfo> result = new ArrayList<>(launcherActivities.getList());
-            final PackageManagerInternal pmInt =
-                    LocalServices.getService(PackageManagerInternal.class);
-            if (packageName != null) {
-                // If this hidden app should not be shown, return the original list.
-                // Otherwise, inject hidden activity that forwards user to app details page.
-                if (result.size() > 0) {
-                    return launcherActivities;
-                }
-                ApplicationInfo appInfo = pmInt.getApplicationInfo(packageName, /*flags*/ 0,
-                        callingUid, user.getIdentifier());
-                if (shouldShowHiddenApp(appInfo)) {
-                    ResolveInfo info = getHiddenAppActivityInfo(packageName, callingUid, user);
-                    if (info != null) {
-                        result.add(info);
-                    }
-                }
-                return new ParceledListSlice<>(result);
+            if (launcherActivities == null) {
+                // Cannot access profile, so we don't even return any hidden apps.
+                return null;
             }
 
-            long ident = injectClearCallingIdentity();
+            final int callingUid = injectBinderCallingUid();
+            final long ident = injectClearCallingIdentity();
             try {
+                if (mUm.getUserInfo(user.getIdentifier()).isManagedProfile()) {
+                    // Managed profile should not show hidden apps
+                    return launcherActivities;
+                }
+                if (mDpm.getDeviceOwnerComponentOnAnyUser() != null) {
+                    // Device owner devices should not show hidden apps
+                    return launcherActivities;
+                }
+
+                final ArrayList<ResolveInfo> result = new ArrayList<>(launcherActivities.getList());
+                final PackageManagerInternal pmInt =
+                        LocalServices.getService(PackageManagerInternal.class);
+                if (packageName != null) {
+                    // If this hidden app should not be shown, return the original list.
+                    // Otherwise, inject hidden activity that forwards user to app details page.
+                    if (result.size() > 0) {
+                        return launcherActivities;
+                    }
+                    ApplicationInfo appInfo = pmInt.getApplicationInfo(packageName, /*flags*/ 0,
+                            callingUid, user.getIdentifier());
+                    if (shouldShowHiddenApp(appInfo)) {
+                        ResolveInfo info = getHiddenAppActivityInfo(packageName, callingUid, user);
+                        if (info != null) {
+                            result.add(info);
+                        }
+                    }
+                    return new ParceledListSlice<>(result);
+                }
                 final HashSet<String> visiblePackages = new HashSet<>();
                 for (ResolveInfo info : result) {
                     visiblePackages.add(info.activityInfo.packageName);
diff --git a/services/core/java/com/android/server/rollback/PackageRollbackData.java b/services/core/java/com/android/server/rollback/RollbackData.java
similarity index 65%
rename from services/core/java/com/android/server/rollback/PackageRollbackData.java
rename to services/core/java/com/android/server/rollback/RollbackData.java
index 15d1242..f0589aa 100644
--- a/services/core/java/com/android/server/rollback/PackageRollbackData.java
+++ b/services/core/java/com/android/server/rollback/RollbackData.java
@@ -20,17 +20,21 @@
 
 import java.io.File;
 import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
- * Information about a rollback available for a particular package.
- * This is similar to {@link PackageRollbackInfo}, but extended with
- * additional information for internal bookkeeping.
+ * Information about a rollback available for a set of atomically installed
+ * packages.
  */
-class PackageRollbackData {
-    public final PackageRollbackInfo info;
+class RollbackData {
+    /**
+     * The per-package rollback information.
+     */
+    public final List<PackageRollbackInfo> packages = new ArrayList<>();
 
     /**
-     * The directory where the apk backup is stored.
+     * The directory where the rollback data is stored.
      */
     public final File backupDir;
 
@@ -38,12 +42,9 @@
      * The time when the upgrade occurred, for purposes of expiring
      * rollback data.
      */
-    public final Instant timestamp;
+    public Instant timestamp;
 
-    PackageRollbackData(PackageRollbackInfo info,
-            File backupDir, Instant timestamp) {
-        this.info = info;
+    RollbackData(File backupDir) {
         this.backupDir = backupDir;
-        this.timestamp = timestamp;
     }
 }
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 0c21312..8021265 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -63,9 +63,11 @@
 import java.time.format.DateTimeParseException;
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
@@ -86,10 +88,20 @@
     // mLock is held when they are called.
     private final Object mLock = new Object();
 
+    // Package rollback data for rollback-enabled installs that have not yet
+    // been committed. Maps from sessionId to rollback data.
+    @GuardedBy("mLock")
+    private final Map<Integer, RollbackData> mPendingRollbacks = new HashMap<>();
+
+    // Map from child session id's for enabled rollbacks to their
+    // corresponding parent session ids.
+    @GuardedBy("mLock")
+    private final Map<Integer, Integer> mChildSessions = new HashMap<>();
+
     // Package rollback data available to be used for rolling back a package.
     // This list is null until the rollback data has been loaded.
     @GuardedBy("mLock")
-    private List<PackageRollbackData> mAvailableRollbacks;
+    private List<RollbackData> mAvailableRollbacks;
 
     // The list of recently executed rollbacks.
     // This list is null until the rollback data has been loaded.
@@ -102,14 +114,25 @@
     // to store this data:
     //   /data/rollback/
     //      available/
-    //          com.package.A-XXX/
-    //              base.apk
-    //              rollback.json
-    //          com.package.B-YYY/
-    //              base.apk
-    //              rollback.json
+    //          XXX/
+    //              com.package.A/
+    //                  base.apk
+    //                  info.json
+    //              enabled.txt
+    //          YYY/
+    //              com.package.B/
+    //                  base.apk
+    //                  info.json
+    //              enabled.txt
     //      recently_executed.json
-    // TODO: Use AtomicFile for rollback.json and recently_executed.json.
+    //
+    // * XXX, YYY are random strings from Files.createTempDirectory
+    // * info.json contains the package version to roll back from/to.
+    // * enabled.txt contains a timestamp for when the rollback was first
+    //   made available. This file is not written until the rollback is made
+    //   available.
+    //
+    // TODO: Use AtomicFile for all the .json files?
     private final File mRollbackDataDir;
     private final File mAvailableRollbacksDir;
     private final File mRecentlyExecutedRollbacksFile;
@@ -135,6 +158,9 @@
         // expiration.
         getHandler().post(() -> ensureRollbackDataLoaded());
 
+        PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
+        installer.registerSessionCallback(new SessionCallback(), getHandler());
+
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
         filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
@@ -199,27 +225,23 @@
                 android.Manifest.permission.MANAGE_ROLLBACKS,
                 "getAvailableRollback");
 
-        PackageRollbackInfo.PackageVersion installedVersion =
-                getInstalledPackageVersion(packageName);
-        if (installedVersion == null) {
+        RollbackData data = getRollbackForPackage(packageName);
+        if (data == null) {
             return null;
         }
 
-        synchronized (mLock) {
-            // TODO: Have ensureRollbackDataLoadedLocked return the list of
-            // available rollbacks, to hopefully avoid forgetting to call it?
-            ensureRollbackDataLoadedLocked();
-            for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
-                PackageRollbackData data = mAvailableRollbacks.get(i);
-                if (data.info.packageName.equals(packageName)
-                        && data.info.higherVersion.equals(installedVersion)) {
-                    // TODO: For atomic installs, check all dependent packages
-                    // for available rollbacks and include that info here.
-                    return new RollbackInfo(data.info);
-                }
+        // Note: The rollback for the package ought to be for the currently
+        // installed version, otherwise the rollback data is out of date. In
+        // that rare case, we'll check when we execute the rollback whether
+        // it's out of date or not, so no need to check package versions here.
+
+        for (PackageRollbackInfo info : data.packages) {
+            if (info.packageName.equals(packageName)) {
+                // TODO: Once the RollbackInfo API supports info about
+                // dependant packages, add that info here.
+                return new RollbackInfo(info);
             }
         }
-
         return null;
     }
 
@@ -229,16 +251,14 @@
                 android.Manifest.permission.MANAGE_ROLLBACKS,
                 "getPackagesWithAvailableRollbacks");
 
-        // TODO: This may return packages whose rollback is out of date or
-        // expired.  Presumably that's okay because the package rollback could
-        // be expired anyway between when the caller calls this method and
-        // when the caller calls getAvailableRollback for more details.
         final Set<String> packageNames = new HashSet<>();
         synchronized (mLock) {
             ensureRollbackDataLoadedLocked();
             for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
-                PackageRollbackData data = mAvailableRollbacks.get(i);
-                packageNames.add(data.info.packageName);
+                RollbackData data = mAvailableRollbacks.get(i);
+                for (PackageRollbackInfo info : data.packages) {
+                    packageNames.add(info.packageName);
+                }
             }
         }
         return new StringParceledListSlice(new ArrayList<>(packageNames));
@@ -279,47 +299,49 @@
      */
     private void executeRollbackInternal(RollbackInfo rollback,
             String callerPackageName, IntentSender statusReceiver) {
-        String packageName = rollback.targetPackage.packageName;
-        Log.i(TAG, "Initiating rollback of " + packageName);
+        String targetPackageName = rollback.targetPackage.packageName;
+        Log.i(TAG, "Initiating rollback of " + targetPackageName);
 
-        PackageRollbackInfo.PackageVersion installedVersion =
-                getInstalledPackageVersion(packageName);
-        if (installedVersion == null) {
-            // TODO: Test this case
-            sendFailure(statusReceiver, "Target package to roll back is not installed");
+        // Get the latest RollbackData for the target package.
+        RollbackData data = getRollbackForPackage(targetPackageName);
+        if (data == null) {
+            sendFailure(statusReceiver, "No rollback available for package.");
             return;
         }
 
-        if (!rollback.targetPackage.higherVersion.equals(installedVersion)) {
-            // TODO: Test this case
-            sendFailure(statusReceiver, "Target package version to roll back not installed.");
-            return;
+        // Verify the latest rollback matches the version requested.
+        // TODO: Check dependant packages too once RollbackInfo includes that
+        // information.
+        for (PackageRollbackInfo info : data.packages) {
+            if (info.packageName.equals(targetPackageName)
+                    && !rollback.targetPackage.higherVersion.equals(info.higherVersion)) {
+                sendFailure(statusReceiver, "Rollback is out of date.");
+                return;
+            }
         }
 
+        // Verify the RollbackData is up to date with what's installed on
+        // device.
         // TODO: We assume that between now and the time we commit the
         // downgrade install, the currently installed package version does not
         // change. This is not safe to assume, particularly in the case of a
         // rollback racing with a roll-forward fix of a buggy package.
         // Figure out how to ensure we don't commit the rollback if
         // roll forward happens at the same time.
-        PackageRollbackData data = null;
-        synchronized (mLock) {
-            ensureRollbackDataLoadedLocked();
-            for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
-                PackageRollbackData available = mAvailableRollbacks.get(i);
-                // TODO: Check if available.info.lowerVersion matches
-                // rollback.targetPackage.lowerVersion?
-                if (available.info.packageName.equals(packageName)
-                        && available.info.higherVersion.equals(installedVersion)) {
-                    data = available;
-                    break;
-                }
+        for (PackageRollbackInfo info : data.packages) {
+            PackageRollbackInfo.PackageVersion installedVersion =
+                    getInstalledPackageVersion(info.packageName);
+            if (installedVersion == null) {
+                // TODO: Test this case
+                sendFailure(statusReceiver, "Package to roll back is not installed");
+                return;
             }
-        }
 
-        if (data == null) {
-            sendFailure(statusReceiver, "Rollback not available");
-            return;
+            if (!info.higherVersion.equals(installedVersion)) {
+                // TODO: Test this case
+                sendFailure(statusReceiver, "Package version to roll back not installed.");
+                return;
+            }
         }
 
         // Get a context for the caller to use to install the downgraded
@@ -334,29 +356,39 @@
 
         PackageManager pm = context.getPackageManager();
         try {
-            PackageInstaller.Session session = null;
-
             PackageInstaller packageInstaller = pm.getPackageInstaller();
-            PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+            PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
                     PackageInstaller.SessionParams.MODE_FULL_INSTALL);
-            params.setAllowDowngrade(true);
-            int sessionId = packageInstaller.createSession(params);
-            session = packageInstaller.openSession(sessionId);
+            parentParams.setAllowDowngrade(true);
+            parentParams.setMultiPackage();
+            int parentSessionId = packageInstaller.createSession(parentParams);
+            PackageInstaller.Session parentSession = packageInstaller.openSession(parentSessionId);
 
-            // TODO: Will it always be called "base.apk"? What about splits?
-            File baseApk = new File(data.backupDir, "base.apk");
-            try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(baseApk,
-                    ParcelFileDescriptor.MODE_READ_ONLY)) {
-                final long token = Binder.clearCallingIdentity();
-                try {
-                    session.write("base.apk", 0, baseApk.length(), fd);
-                } finally {
-                    Binder.restoreCallingIdentity(token);
+            for (PackageRollbackInfo info : data.packages) {
+                PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+                        PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+                params.setAllowDowngrade(true);
+                int sessionId = packageInstaller.createSession(params);
+                PackageInstaller.Session session = packageInstaller.openSession(sessionId);
+
+                // TODO: Will it always be called "base.apk"? What about splits?
+                // What about apex?
+                File packageDir = new File(data.backupDir, info.packageName);
+                File baseApk = new File(packageDir, "base.apk");
+                try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(baseApk,
+                        ParcelFileDescriptor.MODE_READ_ONLY)) {
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        session.write("base.apk", 0, baseApk.length(), fd);
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
                 }
+                parentSession.addChildSessionId(sessionId);
             }
 
             final LocalIntentReceiver receiver = new LocalIntentReceiver();
-            session.commit(receiver.getIntentSender());
+            parentSession.commit(receiver.getIntentSender());
 
             Intent result = receiver.getResult();
             int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
@@ -371,13 +403,14 @@
             sendSuccess(statusReceiver);
 
             Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED,
-                    Uri.fromParts("package", packageName, Manifest.permission.MANAGE_ROLLBACKS));
+                    Uri.fromParts("package", targetPackageName,
+                        Manifest.permission.MANAGE_ROLLBACKS));
 
             // TODO: This call emits the warning "Calling a method in the
             // system process without a qualified user". Fix that.
             mContext.sendBroadcast(broadcast);
         } catch (IOException e) {
-            Log.e(TAG, "Unable to roll back " + packageName, e);
+            Log.e(TAG, "Unable to roll back " + targetPackageName, e);
             sendFailure(statusReceiver, "IOException: " + e.toString());
             return;
         }
@@ -408,12 +441,15 @@
         // testing anyway.
         synchronized (mLock) {
             ensureRollbackDataLoadedLocked();
-            Iterator<PackageRollbackData> iter = mAvailableRollbacks.iterator();
+            Iterator<RollbackData> iter = mAvailableRollbacks.iterator();
             while (iter.hasNext()) {
-                PackageRollbackData data = iter.next();
-                if (data.info.packageName.equals(packageName)) {
-                    iter.remove();
-                    removeFile(data.backupDir);
+                RollbackData data = iter.next();
+                for (PackageRollbackInfo info : data.packages) {
+                    if (info.packageName.equals(packageName)) {
+                        iter.remove();
+                        removeFile(data.backupDir);
+                        break;
+                    }
                 }
             }
         }
@@ -453,27 +489,41 @@
         mAvailableRollbacksDir.mkdirs();
         mAvailableRollbacks = new ArrayList<>();
         for (File rollbackDir : mAvailableRollbacksDir.listFiles()) {
-            if (rollbackDir.isDirectory()) {
-                // TODO: How to detect and clean up an invalid rollback
-                // directory? We don't know if it's invalid because something
-                // went wrong, or if it's only temporarily invalid because
-                // it's in the process of being created.
+            File enabledFile = new File(rollbackDir, "enabled.txt");
+            // TODO: Delete any directories without an enabled.txt? That could
+            // potentially delete pending rollback data if reloadPersistedData
+            // is called, though there's no reason besides testing for that to
+            // be called.
+            if (rollbackDir.isDirectory() && enabledFile.isFile()) {
+                RollbackData data = new RollbackData(rollbackDir);
                 try {
-                    File jsonFile = new File(rollbackDir, "rollback.json");
-                    String jsonString = IoUtils.readFileAsString(jsonFile.getAbsolutePath());
-                    JSONObject jsonObject = new JSONObject(jsonString);
-                    String packageName = jsonObject.getString("packageName");
-                    long higherVersionCode = jsonObject.getLong("higherVersionCode");
-                    long lowerVersionCode = jsonObject.getLong("lowerVersionCode");
-                    Instant timestamp = Instant.parse(jsonObject.getString("timestamp"));
-                    PackageRollbackData data = new PackageRollbackData(
-                            new PackageRollbackInfo(packageName,
-                                new PackageRollbackInfo.PackageVersion(higherVersionCode),
-                                new PackageRollbackInfo.PackageVersion(lowerVersionCode)),
-                            rollbackDir, timestamp);
+                    PackageRollbackInfo info = null;
+                    for (File packageDir : rollbackDir.listFiles()) {
+                        if (packageDir.isDirectory()) {
+                            File jsonFile = new File(packageDir, "info.json");
+                            String jsonString = IoUtils.readFileAsString(
+                                    jsonFile.getAbsolutePath());
+                            JSONObject jsonObject = new JSONObject(jsonString);
+                            String packageName = jsonObject.getString("packageName");
+                            long higherVersionCode = jsonObject.getLong("higherVersionCode");
+                            long lowerVersionCode = jsonObject.getLong("lowerVersionCode");
+
+                            data.packages.add(new PackageRollbackInfo(packageName,
+                                        new PackageRollbackInfo.PackageVersion(higherVersionCode),
+                                        new PackageRollbackInfo.PackageVersion(lowerVersionCode)));
+                        }
+                    }
+
+                    if (data.packages.isEmpty()) {
+                        throw new IOException("No package rollback info found");
+                    }
+
+                    String enabledString = IoUtils.readFileAsString(enabledFile.getAbsolutePath());
+                    data.timestamp = Instant.parse(enabledString.trim());
                     mAvailableRollbacks.add(data);
                 } catch (IOException | JSONException | DateTimeParseException e) {
                     Log.e(TAG, "Unable to read rollback data at " + rollbackDir, e);
+                    removeFile(rollbackDir);
                 }
             }
         }
@@ -521,13 +571,16 @@
 
         synchronized (mLock) {
             ensureRollbackDataLoadedLocked();
-            Iterator<PackageRollbackData> iter = mAvailableRollbacks.iterator();
+            Iterator<RollbackData> iter = mAvailableRollbacks.iterator();
             while (iter.hasNext()) {
-                PackageRollbackData data = iter.next();
-                if (data.info.packageName.equals(packageName)
-                        && !data.info.higherVersion.equals(installedVersion)) {
-                    iter.remove();
-                    removeFile(data.backupDir);
+                RollbackData data = iter.next();
+                for (PackageRollbackInfo info : data.packages) {
+                    if (info.packageName.equals(packageName)
+                            && !info.higherVersion.equals(installedVersion)) {
+                        iter.remove();
+                        removeFile(data.backupDir);
+                        break;
+                    }
                 }
             }
         }
@@ -643,9 +696,9 @@
         synchronized (mLock) {
             ensureRollbackDataLoadedLocked();
 
-            Iterator<PackageRollbackData> iter = mAvailableRollbacks.iterator();
+            Iterator<RollbackData> iter = mAvailableRollbacks.iterator();
             while (iter.hasNext()) {
-                PackageRollbackData data = iter.next();
+                RollbackData data = iter.next();
                 if (!now.isBefore(data.timestamp.plusMillis(ROLLBACK_LIFETIME_DURATION_MILLIS))) {
                     iter.remove();
                     removeFile(data.backupDir);
@@ -673,6 +726,23 @@
         return mHandlerThread.getThreadHandler();
     }
 
+    // Returns true if <code>session</code> has installFlags and code path
+    // matching the installFlags and new package code path given to
+    // enableRollback.
+    private boolean sessionMatchesForEnableRollback(PackageInstaller.SessionInfo session,
+            int installFlags, File newPackageCodePath) {
+        if (session == null || session.resolvedBaseCodePath == null) {
+            return false;
+        }
+
+        File packageCodePath = new File(session.resolvedBaseCodePath).getParentFile();
+        if (newPackageCodePath.equals(packageCodePath) && installFlags == session.installFlags) {
+            return true;
+        }
+
+        return false;
+    }
+
     /**
      * Called via broadcast by the package manager when a package is being
      * staged for install with rollback enabled. Called before the package has
@@ -705,6 +775,34 @@
         String packageName = newPackage.packageName;
         Log.i(TAG, "Enabling rollback for install of " + packageName);
 
+        // Figure out the session id associated with this install.
+        int parentSessionId = PackageInstaller.SessionInfo.INVALID_ID;
+        int childSessionId = PackageInstaller.SessionInfo.INVALID_ID;
+        PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
+        for (PackageInstaller.SessionInfo info : installer.getAllSessions()) {
+            if (info.isMultiPackage()) {
+                for (int childId : info.getChildSessionIds()) {
+                    PackageInstaller.SessionInfo child = installer.getSessionInfo(childId);
+                    if (sessionMatchesForEnableRollback(child, installFlags, newPackageCodePath)) {
+                        // TODO: Check we only have one matching session?
+                        parentSessionId = info.getSessionId();
+                        childSessionId = childId;
+                    }
+                }
+            } else {
+                if (sessionMatchesForEnableRollback(info, installFlags, newPackageCodePath)) {
+                    // TODO: Check we only have one matching session?
+                    parentSessionId = info.getSessionId();
+                    childSessionId = parentSessionId;
+                }
+            }
+        }
+
+        if (parentSessionId == PackageInstaller.SessionInfo.INVALID_ID) {
+            Log.e(TAG, "Unable to find session id for enabled rollback.");
+            return false;
+        }
+
         PackageRollbackInfo.PackageVersion newVersion =
                 new PackageRollbackInfo.PackageVersion(newPackage.versionCode);
 
@@ -720,52 +818,53 @@
         PackageRollbackInfo.PackageVersion installedVersion =
                 new PackageRollbackInfo.PackageVersion(installedPackage.getLongVersionCode());
 
-        File backupDir;
+        PackageRollbackInfo info = new PackageRollbackInfo(
+                packageName, newVersion, installedVersion);
+
+        RollbackData data;
         try {
-            backupDir = Files.createTempDirectory(
-                mAvailableRollbacksDir.toPath(), packageName + "-").toFile();
+            synchronized (mLock) {
+                mChildSessions.put(childSessionId, parentSessionId);
+                data = mPendingRollbacks.get(parentSessionId);
+                if (data == null) {
+                    File backupDir = Files.createTempDirectory(
+                            mAvailableRollbacksDir.toPath(), null).toFile();
+                    data = new RollbackData(backupDir);
+                    mPendingRollbacks.put(parentSessionId, data);
+                }
+                data.packages.add(info);
+            }
         } catch (IOException e) {
             Log.e(TAG, "Unable to create rollback for " + packageName, e);
             return false;
         }
 
-        // TODO: Should the timestamp be for when we commit the install, not
-        // when we create the pending one?
-        Instant timestamp = Instant.now();
+        File packageDir = new File(data.backupDir, packageName);
+        packageDir.mkdirs();
         try {
             JSONObject json = new JSONObject();
             json.put("packageName", packageName);
             json.put("higherVersionCode", newVersion.versionCode);
             json.put("lowerVersionCode", installedVersion.versionCode);
-            json.put("timestamp", timestamp.toString());
 
-            File jsonFile = new File(backupDir, "rollback.json");
+            File jsonFile = new File(packageDir, "info.json");
             PrintWriter pw = new PrintWriter(jsonFile);
             pw.println(json.toString());
             pw.close();
         } catch (IOException | JSONException e) {
             Log.e(TAG, "Unable to create rollback for " + packageName, e);
-            removeFile(backupDir);
+            removeFile(packageDir);
             return false;
         }
 
         // TODO: Copy by hard link instead to save on cpu and storage space?
-        int status = PackageManagerServiceUtils.copyPackage(installedPackage.codePath, backupDir);
+        int status = PackageManagerServiceUtils.copyPackage(installedPackage.codePath, packageDir);
         if (status != PackageManager.INSTALL_SUCCEEDED) {
             Log.e(TAG, "Unable to copy package for rollback for " + packageName);
-            removeFile(backupDir);
+            removeFile(packageDir);
             return false;
         }
 
-        PackageRollbackData data = new PackageRollbackData(
-                new PackageRollbackInfo(packageName, newVersion, installedVersion),
-                backupDir, timestamp);
-
-        synchronized (mLock) {
-            ensureRollbackDataLoadedLocked();
-            mAvailableRollbacks.add(data);
-        }
-
         return true;
     }
 
@@ -829,4 +928,87 @@
 
         return new PackageRollbackInfo.PackageVersion(pkgInfo.getLongVersionCode());
     }
+
+    private class SessionCallback extends PackageInstaller.SessionCallback {
+
+        @Override
+        public void onCreated(int sessionId) { }
+
+        @Override
+        public void onBadgingChanged(int sessionId) { }
+
+        @Override
+        public void onActiveChanged(int sessionId, boolean active) { }
+
+        @Override
+        public void onProgressChanged(int sessionId, float progress) { }
+
+        @Override
+        public void onFinished(int sessionId, boolean success) {
+            RollbackData data = null;
+            synchronized (mLock) {
+                Integer parentSessionId = mChildSessions.remove(sessionId);
+                if (parentSessionId != null) {
+                    sessionId = parentSessionId;
+                }
+                data = mPendingRollbacks.remove(sessionId);
+            }
+
+            if (data != null) {
+                if (success) {
+                    try {
+                        data.timestamp = Instant.now();
+                        File enabledFile = new File(data.backupDir, "enabled.txt");
+                        PrintWriter pw = new PrintWriter(enabledFile);
+                        pw.println(data.timestamp.toString());
+                        pw.close();
+
+                        synchronized (mLock) {
+                            // Note: There is a small window of time between when
+                            // the session has been committed by the package
+                            // manager and when we make the rollback available
+                            // here. Presumably the window is small enough that
+                            // nobody will want to roll back the newly installed
+                            // package before we make the rollback available.
+                            // TODO: We'll lose the rollback data if the
+                            // device reboots between when the session is
+                            // committed and this point. Revisit this after
+                            // adding support for rollback of staged installs.
+                            ensureRollbackDataLoadedLocked();
+                            mAvailableRollbacks.add(data);
+                        }
+                    } catch (IOException e) {
+                        Log.e(TAG, "Unable to enable rollback", e);
+                        removeFile(data.backupDir);
+                    }
+                } else {
+                    // The install session was aborted, clean up the pending
+                    // install.
+                    removeFile(data.backupDir);
+                }
+            }
+        }
+    }
+
+    /*
+     * Returns the RollbackData, if any, for an available rollback that would
+     * roll back the given package. Note: This assumes we have at most one
+     * available rollback for a given package at any one time.
+     */
+    private RollbackData getRollbackForPackage(String packageName) {
+        synchronized (mLock) {
+            // TODO: Have ensureRollbackDataLoadedLocked return the list of
+            // available rollbacks, to hopefully avoid forgetting to call it?
+            ensureRollbackDataLoadedLocked();
+            for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
+                RollbackData data = mAvailableRollbacks.get(i);
+                for (PackageRollbackInfo info : data.packages) {
+                    if (info.packageName.equals(packageName)) {
+                        return data;
+                    }
+                }
+            }
+        }
+        return null;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f8f0d1c..9f8af50 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -54,6 +54,7 @@
 import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
 import static android.content.pm.ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
 import static android.content.pm.ActivityInfo.FLAG_IMMERSIVE;
+import static android.content.pm.ActivityInfo.FLAG_INHERIT_SHOW_WHEN_LOCKED;
 import static android.content.pm.ActivityInfo.FLAG_MULTIPROCESS;
 import static android.content.pm.ActivityInfo.FLAG_NO_HISTORY;
 import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
@@ -142,6 +143,7 @@
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager.TaskDescription;
 import android.app.ActivityOptions;
 import android.app.PendingIntent;
@@ -387,6 +389,7 @@
     int mRotationAnimationHint = -1;
 
     private boolean mShowWhenLocked;
+    private boolean mInheritShownWhenLocked;
     private boolean mTurnScreenOn;
 
     /**
@@ -1002,6 +1005,7 @@
                 null : ComponentName.unflattenFromString(aInfo.requestedVrComponent);
 
         mShowWhenLocked = (aInfo.flags & FLAG_SHOW_WHEN_LOCKED) != 0;
+        mInheritShownWhenLocked = (aInfo.privateFlags & FLAG_INHERIT_SHOW_WHEN_LOCKED) != 0;
         mTurnScreenOn = (aInfo.flags & FLAG_TURN_SCREEN_ON) != 0;
 
         mRotationAnimationHint = aInfo.rotationAnimation;
@@ -1487,14 +1491,6 @@
         return true;
     }
 
-    /**
-     * @return true if the activity contains windows that have
-     *         {@link LayoutParams#FLAG_DISMISS_KEYGUARD} set
-     */
-    boolean hasDismissKeyguardWindows() {
-        return mAtmService.mWindowManager.containsDismissKeyguardWindow(appToken);
-    }
-
     void makeFinishingLocked() {
         if (finishing) {
             return;
@@ -3224,16 +3220,45 @@
                 false /* preserveWindows */);
     }
 
+    void setInheritShowWhenLocked(boolean inheritShowWhenLocked) {
+        mInheritShownWhenLocked = inheritShowWhenLocked;
+        mRootActivityContainer.ensureActivitiesVisible(null, 0, false);
+    }
+
     /**
      * @return true if the activity windowing mode is not
-     *         {@link android.app.WindowConfiguration#WINDOWING_MODE_PINNED} and activity contains
-     *         windows that have {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} set or if the activity
-     *         has set {@link #mShowWhenLocked}.
+     *         {@link android.app.WindowConfiguration#WINDOWING_MODE_PINNED} and a) activity
+     *         contains windows that have {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} set or if the
+     *         activity has set {@link #mShowWhenLocked}, or b) if the activity has set
+     *         {@link #mInheritShownWhenLocked} and the activity behind this satisfies the
+     *         conditions a) above.
      *         Multi-windowing mode will be exited if true is returned.
      */
     boolean canShowWhenLocked() {
-        return !inPinnedWindowingMode() && (mShowWhenLocked
-                || mAtmService.mWindowManager.containsShowWhenLockedWindow(appToken));
+        if (!inPinnedWindowingMode() && (mShowWhenLocked
+                || (mAppWindowToken != null && mAppWindowToken.containsShowWhenLockedWindow()))) {
+            return true;
+        } else if (mInheritShownWhenLocked) {
+            ActivityRecord r = getActivityBelow();
+            return r != null && !r.inPinnedWindowingMode() && (r.mShowWhenLocked
+                    || (r.mAppWindowToken != null
+                        && r.mAppWindowToken.containsShowWhenLockedWindow()));
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * @return an {@link ActivityRecord} of the activity below this activity, or {@code null} if no
+     * such activity exists.
+     */
+    @Nullable
+    private ActivityRecord getActivityBelow() {
+        final int pos = task.mActivities.indexOf(this);
+        if (pos == -1) {
+            throw new IllegalStateException("Activity not found in its task");
+        }
+        return pos == 0 ? null : task.getChildAt(pos - 1);
     }
 
     void setTurnScreenOn(boolean turnScreenOn) {
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 4581a0f..4d7de905 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -2204,7 +2204,8 @@
                 .isKeyguardOrAodShowing(displayId);
         final boolean keyguardLocked = mStackSupervisor.getKeyguardController().isKeyguardLocked();
         final boolean showWhenLocked = r.canShowWhenLocked();
-        final boolean dismissKeyguard = r.hasDismissKeyguardWindows();
+        final boolean dismissKeyguard = r.mAppWindowToken != null
+                && r.mAppWindowToken.containsDismissKeyguardWindow();
         if (shouldBeVisible) {
             if (dismissKeyguard && mTopDismissingKeyguardActivity == null) {
                 mTopDismissingKeyguardActivity = r;
@@ -2561,7 +2562,9 @@
                 final boolean canShowWhenLocked = !mTopActivityOccludesKeyguard
                         && next.canShowWhenLocked();
                 final boolean mayDismissKeyguard = mTopDismissingKeyguardActivity != next
-                        && next.hasDismissKeyguardWindows();
+                        && next.mAppWindowToken != null
+                        && next.mAppWindowToken.containsDismissKeyguardWindow();
+
                 if (canShowWhenLocked || mayDismissKeyguard) {
                     ensureActivitiesVisibleLocked(null /* starting */, 0 /* configChanges */,
                             !PRESERVE_WINDOWS);
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index b100ecd..a207354 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -945,7 +945,7 @@
     /** Returns true if uid has a visible window or its process is in a top state. */
     private boolean isUidForeground(int uid) {
         return (mService.getUidStateLocked(uid) == ActivityManager.PROCESS_STATE_TOP)
-            || mService.mWindowManager.isAnyWindowVisibleForUid(uid);
+            || mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(uid);
     }
 
     /** Returns true if uid is in a persistent state. */
@@ -968,18 +968,19 @@
             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "logActivityStart");
             final int callingUidProcState = mService.getUidStateLocked(callingUid);
             final boolean callingUidHasAnyVisibleWindow =
-                    mService.mWindowManager.isAnyWindowVisibleForUid(callingUid);
+                    mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(callingUid);
             final int realCallingUidProcState = (callingUid == realCallingUid)
                     ? callingUidProcState
                     : mService.getUidStateLocked(realCallingUid);
             final boolean realCallingUidHasAnyVisibleWindow = (callingUid == realCallingUid)
                     ? callingUidHasAnyVisibleWindow
-                    : mService.mWindowManager.isAnyWindowVisibleForUid(realCallingUid);
+                    : mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(
+                            realCallingUid);
             final String targetPackage = (r != null) ? r.packageName : null;
             final int targetUid = (r!= null) ? ((r.appInfo != null) ? r.appInfo.uid : -1) : -1;
             final int targetUidProcState = mService.getUidStateLocked(targetUid);
             final boolean targetUidHasAnyVisibleWindow = (targetUid != -1)
-                    ? mService.mWindowManager.isAnyWindowVisibleForUid(targetUid)
+                    ? mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(targetUid)
                     : false;
             final String targetWhitelistTag = (targetUid != -1)
                     ? mService.getPendingTempWhitelistTagForUidLocked(targetUid)
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index e5bf21a..e82e748 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4334,6 +4334,22 @@
     }
 
     @Override
+    public void setInheritShowWhenLocked(IBinder token, boolean inheritShowWhenLocked) {
+        synchronized (mGlobalLock) {
+            final ActivityRecord r = ActivityRecord.isInStackLocked(token);
+            if (r == null) {
+                return;
+            }
+            final long origId = Binder.clearCallingIdentity();
+            try {
+                r.setInheritShowWhenLocked(inheritShowWhenLocked);
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+
+    @Override
     public void setTurnScreenOn(IBinder token, boolean turnScreenOn) {
         synchronized (mGlobalLock) {
             final ActivityRecord r = ActivityRecord.isInStackLocked(token);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 4e70bbc..8fb7947 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -23,6 +23,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE;
 import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -280,6 +281,15 @@
     }
 
     /**
+     * Returns true if the callingUid has any non-toast window currently visible to the user.
+     */
+    boolean isAnyNonToastWindowVisibleForUid(int callingUid) {
+        return forAllWindows(w -> {
+            return w.getOwningUid() == callingUid && w.isVisible() && w.mAttrs.type != TYPE_TOAST;
+        }, true /* traverseTopToBottom */);
+    }
+
+    /**
      * Returns the app window token for the input binder if it exist in the system.
      * NOTE: Only one AppWindowToken is allowed to exist in the system for a binder token, since
      * AppWindowToken represents an activity which can only exist on one display.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 91aac7e..90506e7 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2651,28 +2651,6 @@
     }
 
     /**
-     * @return true if the activity contains windows that have
-     *         {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} set
-     */
-    public boolean containsShowWhenLockedWindow(IBinder token) {
-        synchronized (mGlobalLock) {
-            final AppWindowToken wtoken = mRoot.getAppWindowToken(token);
-            return wtoken != null && wtoken.containsShowWhenLockedWindow();
-        }
-    }
-
-    /**
-     * @return true if the activity contains windows that have
-     *         {@link LayoutParams#FLAG_DISMISS_KEYGUARD} set
-     */
-    public boolean containsDismissKeyguardWindow(IBinder token) {
-        synchronized (mGlobalLock) {
-            final AppWindowToken wtoken = mRoot.getAppWindowToken(token);
-            return wtoken != null && wtoken.containsDismissKeyguardWindow();
-        }
-    }
-
-    /**
      * Notifies activity manager that some Keyguard flags have changed and that it needs to
      * reevaluate the visibilities of the activities.
      * @param callback Runnable to be called when activity manager is done reevaluating visibilities
@@ -5738,17 +5716,6 @@
     }
 
     /**
-     * Returns true if the callingUid has any window currently visible to the user.
-     */
-    public boolean isAnyWindowVisibleForUid(int callingUid) {
-        synchronized (mGlobalLock) {
-            return mRoot.forAllWindows(w -> {
-                return w.getOwningUid() == callingUid && w.isVisible();
-            }, true /* traverseTopToBottom */);
-        }
-    }
-
-    /**
      * Called when a task has been removed from the recent tasks list.
      * <p>
      * Note: This doesn't go through {@link TaskWindowContainerController} yet as the window
diff --git a/services/robotests/Android.mk b/services/robotests/Android.mk
index 9159f0d..0cf0d34 100644
--- a/services/robotests/Android.mk
+++ b/services/robotests/Android.mk
@@ -26,9 +26,6 @@
 LOCAL_PRIVILEGED_MODULE := true
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    bmgrlib \
-    bu \
-    services.backup \
     services.core \
     services.net
 
@@ -41,8 +38,7 @@
 
 LOCAL_MODULE := FrameworksServicesRoboTests
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src) \
-    $(call all-java-files-under, backup/src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_RESOURCE_DIR := \
     $(LOCAL_PATH)/res
@@ -82,8 +78,7 @@
 
 LOCAL_TEST_PACKAGE := FrameworksServicesLib
 
-LOCAL_ROBOTEST_FILES := $(call find-files-in-subdirs,$(LOCAL_PATH)/src,*Test.java,.) \
-    $(call find-files-in-subdirs,$(LOCAL_PATH)/backup/src,*Test.java,.)
+LOCAL_ROBOTEST_FILES := $(call find-files-in-subdirs,$(LOCAL_PATH)/src,*Test.java,.)
 
 include external/robolectric-shadows/run_robotests.mk
 
diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java
index 769a9d4..0d3b6b2 100644
--- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -1075,7 +1075,7 @@
                 createServiceAndRegisterUser(UserHandle.USER_SYSTEM, mUserOneService);
         FullBackupJob job = new FullBackupJob();
 
-        backupManagerService.beginFullBackup(job);
+        backupManagerService.beginFullBackup(UserHandle.USER_SYSTEM, job);
 
         verify(mUserOneService).beginFullBackup(job);
     }
@@ -1086,7 +1086,7 @@
         BackupManagerService backupManagerService = createService();
         FullBackupJob job = new FullBackupJob();
 
-        backupManagerService.beginFullBackup(job);
+        backupManagerService.beginFullBackup(UserHandle.USER_SYSTEM, job);
 
         verify(mUserOneService, never()).beginFullBackup(job);
     }
@@ -1097,7 +1097,7 @@
         BackupManagerService backupManagerService =
                 createServiceAndRegisterUser(UserHandle.USER_SYSTEM, mUserOneService);
 
-        backupManagerService.endFullBackup();
+        backupManagerService.endFullBackup(UserHandle.USER_SYSTEM);
 
         verify(mUserOneService).endFullBackup();
     }
@@ -1107,7 +1107,7 @@
     public void testEndFullBackup_onUnknownUser_doesNotPropagateCall() throws Exception {
         BackupManagerService backupManagerService = createService();
 
-        backupManagerService.endFullBackup();
+        backupManagerService.endFullBackup(UserHandle.USER_SYSTEM);
 
         verify(mUserOneService, never()).endFullBackup();
     }
diff --git a/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java b/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java
new file mode 100644
index 0000000..9a78d0b3
--- /dev/null
+++ b/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.UserIdInt;
+import android.app.job.JobScheduler;
+import android.content.Context;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowJobScheduler;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowJobScheduler.class})
+@Presubmit
+public class FullBackupJobTest {
+    private Context mContext;
+    private BackupManagerConstants mConstants;
+    private ShadowJobScheduler mShadowJobScheduler;
+
+    @UserIdInt private int mUserOneId;
+    @UserIdInt private int mUserTwoId;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = RuntimeEnvironment.application;
+        mConstants = new BackupManagerConstants(Handler.getMain(), mContext.getContentResolver());
+        mConstants.start();
+
+        mShadowJobScheduler = Shadows.shadowOf(mContext.getSystemService(JobScheduler.class));
+
+        mUserOneId = UserHandle.USER_SYSTEM;
+        mUserTwoId = mUserOneId + 1;
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mConstants.stop();
+        FullBackupJob.cancel(mUserOneId, mContext);
+        FullBackupJob.cancel(mUserTwoId, mContext);
+    }
+
+    @Test
+    public void testSchedule_afterScheduling_jobExists() {
+        FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
+        FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants);
+
+        assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNotNull();
+        assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNotNull();
+    }
+
+    @Test
+    public void testCancel_afterCancelling_jobDoesntExist() {
+        FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
+        FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants);
+        FullBackupJob.cancel(mUserOneId, mContext);
+        FullBackupJob.cancel(mUserTwoId, mContext);
+
+        assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNull();
+        assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNull();
+    }
+//
+    @Test
+    public void testSchedule_onlySchedulesForRequestedUser() {
+        FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
+
+        assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNotNull();
+        assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNull();
+    }
+//
+    @Test
+    public void testCancel_onlyCancelsForRequestedUser() {
+        FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
+        FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants);
+        FullBackupJob.cancel(mUserOneId, mContext);
+
+        assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNull();
+        assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNotNull();
+    }
+
+    private static int getJobIdForUserId(int userId) {
+        return JobIdManager.getJobIdForUserId(FullBackupJob.MIN_JOB_ID, FullBackupJob.MAX_JOB_ID,
+                userId);
+    }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/JobIdManagerTest.java b/services/robotests/backup/src/com/android/server/backup/JobIdManagerTest.java
new file mode 100644
index 0000000..f8bb1ee
--- /dev/null
+++ b/services/robotests/backup/src/com/android/server/backup/JobIdManagerTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.UserIdInt;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import static org.testng.Assert.expectThrows;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class JobIdManagerTest {
+    private static final int MIN_JOB_ID = 10;
+    private static final int MAX_JOB_ID = 20;
+
+    @UserIdInt private int mUserOneId;
+    @UserIdInt private int mUserTwoId;
+
+    @Before
+    public void setUp() {
+        mUserOneId = UserHandle.USER_SYSTEM;
+        mUserTwoId = mUserOneId + 1;
+    }
+
+    @Test
+    public void testGetJobIdForUserId_returnsDifferentJobIdsForDifferentUsers() {
+        int jobIdOne = JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, mUserOneId);
+        int jobIdTwo = JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, mUserTwoId);
+
+        assertThat(jobIdOne).isNotEqualTo(jobIdTwo);
+    }
+
+    @Test
+    public void testGetJobIdForUserId_returnsSameJobIdForSameUser() {
+        int jobIdOne = JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, mUserOneId);
+        int jobIdTwo = JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, mUserOneId);
+
+        assertThat(jobIdOne).isEqualTo(jobIdTwo);
+    }
+
+    @Test
+    public void testGetJobIdForUserId_throwsExceptionIfRangeIsExceeded() {
+        expectThrows(
+                RuntimeException.class,
+                () -> JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID,
+                        MAX_JOB_ID + 1));
+    }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java b/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java
index 8e17209..8d9e44f 100644
--- a/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java
@@ -18,8 +18,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.annotation.UserIdInt;
 import android.content.Context;
 import android.os.Handler;
+import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 
 import org.junit.After;
@@ -35,32 +37,67 @@
     private Context mContext;
     private BackupManagerConstants mConstants;
 
+    @UserIdInt private int mUserOneId;
+    @UserIdInt private int mUserTwoId;
+
     @Before
     public void setUp() throws Exception {
         mContext = RuntimeEnvironment.application;
         mConstants = new BackupManagerConstants(Handler.getMain(), mContext.getContentResolver());
         mConstants.start();
+
+        mUserOneId = UserHandle.USER_SYSTEM;
+        mUserTwoId = mUserOneId + 1;
     }
 
     @After
     public void tearDown() throws Exception {
         mConstants.stop();
-        KeyValueBackupJob.cancel(mContext);
+        KeyValueBackupJob.cancel(mUserOneId, mContext);
+        KeyValueBackupJob.cancel(mUserTwoId, mContext);
     }
 
     @Test
     public void testIsScheduled_beforeScheduling_returnsFalse() {
-        boolean isScheduled = KeyValueBackupJob.isScheduled();
-
-        assertThat(isScheduled).isFalse();
+        assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse();
+        assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isFalse();
     }
 
     @Test
     public void testIsScheduled_afterScheduling_returnsTrue() {
-        KeyValueBackupJob.schedule(mContext, mConstants);
+        KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
+        KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants);
 
-        boolean isScheduled = KeyValueBackupJob.isScheduled();
+        assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isTrue();
+        assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isTrue();
+    }
 
-        assertThat(isScheduled).isTrue();
+    @Test
+    public void testIsScheduled_afterCancelling_returnsFalse() {
+        KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
+        KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants);
+        KeyValueBackupJob.cancel(mUserOneId, mContext);
+        KeyValueBackupJob.cancel(mUserTwoId, mContext);
+
+        assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse();
+        assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isFalse();
+    }
+
+    @Test
+    public void testIsScheduled_afterScheduling_returnsTrueOnlyForScheduledUser() {
+        KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
+
+        assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isTrue();
+        assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isFalse();
+    }
+
+    @Test
+    public void testIsScheduled_afterCancelling_returnsFalseOnlyForCancelledUser() {
+        KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
+        KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants);
+        KeyValueBackupJob.cancel(mUserOneId, mContext);
+
+        assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse();
+        assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isTrue();
     }
 }
diff --git a/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java
index 693092d..7559560 100644
--- a/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java
@@ -41,6 +41,7 @@
 import static java.util.stream.Stream.concat;
 
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.backup.BackupManager;
 import android.app.backup.BackupTransport;
 import android.content.ComponentName;
@@ -48,6 +49,7 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
+import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 
 import com.android.server.backup.testing.TransportData;
@@ -84,12 +86,14 @@
     private TransportData mTransportA2;
     private TransportData mTransportB1;
     private ShadowPackageManager mShadowPackageManager;
+    private @UserIdInt int mUserId;
     private Context mContext;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
+        mUserId = UserHandle.USER_SYSTEM;
         mContext = RuntimeEnvironment.application;
         mShadowPackageManager = shadowOf(mContext.getPackageManager());
 
@@ -684,6 +688,7 @@
             @Nullable TransportData selectedTransport, TransportData... transports) {
         TransportManager transportManager =
                 new TransportManager(
+                        mUserId,
                         mContext,
                         merge(selectedTransport, transports)
                                 .stream()
diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 8d5c301..4968176 100644
--- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -1137,8 +1137,9 @@
          * Implementation of {@link ShadowKeyValueBackupJob#schedule(Context, long,
          * BackupManagerConstants)} that throws an {@link IllegalArgumentException}.
          */
-        public static void schedule(Context ctx, long delay, BackupManagerConstants constants) {
-            ShadowKeyValueBackupJob.schedule(ctx, delay, constants);
+        public static void schedule(int userId, Context ctx, long delay,
+                BackupManagerConstants constants) {
+            ShadowKeyValueBackupJob.schedule(userId, ctx, delay, constants);
             throw new IllegalArgumentException();
         }
     }
diff --git a/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java b/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java
index b754356..b08ae6d 100644
--- a/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java
@@ -125,7 +125,7 @@
 
         setupObserver.onChange(true);
 
-        assertThat(KeyValueBackupJob.isScheduled()).isTrue();
+        assertThat(KeyValueBackupJob.isScheduled(mUserBackupManagerService.getUserId())).isTrue();
         // Verifies that the full backup job is scheduled. The job is scheduled via a posted message
         // on the backup handler so we verify that a message exists.
         assertThat(mUserBackupManagerService.getBackupHandler().hasMessagesOrCallbacks()).isTrue();
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index 7dac795..b923bb0 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -2727,7 +2727,7 @@
             throws RemoteException, IOException {
         verify(transportMock.transport).requestBackupTime();
         assertBackupPendingFor(packages);
-        assertThat(KeyValueBackupJob.isScheduled()).isTrue();
+        assertThat(KeyValueBackupJob.isScheduled(mBackupManagerService.getUserId())).isTrue();
     }
 
     private void assertBackupPendingFor(PackageData... packages) throws IOException {
diff --git a/services/robotests/backup/src/com/android/server/backup/transport/TransportClientManagerTest.java b/services/robotests/backup/src/com/android/server/backup/transport/TransportClientManagerTest.java
index 7dd0d92..f033af8 100644
--- a/services/robotests/backup/src/com/android/server/backup/transport/TransportClientManagerTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/transport/TransportClientManagerTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -49,6 +50,7 @@
 
     @Mock private Context mContext;
     @Mock private TransportConnectionListener mTransportConnectionListener;
+    private @UserIdInt int mUserId;
     private TransportClientManager mTransportClientManager;
     private ComponentName mTransportComponent;
     private Intent mBindIntent;
@@ -57,7 +59,9 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        mTransportClientManager = new TransportClientManager(mContext, new TransportStats());
+        mUserId = UserHandle.USER_SYSTEM;
+        mTransportClientManager =
+                new TransportClientManager(mUserId, mContext, new TransportStats());
         mTransportComponent = new ComponentName(PACKAGE_NAME, CLASS_NAME);
         mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(mTransportComponent);
 
diff --git a/services/robotests/backup/src/com/android/server/backup/transport/TransportClientTest.java b/services/robotests/backup/src/com/android/server/backup/transport/TransportClientTest.java
index 7281a3c..392f2ca 100644
--- a/services/robotests/backup/src/com/android/server/backup/transport/TransportClientTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/transport/TransportClientTest.java
@@ -36,6 +36,7 @@
 import static org.testng.Assert.expectThrows;
 
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -83,6 +84,7 @@
     @Mock private TransportConnectionListener mTransportConnectionListener;
     @Mock private TransportConnectionListener mTransportConnectionListener2;
     @Mock private IBackupTransport.Stub mTransportBinder;
+    @UserIdInt private int mUserId;
     private TransportStats mTransportStats;
     private TransportClient mTransportClient;
     private ComponentName mTransportComponent;
@@ -96,6 +98,7 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
+        mUserId = UserHandle.USER_SYSTEM;
         Looper mainLooper = Looper.getMainLooper();
         mShadowMainLooper = extract(mainLooper);
         mTransportComponent =
@@ -105,6 +108,7 @@
         mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(mTransportComponent);
         mTransportClient =
                 new TransportClient(
+                        mUserId,
                         mContext,
                         mTransportStats,
                         mBindIntent,
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java
index 23c44b0e..f90ea6a 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java
@@ -34,7 +34,8 @@
     }
 
     @Implementation
-    protected static void schedule(Context ctx, long delay, BackupManagerConstants constants) {
+    protected static void schedule(int userId, Context ctx, long delay,
+            BackupManagerConstants constants) {
         callingUid = Binder.getCallingUid();
     }
 }
diff --git a/services/tests/rescueparty/Android.bp b/services/tests/rescueparty/Android.bp
new file mode 100644
index 0000000..6733af4
--- /dev/null
+++ b/services/tests/rescueparty/Android.bp
@@ -0,0 +1,23 @@
+// Copyright (C) 2008 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.
+
+cc_test {
+    name: "log_rescueparty_reset_event_reported",
+    srcs: ["log_rescueparty_reset_event_reported.cpp"],
+    shared_libs: [
+        "libbase",
+        "libstatslog",
+    ],
+    gtest: false,
+}
diff --git a/services/tests/rescueparty/how_to_run.txt b/services/tests/rescueparty/how_to_run.txt
new file mode 100644
index 0000000..9528d39
--- /dev/null
+++ b/services/tests/rescueparty/how_to_run.txt
@@ -0,0 +1,9 @@
+# Per http://go/westworld-local-development#step3-test-atom-and-metric-locally-on-device ,
+# In one terminal:
+make statsd_testdrive
+./out/host/linux-x86/bin/statsd_testdrive 122
+
+# In another terminal:
+mma -j $(nproc) log_rescueparty_reset_event_reported
+adb push $OUT/testcases/log_rescueparty_reset_event_reported/arm64/log_rescueparty_reset_event_reported /data
+adb shell /data/log_rescueparty_reset_event_reported 1234
diff --git a/services/tests/rescueparty/log_rescueparty_reset_event_reported.cpp b/services/tests/rescueparty/log_rescueparty_reset_event_reported.cpp
new file mode 100644
index 0000000..4aea917
--- /dev/null
+++ b/services/tests/rescueparty/log_rescueparty_reset_event_reported.cpp
@@ -0,0 +1,24 @@
+// Copyright (C) 2008 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.
+
+#include <statslog.h>
+#include <cstdlib>
+
+int main(int argc, const char** argv) {
+    int level = 0;
+    if (argc > 1) {
+        level = std::atoi(argv[1]);
+    }
+    android::util::stats_write(android::util::RESCUE_PARTY_RESET_REPORTED, level);
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
index e4fe599..00a60b9 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
@@ -117,7 +117,7 @@
         thread2.assertWaiting("Unexpected state for " + record2);
         thread2.interrupt();
 
-        mAms.mActiveUids.clear();
+        mAms.mProcessList.mActiveUids.clear();
     }
 
     private UidRecord addActiveUidRecord(int uid, long curProcStateSeq,
@@ -126,7 +126,7 @@
         record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq;
         record.curProcStateSeq = curProcStateSeq;
         record.waitingForNetwork = true;
-        mAms.mActiveUids.put(uid, record);
+        mAms.mProcessList.mActiveUids.put(uid, record);
         return record;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 2cc338c..419c736 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -253,7 +253,7 @@
         final UidRecord uidRec = new UidRecord(uid, null /* atmInternal */);
         uidRec.waitingForNetwork = true;
         uidRec.hasInternetPermission = true;
-        mAms.mActiveUids.put(uid, uidRec);
+        mAms.mProcessList.mActiveUids.put(uid, uidRec);
 
         final ProcessRecord appRec = new ProcessRecord(mAms, new ApplicationInfo(), TAG, uid);
         appRec.thread = Mockito.mock(IApplicationThread.class);
@@ -715,7 +715,8 @@
 
         // Verify that when uid state changes to CHANGE_GONE or CHANGE_GONE_IDLE, then it
         // will be removed from validateUids.
-        assertNotEquals("validateUids should not be empty", 0, mAms.mValidateUids.size());
+        assertNotEquals("validateUids should not be empty", 0,
+                mAms.mValidateUids.size());
         for (int i = 0; i < pendingItemsForUids.size(); ++i) {
             final UidRecord.ChangeItem item = pendingItemsForUids.get(i);
             // Assign CHANGE_GONE_IDLE to some items and CHANGE_GONE to the others, using even/odd
@@ -853,7 +854,7 @@
         record.curProcStateSeq = curProcStateSeq;
         record.lastDispatchedProcStateSeq = lastDispatchedProcStateSeq;
         record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq;
-        mAms.mActiveUids.put(Process.myUid(), record);
+        mAms.mProcessList.mActiveUids.put(Process.myUid(), record);
 
         CustomThread thread = new CustomThread(record.networkStateLock, new Runnable() {
             @Override
@@ -876,7 +877,7 @@
             thread.assertTerminated(errMsg);
         }
 
-        mAms.mActiveUids.clear();
+        mAms.mProcessList.mActiveUids.clear();
     }
 
     private static class TestHandler extends Handler {
diff --git a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
index db83505..6a9a121 100644
--- a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
@@ -1149,31 +1149,31 @@
 
     @Test
     public void beginFullBackup_calledBeforeInitialize_ignored() throws RemoteException {
-        mTrampoline.beginFullBackup(new FullBackupJob());
+        mTrampoline.beginFullBackup(mUserId, new FullBackupJob());
         verifyNoMoreInteractions(mBackupManagerServiceMock);
     }
 
     @Test
     public void beginFullBackup_forwarded() throws RemoteException {
         FullBackupJob fullBackupJob = new FullBackupJob();
-        when(mBackupManagerServiceMock.beginFullBackup(fullBackupJob)).thenReturn(true);
+        when(mBackupManagerServiceMock.beginFullBackup(mUserId, fullBackupJob)).thenReturn(true);
 
         mTrampoline.initializeService();
-        assertTrue(mTrampoline.beginFullBackup(fullBackupJob));
-        verify(mBackupManagerServiceMock).beginFullBackup(fullBackupJob);
+        assertTrue(mTrampoline.beginFullBackup(mUserId, fullBackupJob));
+        verify(mBackupManagerServiceMock).beginFullBackup(mUserId, fullBackupJob);
     }
 
     @Test
     public void endFullBackup_calledBeforeInitialize_ignored() throws RemoteException {
-        mTrampoline.endFullBackup();
+        mTrampoline.endFullBackup(mUserId);
         verifyNoMoreInteractions(mBackupManagerServiceMock);
     }
 
     @Test
     public void endFullBackup_forwarded() throws RemoteException {
         mTrampoline.initializeService();
-        mTrampoline.endFullBackup();
-        verify(mBackupManagerServiceMock).endFullBackup();
+        mTrampoline.endFullBackup(mUserId);
+        verify(mBackupManagerServiceMock).endFullBackup(mUserId);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
index 4255e37..8607ec6 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
@@ -50,6 +50,7 @@
 
     private final List<HdmiCecMessage> mResultMessages = new ArrayList<>();
     private int mMyPhysicalAddress = 0;
+    private HdmiPortInfo[] mHdmiPortInfo = null;
 
     @Override
     public long nativeInit(HdmiCecController handler, MessageQueue messageQueue) {
@@ -92,9 +93,11 @@
 
     @Override
     public HdmiPortInfo[] nativeGetPortInfos(long controllerPtr) {
-        HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[1];
-        hdmiPortInfo[0] = new HdmiPortInfo(1, 1, 0x1000, true, true, true);
-        return hdmiPortInfo;
+        if (mHdmiPortInfo == null) {
+            mHdmiPortInfo = new HdmiPortInfo[1];
+            mHdmiPortInfo[0] = new HdmiPortInfo(1, 1, 0x1000, true, true, true);
+        }
+        return mHdmiPortInfo;
     }
 
     @Override
@@ -131,4 +134,10 @@
     protected void setPhysicalAddress(int physicalAddress) {
         mMyPhysicalAddress = physicalAddress;
     }
+
+    @VisibleForTesting
+    protected void setPortInfo(HdmiPortInfo[] hdmiPortInfo) {
+        mHdmiPortInfo = new HdmiPortInfo[hdmiPortInfo.length];
+        System.arraycopy(hdmiPortInfo, 0, mHdmiPortInfo, 0, hdmiPortInfo.length);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index b47f269..3b51a2a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -15,6 +15,9 @@
  */
 package com.android.server.hdmi;
 
+import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE;
+import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE;
+
 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
@@ -25,6 +28,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.hardware.hdmi.HdmiDeviceInfo;
 import android.media.AudioManager;
 import android.os.Looper;
 import android.os.SystemProperties;
@@ -64,6 +68,8 @@
     private int mMusicMaxVolume;
     private boolean mMusicMute;
     private int mAvrPhysicalAddress;
+    private int mInvokeDeviceEventState;
+    private HdmiDeviceInfo mDeviceInfo;
 
     @Before
     public void setUp() {
@@ -127,6 +133,18 @@
 
                 @Override
                 void wakeUp() {}
+
+                @Override
+                void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
+                    mDeviceInfo = device;
+                    mInvokeDeviceEventState = status;
+                }
+
+                @Override
+                int pathToPortId(int path) {
+                    // port id is not useful for the test right now
+                    return 1;
+                }
             };
 
         mMyLooper = mTestLooper.getLooper();
@@ -157,6 +175,8 @@
         mNativeWrapper.setPhysicalAddress(mAvrPhysicalAddress);
         SystemProperties.set(Constants.PROPERTY_ARC_SUPPORT, "true");
         SystemProperties.set(Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE, "true");
+        mInvokeDeviceEventState = 0;
+        mDeviceInfo = null;
     }
 
     @Test
@@ -611,4 +631,73 @@
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
     }
+
+    @Test
+    public void updateCecDevice_deviceNotExists_addDevice() {
+        assertThat(mInvokeDeviceEventState).isNotEqualTo(DEVICE_EVENT_ADD_DEVICE);
+        HdmiDeviceInfo newDevice = new HdmiDeviceInfo(
+                ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK,
+                Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1));
+
+        mHdmiCecLocalDeviceAudioSystem.updateCecDevice(newDevice);
+        assertThat(mDeviceInfo).isEqualTo(newDevice);
+        assertThat(mHdmiCecLocalDeviceAudioSystem
+            .getCecDeviceInfo(newDevice.getLogicalAddress())).isEqualTo(newDevice);
+        assertThat(mInvokeDeviceEventState).isEqualTo(DEVICE_EVENT_ADD_DEVICE);
+    }
+
+    @Test
+    public void updateCecDevice_deviceExists_doNothing() {
+        mInvokeDeviceEventState = 0;
+        HdmiDeviceInfo oldDevice = new HdmiDeviceInfo(
+                ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK,
+                Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1));
+        mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice);
+
+        mHdmiCecLocalDeviceAudioSystem.updateCecDevice(oldDevice);
+        assertThat(mInvokeDeviceEventState).isEqualTo(0);
+    }
+
+    @Test
+    public void updateCecDevice_deviceInfoDifferent_updateDevice() {
+        assertThat(mInvokeDeviceEventState).isNotEqualTo(DEVICE_EVENT_UPDATE_DEVICE);
+        HdmiDeviceInfo oldDevice = new HdmiDeviceInfo(
+                ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK,
+                Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1));
+        mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice);
+
+        HdmiDeviceInfo differentDevice = new HdmiDeviceInfo(
+                ADDR_PLAYBACK_1, 0x2100, 4, HdmiDeviceInfo.DEVICE_PLAYBACK,
+                Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1));
+
+        mHdmiCecLocalDeviceAudioSystem.updateCecDevice(differentDevice);
+        assertThat(mDeviceInfo).isEqualTo(differentDevice);
+        assertThat(mHdmiCecLocalDeviceAudioSystem
+            .getCecDeviceInfo(differentDevice.getLogicalAddress())).isEqualTo(differentDevice);
+        assertThat(mInvokeDeviceEventState).isEqualTo(DEVICE_EVENT_UPDATE_DEVICE);
+    }
+
+    @Test
+    public void handleReportPhysicalAddress_differentPath_addDevice() {
+        assertThat(mInvokeDeviceEventState).isNotEqualTo(DEVICE_EVENT_ADD_DEVICE);
+        HdmiDeviceInfo oldDevice = new HdmiDeviceInfo(
+                ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK,
+                Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1));
+        mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice);
+
+        HdmiDeviceInfo differentDevice = new HdmiDeviceInfo(
+                ADDR_PLAYBACK_1, 0x2200, 1, HdmiDeviceInfo.DEVICE_PLAYBACK,
+                Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1));
+        HdmiCecMessage reportPhysicalAddress = HdmiCecMessageBuilder
+                .buildReportPhysicalAddressCommand(
+                        ADDR_PLAYBACK_1, 0x2200, HdmiDeviceInfo.DEVICE_PLAYBACK);
+        mHdmiCecLocalDeviceAudioSystem.handleReportPhysicalAddress(reportPhysicalAddress);
+
+        mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice);
+        mTestLooper.dispatchAll();
+        assertThat(mDeviceInfo).isEqualTo(differentDevice);
+        assertThat(mHdmiCecLocalDeviceAudioSystem
+            .getCecDeviceInfo(differentDevice.getLogicalAddress())).isEqualTo(differentDevice);
+        assertThat(mInvokeDeviceEventState).isEqualTo(DEVICE_EVENT_ADD_DEVICE);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 18c9a65..67ce13f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -20,13 +20,18 @@
 
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
+import android.hardware.hdmi.HdmiPortInfo;
 import android.os.Looper;
 import android.os.test.TestLooper;
+
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -102,6 +107,7 @@
     private TestLooper mTestLooper = new TestLooper();
     private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
     private boolean mStandbyMessageReceived;
+    private HdmiPortInfo[] mHdmiPortInfo;
 
     @Before
     public void SetUp() {
@@ -131,6 +137,16 @@
 
         mLocalDevices.add(mMyAudioSystemDevice);
         mLocalDevices.add(mMyPlaybackDevice);
+        mHdmiPortInfo = new HdmiPortInfo[4];
+        mHdmiPortInfo[0] =
+            new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false);
+        mHdmiPortInfo[1] =
+            new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2200, true, false, false);
+        mHdmiPortInfo[2] =
+            new HdmiPortInfo(3, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false);
+        mHdmiPortInfo[3] =
+            new HdmiPortInfo(4, HdmiPortInfo.PORT_INPUT, 0x3000, true, false, false);
+        mNativeWrapper.setPortInfo(mHdmiPortInfo);
         mHdmiControlService.initPortInfo();
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
 
@@ -159,4 +175,24 @@
         assertTrue(mMyPlaybackDevice.isDisabled());
         assertTrue(mMyAudioSystemDevice.isDisabled());
     }
+
+    @Test
+    public void pathToPort_pathExists_weAreNonTv() {
+        mNativeWrapper.setPhysicalAddress(0x2000);
+        assertThat(mHdmiControlService.pathToPortId(0x2120)).isEqualTo(1);
+        assertThat(mHdmiControlService.pathToPortId(0x2234)).isEqualTo(2);
+    }
+
+    @Test
+    public void pathToPort_pathExists_weAreTv() {
+        mNativeWrapper.setPhysicalAddress(0x0000);
+        assertThat(mHdmiControlService.pathToPortId(0x2120)).isEqualTo(3);
+        assertThat(mHdmiControlService.pathToPortId(0x3234)).isEqualTo(4);
+    }
+
+    @Test
+    public void pathToPort_pathInvalid() {
+        mNativeWrapper.setPhysicalAddress(0x2000);
+        assertThat(mHdmiControlService.pathToPortId(0x1000)).isEqualTo(Constants.INVALID_PORT_ID);
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 1458266..b9ae7d5 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -56,8 +56,8 @@
 import android.provider.Settings;
 import android.service.notification.Adjustment;
 import android.service.notification.StatusBarNotification;
+import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
-import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.R;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -87,14 +87,7 @@
     private final String channelId = "channel";
     private NotificationChannel channel =
             new NotificationChannel(channelId, "test", NotificationManager.IMPORTANCE_DEFAULT);
-    private final String channelIdLong =
-            "give_a_developer_a_string_argument_and_who_knows_what_they_will_pass_in_there";
     private final String groupId = "group";
-    private final String groupIdOverride = "other_group";
-    private final String groupIdLong =
-            "0|com.foo.bar|g:content://com.foo.bar.ui/account%3A-0000000/account/";
-    private NotificationChannel channelLongId =
-            new NotificationChannel(channelIdLong, "long", NotificationManager.IMPORTANCE_DEFAULT);
     private NotificationChannel defaultChannel =
             new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "test",
                     NotificationManager.IMPORTANCE_UNSPECIFIED);
@@ -408,73 +401,27 @@
     }
 
     @Test
-    public void testLogmakerShortChannel() {
+    public void testLogMaker() {
+        long timestamp = 1000L;
         StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
                 true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
                 false /* lights */, false /* defaultLights */, null /* group */);
         NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
-        final LogMaker logMaker = record.getLogMaker();
+        final LogMaker logMaker = record.getLogMaker(timestamp);
+
+        assertNull(logMaker.getTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX));
         assertEquals(channelId,
                 (String) logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID));
         assertEquals(channel.getImportance(),
                 logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE));
-    }
-
-    @Test
-    public void testLogmakerLongChannel() {
-        StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
-        true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
-        false /* lights */, false /*defaultLights */, null /* group */);
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channelLongId);
-        final String loggedId = (String)
-            record.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID);
-        assertEquals(channelIdLong.substring(0,10), loggedId.substring(0, 10));
-    }
-
-    @Test
-    public void testLogmakerNoGroup() {
-        StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
-                true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
-                false /* lights */, false /*defaultLights */, null /* group */);
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
-        assertNull(record.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
-    }
-
-    @Test
-    public void testLogmakerShortGroup() {
-        StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
-                true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
-                false /* lights */, false /* defaultLights */, groupId /* group */);
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
-        assertEquals(groupId,
-                record.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
-    }
-
-    @Test
-    public void testLogmakerLongGroup() {
-        StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
-                true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
-                false /* lights */, false /* defaultLights */, groupIdLong /* group */);
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
-        final String loggedId = (String)
-                record.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID);
-        assertEquals(groupIdLong.substring(0,10), loggedId.substring(0, 10));
-    }
-
-    @Test
-    public void testLogmakerOverrideGroup() {
-        StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
-                true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
-                false /* lights */, false /* defaultLights */, groupId /* group */);
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
-        assertEquals(groupId,
-                record.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
-        record.setOverrideGroupKey(groupIdOverride);
-        assertEquals(groupIdOverride,
-                record.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
-        record.setOverrideGroupKey(null);
-        assertEquals(groupId,
-                record.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
+        assertEquals(record.getLifespanMs(timestamp),
+                (int) logMaker.getTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS));
+        assertEquals(record.getFreshnessMs(timestamp),
+                (int) logMaker.getTaggedData(MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS));
+        assertEquals(record.getExposureMs(timestamp),
+                (int) logMaker.getTaggedData(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS));
+        assertEquals(record.getInterruptionMs(timestamp),
+                (int) logMaker.getTaggedData(MetricsEvent.NOTIFICATION_SINCE_INTERRUPTION_MILLIS));
     }
 
     @Test
@@ -845,4 +792,52 @@
 
         assertFalse(record.isNewEnoughForAlerting(record.mUpdateTimeMs + (1000 * 60 * 60)));
     }
+
+    @Test
+    public void testIgnoreImportanceAdjustmentsForOemLockedChannels() {
+        NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
+        channel.setImportanceLockedByOEM(true);
+
+        StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
+                true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+                false /* lights */, false /* defaultLights */, groupId /* group */);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+        assertEquals(IMPORTANCE_DEFAULT, record.getImportance());
+
+        Bundle bundle = new Bundle();
+        bundle.putInt(Adjustment.KEY_IMPORTANCE, IMPORTANCE_LOW);
+        Adjustment adjustment = new Adjustment(
+                PKG_O, record.getKey(), bundle, "", record.getUserId());
+
+        record.addAdjustment(adjustment);
+        record.applyAdjustments();
+        record.calculateImportance();
+
+        assertEquals(IMPORTANCE_DEFAULT, record.getImportance());
+    }
+
+    @Test
+    public void testApplyImportanceAdjustmentsForNonOemLockedChannels() {
+        NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
+        channel.setImportanceLockedByOEM(false);
+
+        StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
+                true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+                false /* lights */, false /* defaultLights */, groupId /* group */);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+        assertEquals(IMPORTANCE_DEFAULT, record.getImportance());
+
+        Bundle bundle = new Bundle();
+        bundle.putInt(Adjustment.KEY_IMPORTANCE, IMPORTANCE_LOW);
+        Adjustment adjustment = new Adjustment(
+                PKG_O, record.getKey(), bundle, "", record.getUserId());
+
+        record.addAdjustment(adjustment);
+        record.applyAdjustments();
+        record.calculateImportance();
+
+        assertEquals(IMPORTANCE_LOW, record.getImportance());
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 0b73481..0fcfea7 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -2187,4 +2187,121 @@
         assertEquals(PreferencesHelper.LockableAppFields.USER_LOCKED_APP_OVERLAY,
                 mHelper.getAppLockedFields(PKG_O, UID_O));
     }
+
+    @Test
+    public void testLockChannelsForOEM_emptyList() {
+        mHelper.lockChannelsForOEM(null);
+        mHelper.lockChannelsForOEM(new String[0]);
+        // no exception
+    }
+
+    @Test
+    public void testLockChannelsForOEM_appWide() {
+        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+        NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
+        NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT);
+        // different uids, same package
+        mHelper.createNotificationChannel(PKG_O, 3, a, true, false);
+        mHelper.createNotificationChannel(PKG_O, 3, b, false, false);
+        mHelper.createNotificationChannel(PKG_O, 30, c, true, true);
+
+        mHelper.lockChannelsForOEM(new String[] {PKG_O});
+
+        assertTrue(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false)
+                .isImportanceLockedByOEM());
+        assertTrue(mHelper.getNotificationChannel(PKG_O, 3, b.getId(), false)
+                .isImportanceLockedByOEM());
+        assertTrue(mHelper.getNotificationChannel(PKG_O, 30, c.getId(), false)
+                .isImportanceLockedByOEM());
+    }
+
+    @Test
+    public void testLockChannelsForOEM_onlyGivenPkg() {
+        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+        NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
+        mHelper.createNotificationChannel(PKG_O, 3, a, true, false);
+        mHelper.createNotificationChannel(PKG_N_MR1, 30, b, false, false);
+
+        mHelper.lockChannelsForOEM(new String[] {PKG_O});
+
+        assertTrue(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false)
+                .isImportanceLockedByOEM());
+        assertFalse(mHelper.getNotificationChannel(PKG_N_MR1, 30, b.getId(), false)
+                .isImportanceLockedByOEM());
+    }
+
+    @Test
+    public void testLockChannelsForOEM_channelSpecific() {
+        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+        NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
+        NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT);
+        // different uids, same package
+        mHelper.createNotificationChannel(PKG_O, 3, a, true, false);
+        mHelper.createNotificationChannel(PKG_O, 3, b, false, false);
+        mHelper.createNotificationChannel(PKG_O, 30, c, true, true);
+
+        mHelper.lockChannelsForOEM(new String[] {PKG_O + ":b", PKG_O + ":c"});
+
+        assertFalse(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false)
+                .isImportanceLockedByOEM());
+        assertTrue(mHelper.getNotificationChannel(PKG_O, 3, b.getId(), false)
+                .isImportanceLockedByOEM());
+        assertTrue(mHelper.getNotificationChannel(PKG_O, 30, c.getId(), false)
+                .isImportanceLockedByOEM());
+    }
+
+    @Test
+    public void testLockChannelsForOEM_channelDoesNotExistYet_appWide() {
+        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+        NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
+        mHelper.createNotificationChannel(PKG_O, 3, a, true, false);
+
+        mHelper.lockChannelsForOEM(new String[] {PKG_O});
+
+        assertTrue(mHelper.getNotificationChannel(PKG_O, 3, a.getId(), false)
+                .isImportanceLockedByOEM());
+
+        mHelper.createNotificationChannel(PKG_O, 3, b, true, false);
+        assertTrue(mHelper.getNotificationChannel(PKG_O, 3, b.getId(), false)
+                .isImportanceLockedByOEM());
+    }
+
+    @Test
+    public void testLockChannelsForOEM_channelDoesNotExistYet_channelSpecific() {
+        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+        NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW);
+        mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
+
+        mHelper.lockChannelsForOEM(new String[] {PKG_O + ":a", PKG_O + ":b"});
+
+        assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
+                .isImportanceLockedByOEM());
+
+        mHelper.createNotificationChannel(PKG_O, UID_O, b, true, false);
+        assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, b.getId(), false)
+                .isImportanceLockedByOEM());
+    }
+
+    @Test
+    public void testUpdateNotificationChannel_oemLockedImportance() {
+        NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+        mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false);
+
+        mHelper.lockChannelsForOEM(new String[] {PKG_O});
+
+        NotificationChannel update = new NotificationChannel("a", "a", IMPORTANCE_NONE);
+        update.setAllowAppOverlay(false);
+
+        mHelper.updateNotificationChannel(PKG_O, UID_O, update, true);
+
+        assertEquals(IMPORTANCE_HIGH,
+                mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).getImportance());
+        assertEquals(false,
+                mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).canOverlayApps());
+
+        mHelper.updateNotificationChannel(PKG_O, UID_O, update, true);
+
+        assertEquals(IMPORTANCE_HIGH,
+                mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false).getImportance());
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index 5638cb36..c79e1db0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -15,8 +15,11 @@
  */
 package com.android.server.notification;
 
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 
+import static junit.framework.TestCase.assertEquals;
+
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Matchers.anyInt;
@@ -153,7 +156,7 @@
                 .build();
         mRecordGroupGSortA = new NotificationRecord(mContext, new StatusBarNotification(
                 PKG, PKG, 1, null, 0, 0, mNotiGroupGSortA, user,
-                null, System.currentTimeMillis()), getDefaultChannel());
+                null, System.currentTimeMillis()), getLowChannel());
 
         mNotiGroupGSortB = new Notification.Builder(mContext, TEST_CHANNEL_ID)
                 .setContentTitle("B")
@@ -163,7 +166,7 @@
                 .build();
         mRecordGroupGSortB = new NotificationRecord(mContext, new StatusBarNotification(
                 PKG, PKG, 1, null, 0, 0, mNotiGroupGSortB, user,
-                null, System.currentTimeMillis()), getDefaultChannel());
+                null, System.currentTimeMillis()), getLowChannel());
 
         mNotiNoGroup = new Notification.Builder(mContext, TEST_CHANNEL_ID)
                 .setContentTitle("C")
@@ -171,7 +174,7 @@
                 .build();
         mRecordNoGroup = new NotificationRecord(mContext, new StatusBarNotification(
                 PKG, PKG, 1, null, 0, 0, mNotiNoGroup, user,
-                null, System.currentTimeMillis()), getDefaultChannel());
+                null, System.currentTimeMillis()), getLowChannel());
 
         mNotiNoGroup2 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
                 .setContentTitle("D")
@@ -179,7 +182,7 @@
                 .build();
         mRecordNoGroup2 = new NotificationRecord(mContext, new StatusBarNotification(
                 PKG, PKG, 1, null, 0, 0, mNotiNoGroup2, user,
-                null, System.currentTimeMillis()), getDefaultChannel());
+                null, System.currentTimeMillis()), getLowChannel());
 
         mNotiNoGroupSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID)
                 .setContentTitle("E")
@@ -188,7 +191,7 @@
                 .build();
         mRecordNoGroupSortA = new NotificationRecord(mContext, new StatusBarNotification(
                 PKG, PKG, 1, null, 0, 0, mNotiNoGroupSortA, user,
-                null, System.currentTimeMillis()), getDefaultChannel());
+                null, System.currentTimeMillis()), getLowChannel());
 
         mAudioAttributes = new AudioAttributes.Builder()
                 .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
@@ -197,11 +200,16 @@
                 .build();
     }
 
-    private NotificationChannel getDefaultChannel() {
+    private NotificationChannel getLowChannel() {
         return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name",
                 IMPORTANCE_LOW);
     }
 
+    private NotificationChannel getDefaultChannel() {
+        return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name",
+                IMPORTANCE_DEFAULT);
+    }
+
     @Test
     public void testSortShouldRespectCritical() throws Exception {
         ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(7);
@@ -285,4 +293,40 @@
         ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>();
         mHelper.sort(notificationList);
     }
+    
+    @Test
+    public void testGroupNotifications_highestIsProxy() {
+        ArrayList<NotificationRecord> notificationList = new ArrayList<>();
+        // this should be the last in the list, except it's in a group with a high child
+        Notification lowSummaryN = new Notification.Builder(mContext, "")
+                .setGroup("group")
+                .setGroupSummary(true)
+                .build();
+        NotificationRecord lowSummary = new NotificationRecord(mContext, new StatusBarNotification(
+                PKG, PKG, 1, "summary", 0, 0, lowSummaryN, USER,
+                null, System.currentTimeMillis()), getLowChannel());
+        notificationList.add(lowSummary);
+
+        Notification lowN = new Notification.Builder(mContext, "").build();
+        NotificationRecord low = new NotificationRecord(mContext, new StatusBarNotification(
+                PKG, PKG, 1, "low", 0, 0, lowN, USER,
+                null, System.currentTimeMillis()), getLowChannel());
+        low.setContactAffinity(0.5f);
+        notificationList.add(low);
+
+        Notification highChildN = new Notification.Builder(mContext, "")
+                .setGroup("group")
+                .setGroupSummary(false)
+                .build();
+        NotificationRecord highChild = new NotificationRecord(mContext, new StatusBarNotification(
+                PKG, PKG, 1, "child", 0, 0, highChildN, USER,
+                null, System.currentTimeMillis()), getDefaultChannel());
+        notificationList.add(highChild);
+
+        mHelper.sort(notificationList);
+
+        assertEquals(lowSummary, notificationList.get(0));
+        assertEquals(highChild, notificationList.get(1));
+        assertEquals(low, notificationList.get(2));
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 7a6b2b5..ec88718 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -643,10 +643,10 @@
             boolean hasForegroundActivities, boolean callerIsRecents,
             boolean callerIsTempWhitelisted) {
         // window visibility
-        doReturn(callingUidHasVisibleWindow).when(mService.mWindowManager).isAnyWindowVisibleForUid(
-                callingUid);
-        doReturn(realCallingUidHasVisibleWindow).when(mService.mWindowManager)
-                .isAnyWindowVisibleForUid(realCallingUid);
+        doReturn(callingUidHasVisibleWindow).when(mService.mWindowManager.mRoot)
+                .isAnyNonToastWindowVisibleForUid(callingUid);
+        doReturn(realCallingUidHasVisibleWindow).when(mService.mWindowManager.mRoot)
+                .isAnyNonToastWindowVisibleForUid(realCallingUid);
         // process importance
         doReturn(callingUidProcState).when(mService).getUidStateLocked(callingUid);
         doReturn(realCallingUidProcState).when(mService).getUidStateLocked(realCallingUid);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
new file mode 100644
index 0000000..45fe5d2
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Tests for RootWindowContainer.
+ *
+ * Build/Install/Run:
+ *  atest WmTests:RootWindowContainerTests
+ */
+@SmallTest
+@Presubmit
+public class RootWindowContainerTests extends WindowTestsBase {
+
+    private static final int FAKE_CALLING_UID = 667;
+
+    @Test
+    public void testIsAnyNonToastWindowVisibleForUid_oneToastOneNonToastBothVisible() {
+        final WindowState toastyToast = createWindow(null, TYPE_TOAST, "toast", FAKE_CALLING_UID);
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app", FAKE_CALLING_UID);
+        toastyToast.mHasSurface = true;
+        app.mHasSurface = true;
+
+        assertTrue(toastyToast.isVisible());
+        assertTrue(app.isVisible());
+        assertTrue(mWm.mRoot.isAnyNonToastWindowVisibleForUid(FAKE_CALLING_UID));
+    }
+
+    @Test
+    public void testIsAnyNonToastWindowVisibleForUid_onlyToastVisible() {
+        final WindowState toastyToast = createWindow(null, TYPE_TOAST, "toast", FAKE_CALLING_UID);
+        toastyToast.mHasSurface = true;
+
+        assertTrue(toastyToast.isVisible());
+        assertFalse(mWm.mRoot.isAnyNonToastWindowVisibleForUid(FAKE_CALLING_UID));
+    }
+
+    @Test
+    public void testIsAnyNonToastWindowVisibleForUid_aFewNonToastButNoneVisible() {
+        final WindowState topBar = createWindow(null, TYPE_STATUS_BAR, "topBar", FAKE_CALLING_UID);
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app", FAKE_CALLING_UID);
+
+        assertFalse(topBar.isVisible());
+        assertFalse(app.isVisible());
+        assertFalse(mWm.mRoot.isAnyNonToastWindowVisibleForUid(FAKE_CALLING_UID));
+    }
+}
+
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 638cb03..cdc0a47 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -257,6 +257,14 @@
         }
     }
 
+    WindowState createWindow(WindowState parent, int type, String name, int ownerId) {
+        synchronized (mWm.mGlobalLock) {
+            return (parent == null)
+                    ? createWindow(parent, type, mDisplayContent, name, ownerId)
+                    : createWindow(parent, type, parent.mToken, name, ownerId);
+        }
+    }
+
     WindowState createWindowOnStack(WindowState parent, int windowingMode, int activityType,
             int type, DisplayContent dc, String name) {
         synchronized (mWm.mGlobalLock) {
@@ -277,7 +285,16 @@
         synchronized (mWm.mGlobalLock) {
             final WindowToken token = createWindowToken(
                     dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type);
-            return createWindow(parent, type, token, name);
+            return createWindow(parent, type, token, name, 0 /* ownerId */);
+        }
+    }
+
+    WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name,
+            int ownerId) {
+        synchronized (mWm.mGlobalLock) {
+            final WindowToken token = createWindowToken(
+                    dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type);
+            return createWindow(parent, type, token, name, ownerId);
         }
     }
 
@@ -299,6 +316,14 @@
     }
 
     WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
+            int ownerId) {
+        synchronized (mWm.mGlobalLock) {
+            return createWindow(parent, type, token, name, ownerId,
+                    false /* ownerCanAddInternalSystemWindow */);
+        }
+    }
+
+    WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
             int ownerId, boolean ownerCanAddInternalSystemWindow) {
         return createWindow(parent, type, token, name, ownerId, ownerCanAddInternalSystemWindow,
                 mWm, mMockSession, mIWindow);
diff --git a/telephony/java/android/telephony/ICellInfoCallback.aidl b/telephony/java/android/telephony/ICellInfoCallback.aidl
index 7fb62682..ee3c1b1 100644
--- a/telephony/java/android/telephony/ICellInfoCallback.aidl
+++ b/telephony/java/android/telephony/ICellInfoCallback.aidl
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.os.ParcelableException;
 import android.telephony.CellInfo;
 
 import java.util.List;
@@ -27,4 +28,5 @@
 oneway interface ICellInfoCallback
 {
     void onCellInfo(in List<CellInfo> state);
+    void onError(in int errorCode, in ParcelableException detail);
 }
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index 2271069..e77042d 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -24,6 +24,8 @@
 import android.os.Parcelable;
 import android.os.PersistableBundle;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -178,6 +180,35 @@
         return mLte;
     }
 
+    /**
+     * Returns a List of CellSignalStrength Components of this SignalStrength Report.
+     *
+     * Use this API to access underlying
+     * {@link android.telephony#CellSignalStrength CellSignalStrength} objects that provide more
+     * granular information about the SignalStrength report. Only valid (non-empty)
+     * CellSignalStrengths will be returned. The order of any returned elements is not guaranteed,
+     * and the list may contain more than one instance of a CellSignalStrength type.
+     *
+     * @return a List of CellSignalStrength or an empty List if there are no valid measurements.
+     *
+     * @see android.telephony#CellSignalStrength
+     * @see android.telephony#CellSignalStrengthNr
+     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony#CellSignalStrengthTdscdma
+     * @see android.telephony#CellSignalStrengthWcdma
+     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony#CellSignalStrengthGsm
+     */
+    public @NonNull List<CellSignalStrength> getCellSignalStrengths() {
+        List<CellSignalStrength> cssList = new ArrayList<>(2); // Usually have 2 or fewer elems
+        if (mLte.isValid()) cssList.add(mLte);
+        if (mCdma.isValid()) cssList.add(mCdma);
+        if (mTdscdma.isValid()) cssList.add(mTdscdma);
+        if (mWcdma.isValid()) cssList.add(mWcdma);
+        if (mGsm.isValid()) cssList.add(mGsm);
+        return cssList;
+    }
+
     /** @hide */
     public void updateLevel(PersistableBundle cc, ServiceState ss) {
         mLteRsrpBoost = ss.getLteEarfcnRsrpBoost();
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index b48a1ce..7f7d5e7 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -4898,19 +4898,53 @@
     /** Callback for providing asynchronous {@link CellInfo} on request */
     public abstract static class CellInfoCallback {
         /**
-         * Response to
+         * Success response to
          * {@link android.telephony.TelephonyManager#requestCellInfoUpdate requestCellInfoUpdate()}.
          *
-         * <p>Invoked when there is a response to
+         * Invoked when there is a response to
          * {@link android.telephony.TelephonyManager#requestCellInfoUpdate requestCellInfoUpdate()}
          * to provide a list of {@link CellInfo}. If no {@link CellInfo} is available then an empty
-         * list will be provided. If an error occurs, null will be provided.
+         * list will be provided. If an error occurs, null will be provided unless the onError
+         * callback is overridden.
          *
          * @param cellInfo a list of {@link CellInfo}, an empty list, or null.
          *
          * {@see android.telephony.TelephonyManager#getAllCellInfo getAllCellInfo()}
          */
-        public abstract void onCellInfo(List<CellInfo> cellInfo);
+        public abstract void onCellInfo(@NonNull List<CellInfo> cellInfo);
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(prefix = {"ERROR_"}, value = {ERROR_TIMEOUT, ERROR_MODEM_ERROR})
+        public @interface CellInfoCallbackError {}
+
+        /**
+         * The system timed out waiting for a response from the Radio.
+         */
+        public static final int ERROR_TIMEOUT = 1;
+
+        /**
+         * The modem returned a failure.
+         */
+        public static final int ERROR_MODEM_ERROR = 2;
+
+        /**
+         * Error response to
+         * {@link android.telephony.TelephonyManager#requestCellInfoUpdate requestCellInfoUpdate()}.
+         *
+         * Invoked when an error condition prevents updated {@link CellInfo} from being fetched
+         * and returned from the modem. Callers of requestCellInfoUpdate() should override this
+         * function to receive detailed status information in the event of an error. By default,
+         * this function will invoke onCellInfo() with null.
+         *
+         * @param errorCode an error code indicating the type of failure.
+         * @param detail a Throwable object with additional detail regarding the failure if
+         *     available, otherwise null.
+         */
+        public void onError(@CellInfoCallbackError int errorCode, @Nullable Throwable detail) {
+            // By default, simply invoke the success callback with an empty list.
+            onCellInfo(new ArrayList<CellInfo>());
+        }
     };
 
     /**
@@ -4937,6 +4971,12 @@
                             Binder.withCleanCallingIdentity(() ->
                                     executor.execute(() -> callback.onCellInfo(cellInfo)));
                         }
+
+                        public void onError(int errorCode, android.os.ParcelableException detail) {
+                            Binder.withCleanCallingIdentity(() ->
+                                    executor.execute(() -> callback.onError(
+                                            errorCode, detail.getCause())));
+                        }
                     }, getOpPackageName());
 
         } catch (RemoteException ex) {
@@ -4971,6 +5011,12 @@
                             Binder.withCleanCallingIdentity(() ->
                                     executor.execute(() -> callback.onCellInfo(cellInfo)));
                         }
+
+                        public void onError(int errorCode, android.os.ParcelableException detail) {
+                            Binder.withCleanCallingIdentity(() ->
+                                    executor.execute(() -> callback.onError(
+                                            errorCode, detail.getCause())));
+                        }
                     }, getOpPackageName(), workSource);
         } catch (RemoteException ex) {
         }
diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index 0ccfb19..77cd9d8 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -108,6 +108,10 @@
             }
 
             // The app should not be available for rollback.
+            // TODO: See if there is a way to remove this race condition
+            // between when the app is uninstalled and when the previously
+            // available rollback, if any, is removed.
+            Thread.sleep(1000);
             assertNull(rm.getAvailableRollback(TEST_APP_A));
             assertFalse(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
 
@@ -125,6 +129,10 @@
             assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
 
             // The app should now be available for rollback.
+            // TODO: See if there is a way to remove this race condition
+            // between when the app is installed and when the rollback
+            // is made available.
+            Thread.sleep(1000);
             assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
             RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
             assertNotNull(rollback);
@@ -182,6 +190,8 @@
 
             RollbackManager rm = RollbackTestUtils.getRollbackManager();
 
+            // TODO: Test this with multi-package rollback, not just single
+            // package rollback.
             // Prep installation of TEST_APP_A
             RollbackTestUtils.uninstall(TEST_APP_A);
             RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
@@ -189,6 +199,10 @@
             assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
 
             // The app should now be available for rollback.
+            // TODO: See if there is a way to remove this race condition
+            // between when the app is installed and when the rollback
+            // is made available.
+            Thread.sleep(1000);
             assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
             RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
             assertNotNull(rollback);
@@ -263,6 +277,10 @@
             assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
 
             // The app should now be available for rollback.
+            // TODO: See if there is a way to remove this race condition
+            // between when the app is installed and when the rollback
+            // is made available.
+            Thread.sleep(1000);
             assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
             RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
             assertNotNull(rollback);
@@ -405,6 +423,10 @@
 
             // Both test apps should now be available for rollback, and the
             // targetPackage returned for rollback should be correct.
+            // TODO: See if there is a way to remove this race condition
+            // between when the app is installed and when the rollback
+            // is made available.
+            Thread.sleep(1000);
             RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
             assertNotNull(rollbackA);
             assertEquals(TEST_APP_A, rollbackA.targetPackage.packageName);
@@ -491,7 +513,7 @@
      * TODO: Stop ignoring this test once support for multi-package rollback
      * is implemented.
      */
-    @Ignore @Test
+    @Test
     public void testMultiPackage() throws Exception {
         try {
             RollbackTestUtils.adoptShellPermissionIdentity(
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
index 26a6c08..1fa1fd5 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
@@ -35,6 +35,7 @@
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
+import android.text.TextUtils;
 import android.util.Log;
 
 import libcore.util.HexEncoding;
@@ -434,6 +435,8 @@
                 null, // peerMac (not used in this method)
                 pmk,
                 passphrase,
+                0, // no port info for deprecated IB APIs
+                -1, // no transport info for deprecated IB APIs
                 Process.myUid());
     }
 
@@ -473,6 +476,8 @@
                 peer,
                 pmk,
                 passphrase,
+                0, // no port info for OOB APIs
+                -1, // no transport protocol info for OOB APIs
                 Process.myUid());
     }
 
@@ -824,6 +829,8 @@
         private PeerHandle mPeerHandle;
         private String mPskPassphrase;
         private byte[] mPmk;
+        private int mPort = 0; // invalid value
+        private int mTransportProtocol = -1; // invalid value
 
         /**
          * Configure the {@link PublishDiscoverySession} or {@link SubscribeDiscoverySession}
@@ -902,6 +909,55 @@
         }
 
         /**
+         * Configure the port number which will be used to create a connection over this link. This
+         * configuration should only be done on the server device, e.g. the device creating the
+         * {@link java.net.ServerSocket}.
+         * <p>Notes:
+         * <ul>
+         *     <li>The server device must be the Publisher device!
+         *     <li>The port information can only be specified on secure links, specified using
+         *     {@link #setPskPassphrase(String)}.
+         * </ul>
+         *
+         * @param port A positive integer indicating the port to be used for communication.
+         * @return the current {@link NetworkSpecifierBuilder} builder, enabling chaining of builder
+         *         methods.
+         */
+        public @NonNull NetworkSpecifierBuilder setPort(int port) {
+            if (port <= 0 || port > 65535) {
+                throw new IllegalArgumentException("The port must be a positive value (0, 65535]");
+            }
+            mPort = port;
+            return this;
+        }
+
+        /**
+         * Configure the transport protocol which will be used to create a connection over this
+         * link. This configuration should only be done on the server device, e.g. the device
+         * creating the {@link java.net.ServerSocket} for TCP.
+         * <p>Notes:
+         * <ul>
+         *     <li>The server device must be the Publisher device!
+         *     <li>The transport protocol information can only be specified on secure links,
+         *     specified using {@link #setPskPassphrase(String)}.
+         * </ul>
+         * The transport protocol number is assigned by the Internet Assigned Numbers Authority
+         * (IANA) https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml.
+         *
+         * @param transportProtocol The transport protocol to be used for communication.
+         * @return the current {@link NetworkSpecifierBuilder} builder, enabling chaining of builder
+         *         methods.
+         */
+        public @NonNull NetworkSpecifierBuilder setTransportProtocol(int transportProtocol) {
+            if (transportProtocol < 0 || transportProtocol > 255) {
+                throw new IllegalArgumentException(
+                        "The transport protocol must be in range [0, 255]");
+            }
+            mTransportProtocol = transportProtocol;
+            return this;
+        }
+
+        /**
          * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)}
          * for a WiFi Aware connection (link) to the specified peer. The
          * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
@@ -929,6 +985,18 @@
                     ? WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
                     : WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER;
 
+            if (mPort != 0 || mTransportProtocol != -1) {
+                if (role != WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) {
+                    throw new IllegalStateException(
+                            "Port and transport protocol information can only "
+                                    + "be specified on the Publisher device (which is the server");
+                }
+                if (TextUtils.isEmpty(mPskPassphrase) && mPmk == null) {
+                    throw new IllegalStateException("Port and transport protocol information can "
+                            + "only be specified on a secure link");
+                }
+            }
+
             if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR && mPeerHandle == null) {
                 throw new IllegalStateException("Null peerHandle!?");
             }
@@ -936,7 +1004,7 @@
             return new WifiAwareNetworkSpecifier(
                     WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB, role,
                     mDiscoverySession.mClientId, mDiscoverySession.mSessionId, mPeerHandle.peerId,
-                    null, mPmk, mPskPassphrase, Process.myUid());
+                    null, mPmk, mPskPassphrase, mPort, mTransportProtocol, Process.myUid());
         }
     }
 }
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java b/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java
index 0f29e08..b258906 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java
@@ -38,17 +38,30 @@
  * android.net.NetworkCapabilities)} callback.
  * <p>
  * The Wi-Fi Aware-specific network information include the peer's scoped link-local IPv6 address
- * for the Wi-Fi Aware link. The scoped link-local IPv6 can then be used to create a
+ * for the Wi-Fi Aware link, as well as (optionally) the port and transport protocol specified by
+ * the peer.
+ * The scoped link-local IPv6, port, and transport protocol can then be used to create a
  * {@link java.net.Socket} connection to the peer.
+ * <p>
+ * Note: these are the peer's IPv6 and port information - not the local device's!
  */
 public final class WifiAwareNetworkInfo implements TransportInfo, Parcelable {
     private Inet6Address mIpv6Addr;
+    private int mPort = 0; // a value of 0 is considered invalid
+    private int mTransportProtocol = -1; // a value of -1 is considered invalid
 
     /** @hide */
     public WifiAwareNetworkInfo(Inet6Address ipv6Addr) {
         mIpv6Addr = ipv6Addr;
     }
 
+    /** @hide */
+    public WifiAwareNetworkInfo(Inet6Address ipv6Addr, int port, int transportProtocol) {
+        mIpv6Addr = ipv6Addr;
+        mPort = port;
+        mTransportProtocol = transportProtocol;
+    }
+
     /**
      * Get the scoped link-local IPv6 address of the Wi-Fi Aware peer (not of the local device!).
      *
@@ -59,6 +72,34 @@
         return mIpv6Addr;
     }
 
+    /**
+     * Get the port number to be used to create a network connection to the Wi-Fi Aware peer.
+     * The port information is provided by the app running on the peer which requested the
+     * connection, using the {@link WifiAwareManager.NetworkSpecifierBuilder#setPort(int)}.
+     *
+     * @return A port number on the peer. A value of 0 indicates that no port was specified by the
+     *         peer.
+     */
+    public int getPort() {
+        return mPort;
+    }
+
+    /**
+     * Get the transport protocol to be used to communicate over a network connection to the Wi-Fi
+     * Aware peer. The transport protocol is provided by the app running on the peer which requested
+     * the connection, using the
+     * {@link WifiAwareManager.NetworkSpecifierBuilder#setTransportProtocol(int)}.
+     * <p>
+     * The transport protocol number is assigned by the Internet Assigned Numbers Authority
+     * (IANA) https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml.
+     *
+     * @return A transport protocol id. A value of -1 indicates that no transport protocol was
+     *         specified by the peer.
+     */
+    public int getTransportProtocol() {
+        return mTransportProtocol;
+    }
+
     // parcelable methods
 
     @Override
@@ -71,6 +112,8 @@
         dest.writeByteArray(mIpv6Addr.getAddress());
         NetworkInterface ni = mIpv6Addr.getScopedInterface();
         dest.writeString(ni == null ? null : ni.getName());
+        dest.writeInt(mPort);
+        dest.writeInt(mTransportProtocol);
     }
 
     public static final Creator<WifiAwareNetworkInfo> CREATOR =
@@ -94,8 +137,10 @@
                         e.printStackTrace();
                         return null;
                     }
+                    int port = in.readInt();
+                    int transportProtocol = in.readInt();
 
-                    return new WifiAwareNetworkInfo(ipv6Addr);
+                    return new WifiAwareNetworkInfo(ipv6Addr, port, transportProtocol);
                 }
 
                 @Override
@@ -109,7 +154,9 @@
 
     @Override
     public String toString() {
-        return new StringBuilder("AwareNetworkInfo: IPv6=").append(mIpv6Addr).toString();
+        return new StringBuilder("AwareNetworkInfo: IPv6=").append(mIpv6Addr).append(
+                ", port=").append(mPort).append(", transportProtocol=").append(
+                mTransportProtocol).toString();
     }
 
     /** @hide */
@@ -124,12 +171,13 @@
         }
 
         WifiAwareNetworkInfo lhs = (WifiAwareNetworkInfo) obj;
-        return Objects.equals(mIpv6Addr, lhs.mIpv6Addr);
+        return Objects.equals(mIpv6Addr, lhs.mIpv6Addr) && mPort == lhs.mPort
+                && mTransportProtocol == lhs.mTransportProtocol;
     }
 
     /** @hide */
     @Override
     public int hashCode() {
-        return Objects.hash(mIpv6Addr);
+        return Objects.hash(mIpv6Addr, mPort, mTransportProtocol);
     }
 }
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java b/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
index 6e37fcf..a93a6d5 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
@@ -19,7 +19,6 @@
 import android.net.NetworkSpecifier;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.Log;
 
 import java.util.Arrays;
 import java.util.Objects;
@@ -117,6 +116,32 @@
     public final String passphrase;
 
     /**
+     * The port information to be used for this link. This information will be communicated to the
+     * peer as part of the layer 2 link setup.
+     *
+     * Information only allowed on secure links since a single layer-2 link is set up for all
+     * requestors. Therefore if multiple apps on a single device request links to the same peer
+     * device they all get the same link. However, the link is only set up on the first request -
+     * hence only the first can transmit the port information. But we don't want to expose that
+     * information to other apps. Limiting to secure links would (usually) imply single app usage.
+     *
+     * @hide
+     */
+    public final int port;
+
+    /**
+     * The transport protocol information to be used for this link. This information will be
+     * communicated to the peer as part of the layer 2 link setup.
+     *
+     * Information only allowed on secure links since a single layer-2 link is set up for all
+     * requestors. Therefore if multiple apps on a single device request links to the same peer
+     * device they all get the same link. However, the link is only set up on the first request -
+     * hence only the first can transmit the port information. But we don't want to expose that
+     * information to other apps. Limiting to secure links would (usually) imply single app usage.
+     */
+    public final int transportProtocol;
+
+    /**
      * The UID of the process initializing this network specifier. Validated by receiver using
      * checkUidIfNecessary() and is used by satisfiedBy() to determine whether matches the
      * offered network.
@@ -127,7 +152,8 @@
 
     /** @hide */
     public WifiAwareNetworkSpecifier(int type, int role, int clientId, int sessionId, int peerId,
-            byte[] peerMac, byte[] pmk, String passphrase, int requestorUid) {
+            byte[] peerMac, byte[] pmk, String passphrase, int port, int transportProtocol,
+            int requestorUid) {
         this.type = type;
         this.role = role;
         this.clientId = clientId;
@@ -136,6 +162,8 @@
         this.peerMac = peerMac;
         this.pmk = pmk;
         this.passphrase = passphrase;
+        this.port = port;
+        this.transportProtocol = transportProtocol;
         this.requestorUid = requestorUid;
     }
 
@@ -152,6 +180,8 @@
                         in.createByteArray(), // peerMac
                         in.createByteArray(), // pmk
                         in.readString(), // passphrase
+                        in.readInt(), // port
+                        in.readInt(), // transportProtocol
                         in.readInt()); // requestorUid
                 }
 
@@ -186,6 +216,8 @@
         dest.writeByteArray(peerMac);
         dest.writeByteArray(pmk);
         dest.writeString(passphrase);
+        dest.writeInt(port);
+        dest.writeInt(transportProtocol);
         dest.writeInt(requestorUid);
     }
 
@@ -202,19 +234,8 @@
     /** @hide */
     @Override
     public int hashCode() {
-        int result = 17;
-
-        result = 31 * result + type;
-        result = 31 * result + role;
-        result = 31 * result + clientId;
-        result = 31 * result + sessionId;
-        result = 31 * result + peerId;
-        result = 31 * result + Arrays.hashCode(peerMac);
-        result = 31 * result + Arrays.hashCode(pmk);
-        result = 31 * result + Objects.hashCode(passphrase);
-        result = 31 * result + requestorUid;
-
-        return result;
+        return Objects.hash(type, role, clientId, sessionId, peerId, Arrays.hashCode(peerMac),
+                Arrays.hashCode(pmk), passphrase, port, transportProtocol, requestorUid);
     }
 
     /** @hide */
@@ -238,6 +259,8 @@
                 && Arrays.equals(peerMac, lhs.peerMac)
                 && Arrays.equals(pmk, lhs.pmk)
                 && Objects.equals(passphrase, lhs.passphrase)
+                && port == lhs.port
+                && transportProtocol == lhs.transportProtocol
                 && requestorUid == lhs.requestorUid;
     }
 
@@ -256,7 +279,8 @@
                 .append(", pmk=").append((pmk == null) ? "<null>" : "<non-null>")
                 // masking PII
                 .append(", passphrase=").append((passphrase == null) ? "<null>" : "<non-null>")
-                .append(", requestorUid=").append(requestorUid)
+                .append(", port=").append(port).append(", transportProtocol=")
+                .append(transportProtocol).append(", requestorUid=").append(requestorUid)
                 .append("]");
         return sb.toString();
     }
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
index 7689fc3..59a7290 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
@@ -572,7 +572,7 @@
 
         @Override
         public int hashCode() {
-            return Objects.hash(mCertType, mCertSha256Fingerprint);
+            return Objects.hash(mCertType, Arrays.hashCode(mCertSha256Fingerprint));
         }
 
         @Override
@@ -842,24 +842,50 @@
     }
 
     /**
-     * CA (Certificate Authority) X509 certificate.
+     * CA (Certificate Authority) X509 certificates.
      */
-    private X509Certificate mCaCertificate = null;
+    private X509Certificate[] mCaCertificates = null;
+
     /**
      * Set the CA (Certification Authority) certificate associated with this credential.
      *
      * @param caCertificate The CA certificate to set to
      */
     public void setCaCertificate(X509Certificate caCertificate) {
-        mCaCertificate = caCertificate;
+        mCaCertificates = null;
+        if (caCertificate != null) {
+            mCaCertificates = new X509Certificate[] {caCertificate};
+        }
     }
+
+    /**
+     * Set the CA (Certification Authority) certificates associated with this credential.
+     *
+     * @param caCertificates The list of CA certificates to set to
+     * @hide
+     */
+    public void setCaCertificates(X509Certificate[] caCertificates) {
+        mCaCertificates = caCertificates;
+    }
+
     /**
      * Get the CA (Certification Authority) certificate associated with this credential.
      *
-     * @return CA certificate associated with this credential
+     * @return CA certificate associated with this credential, {@code null} if certificate is not
+     * set or certificate is more than one.
      */
     public X509Certificate getCaCertificate() {
-        return mCaCertificate;
+        return mCaCertificates == null || mCaCertificates.length > 1 ? null : mCaCertificates[0];
+    }
+
+    /**
+     * Get the CA (Certification Authority) certificates associated with this credential.
+     *
+     * @return The list of CA certificates associated with this credential
+     * @hide
+     */
+    public X509Certificate[] getCaCertificates() {
+        return mCaCertificates;
     }
 
     /**
@@ -933,7 +959,11 @@
                 mClientCertificateChain = Arrays.copyOf(source.mClientCertificateChain,
                                                         source.mClientCertificateChain.length);
             }
-            mCaCertificate = source.mCaCertificate;
+            if (source.mCaCertificates != null) {
+                mCaCertificates = Arrays.copyOf(source.mCaCertificates,
+                        source.mCaCertificates.length);
+            }
+
             mClientPrivateKey = source.mClientPrivateKey;
         }
     }
@@ -952,7 +982,7 @@
         dest.writeParcelable(mUserCredential, flags);
         dest.writeParcelable(mCertCredential, flags);
         dest.writeParcelable(mSimCredential, flags);
-        ParcelUtil.writeCertificate(dest, mCaCertificate);
+        ParcelUtil.writeCertificates(dest, mCaCertificates);
         ParcelUtil.writeCertificates(dest, mClientCertificateChain);
         ParcelUtil.writePrivateKey(dest, mClientPrivateKey);
     }
@@ -977,16 +1007,17 @@
                     : mCertCredential.equals(that.mCertCredential))
                 && (mSimCredential == null ? that.mSimCredential == null
                     : mSimCredential.equals(that.mSimCredential))
-                && isX509CertificateEquals(mCaCertificate, that.mCaCertificate)
+                && isX509CertificatesEquals(mCaCertificates, that.mCaCertificates)
                 && isX509CertificatesEquals(mClientCertificateChain, that.mClientCertificateChain)
                 && isPrivateKeyEquals(mClientPrivateKey, that.mClientPrivateKey);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mRealm, mCreationTimeInMillis, mExpirationTimeInMillis,
+        return Objects.hash(mCreationTimeInMillis, mExpirationTimeInMillis, mRealm,
                 mCheckAaaServerCertStatus, mUserCredential, mCertCredential, mSimCredential,
-                mCaCertificate, mClientCertificateChain, mClientPrivateKey);
+                mClientPrivateKey, Arrays.hashCode(mCaCertificates),
+                Arrays.hashCode(mClientCertificateChain));
     }
 
     @Override
@@ -1067,7 +1098,7 @@
                 credential.setUserCredential(in.readParcelable(null));
                 credential.setCertCredential(in.readParcelable(null));
                 credential.setSimCredential(in.readParcelable(null));
-                credential.setCaCertificate(ParcelUtil.readCertificate(in));
+                credential.setCaCertificates(ParcelUtil.readCertificates(in));
                 credential.setClientCertificateChain(ParcelUtil.readCertificates(in));
                 credential.setClientPrivateKey(ParcelUtil.readPrivateKey(in));
                 return credential;
@@ -1100,7 +1131,7 @@
 
         // CA certificate is required for R1 Passpoint profile.
         // For R2, it is downloaded using cert URL provided in PPS MO after validation completes.
-        if (isR1 && mCaCertificate == null) {
+        if (isR1 && mCaCertificates == null) {
             Log.d(TAG, "Missing CA Certificate for user credential");
             return false;
         }
@@ -1131,7 +1162,7 @@
         // Verify required key and certificates for certificate credential.
         // CA certificate is required for R1 Passpoint profile.
         // For R2, it is downloaded using cert URL provided in PPS MO after validation completes.
-        if (isR1 && mCaCertificate == null) {
+        if (isR1 && mCaCertificates == null) {
             Log.d(TAG, "Missing CA Certificate for certificate credential");
             return false;
         }
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java
index 4189e40..c3b6285 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java
@@ -62,6 +62,7 @@
                 WifiAwareAgentNetworkSpecifier.CREATOR.createFromParcel(parcelR);
 
         assertEquals(dut, rereadDut);
+        assertEquals(dut.hashCode(), rereadDut.hashCode());
 
         // Ensure that individual network specifiers are satisfied by both the original & marshaled
         // |WifiAwareNetworkAgentSpecifier instances.
@@ -181,6 +182,6 @@
     WifiAwareNetworkSpecifier getDummyNetworkSpecifier(int clientId) {
         return new WifiAwareNetworkSpecifier(WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB,
                 WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR, clientId, 0, 0, new byte[6],
-                null, null, 0);
+                null, null, 10, 5, 0);
     }
 }
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
index ed38c76..6da6d4a 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
@@ -16,8 +16,11 @@
 
 package android.net.wifi.aware;
 
+import static android.net.wifi.aware.WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB;
+
 import static org.hamcrest.core.IsEqual.equalTo;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -913,9 +916,10 @@
         final int clientId = 4565;
         final int sessionId = 123;
         final PeerHandle peerHandle = new PeerHandle(123412);
-        final int role = WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER;
         final byte[] pmk = PMK_VALID;
         final String passphrase = PASSPHRASE_VALID;
+        final int port = 5;
+        final int transportProtocol = 10;
         final ConfigRequest configRequest = new ConfigRequest.Builder().build();
         final PublishConfig publishConfig = new PublishConfig.Builder().build();
 
@@ -959,56 +963,70 @@
                 .setPeerHandle(peerHandle).build();
 
         // validate format
-        collector.checkThat("role", role, equalTo(ns.role));
+        collector.checkThat("role", WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER,
+                equalTo(ns.role));
         collector.checkThat("client_id", clientId, equalTo(ns.clientId));
         collector.checkThat("session_id", sessionId, equalTo(ns.sessionId));
         collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId));
 
-        collector.checkThat("role", role, equalTo(nsb.role));
+        collector.checkThat("role", WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER,
+                equalTo(nsb.role));
         collector.checkThat("client_id", clientId, equalTo(nsb.clientId));
         collector.checkThat("session_id", sessionId, equalTo(nsb.sessionId));
         collector.checkThat("peer_id", peerHandle.peerId, equalTo(nsb.peerId));
+        collector.checkThat("port", 0, equalTo(nsb.port));
+        collector.checkThat("transportProtocol", -1, equalTo(nsb.transportProtocol));
 
         // (4) request an encrypted (PMK) network specifier from the session
         ns = (WifiAwareNetworkSpecifier) publishSession.getValue().createNetworkSpecifierPmk(
                 peerHandle, pmk);
-        nsb =
-                (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
-                        .setDiscoverySession(
-                        publishSession.getValue()).setPeerHandle(peerHandle).setPmk(pmk).build();
+        nsb = (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+                .setDiscoverySession(publishSession.getValue()).setPeerHandle(peerHandle)
+                .setPmk(pmk).setPort(port).setTransportProtocol(transportProtocol).build();
 
         // validate format
-        collector.checkThat("role", role, equalTo(ns.role));
+        collector.checkThat("role", WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER,
+                equalTo(ns.role));
         collector.checkThat("client_id", clientId, equalTo(ns.clientId));
         collector.checkThat("session_id", sessionId, equalTo(ns.sessionId));
         collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId));
         collector.checkThat("pmk", pmk , equalTo(ns.pmk));
 
-        collector.checkThat("role", role, equalTo(nsb.role));
+        collector.checkThat("role", WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER,
+                equalTo(nsb.role));
         collector.checkThat("client_id", clientId, equalTo(nsb.clientId));
         collector.checkThat("session_id", sessionId, equalTo(nsb.sessionId));
         collector.checkThat("peer_id", peerHandle.peerId, equalTo(nsb.peerId));
         collector.checkThat("pmk", pmk , equalTo(nsb.pmk));
+        collector.checkThat("port", port, equalTo(nsb.port));
+        collector.checkThat("transportProtocol", transportProtocol, equalTo(nsb.transportProtocol));
 
         // (5) request an encrypted (Passphrase) network specifier from the session
-        ns = (WifiAwareNetworkSpecifier) publishSession.getValue().createNetworkSpecifierPassphrase(
-                peerHandle, passphrase);
+        ns =
+                (WifiAwareNetworkSpecifier) publishSession.getValue()
+                        .createNetworkSpecifierPassphrase(
+                        peerHandle, passphrase);
         nsb = (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
                 .setDiscoverySession(publishSession.getValue()).setPeerHandle(peerHandle)
-                .setPskPassphrase(passphrase).build();
+                .setPskPassphrase(passphrase).setPort(port).setTransportProtocol(transportProtocol)
+                .build();
 
         // validate format
-        collector.checkThat("role", role, equalTo(ns.role));
+        collector.checkThat("role", WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER,
+                equalTo(ns.role));
         collector.checkThat("client_id", clientId, equalTo(ns.clientId));
         collector.checkThat("session_id", sessionId, equalTo(ns.sessionId));
         collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId));
         collector.checkThat("passphrase", passphrase, equalTo(ns.passphrase));
 
-        collector.checkThat("role", role, equalTo(nsb.role));
+        collector.checkThat("role", WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER,
+                equalTo(nsb.role));
         collector.checkThat("client_id", clientId, equalTo(nsb.clientId));
         collector.checkThat("session_id", sessionId, equalTo(nsb.sessionId));
         collector.checkThat("peer_id", peerHandle.peerId, equalTo(nsb.peerId));
         collector.checkThat("passphrase", passphrase, equalTo(nsb.passphrase));
+        collector.checkThat("port", port, equalTo(nsb.port));
+        collector.checkThat("transportProtocol", transportProtocol, equalTo(nsb.transportProtocol));
 
         verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService,
                 mockPublishSession, mockRttListener);
@@ -1325,6 +1343,140 @@
         executeNetworkSpecifierDirect(null, false, null, PASSPHRASE_VALID, false);
     }
 
+    /**
+     * Validate that get an exception when creating a network specifier with an invalid port number
+     * (<=0).
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testNetworkSpecifierBuilderInvalidPortNumber() throws Exception {
+        final PeerHandle peerHandle = new PeerHandle(123412);
+        final byte[] pmk = PMK_VALID;
+        final int port = 0;
+
+        DiscoverySession publishSession = executeSessionStartup(true);
+
+        WifiAwareNetworkSpecifier nsb =
+                (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+                        .setDiscoverySession(publishSession).setPeerHandle(peerHandle)
+                        .setPmk(pmk).setPort(port).build();
+    }
+
+    /**
+     * Validate that get an exception when creating a network specifier with port information
+     * without also requesting a secure link.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testNetworkSpecifierBuilderInvalidPortOnInsecure() throws Exception {
+        final PeerHandle peerHandle = new PeerHandle(123412);
+        final int port = 5;
+
+        DiscoverySession publishSession = executeSessionStartup(true);
+
+        WifiAwareNetworkSpecifier nsb =
+                (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+                        .setDiscoverySession(publishSession).setPeerHandle(peerHandle)
+                        .setPort(port).build();
+    }
+
+    /**
+     * Validate that get an exception when creating a network specifier with port information on
+     * a responder.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testNetworkSpecifierBuilderInvalidPortOnResponder() throws Exception {
+        final PeerHandle peerHandle = new PeerHandle(123412);
+        final int port = 5;
+
+        DiscoverySession subscribeSession = executeSessionStartup(false);
+
+        WifiAwareNetworkSpecifier nsb =
+                (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+                        .setDiscoverySession(subscribeSession).setPeerHandle(peerHandle)
+                        .setPort(port).build();
+    }
+
+    /**
+     * Validate that get an exception when creating a network specifier with an invalid transport
+     * protocol number (not in [0, 255]).
+     */
+    @Test
+    public void testNetworkSpecifierBuilderInvalidTransportProtocolNumber() throws Exception {
+        final PeerHandle peerHandle = new PeerHandle(123412);
+        final byte[] pmk = PMK_VALID;
+        final int tpNegative = -1;
+        final int tpTooLarge = 256;
+        final int tpSmallest = 0;
+        final int tpLargest = 255;
+
+        DiscoverySession publishSession = executeSessionStartup(true);
+
+        try {
+            WifiAwareNetworkSpecifier nsb =
+                    (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+                            .setDiscoverySession(publishSession).setPeerHandle(peerHandle)
+                            .setPmk(pmk).setTransportProtocol(tpNegative).build();
+            assertTrue("No exception on negative transport protocol!", false);
+        } catch (IllegalArgumentException e) {
+            // nop - exception is correct!
+        }
+        try {
+            WifiAwareNetworkSpecifier nsb =
+                    (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+                            .setDiscoverySession(publishSession).setPeerHandle(peerHandle)
+                            .setPmk(pmk).setTransportProtocol(tpTooLarge).build();
+            assertTrue("No exception on >255 transport protocol!", false);
+        } catch (IllegalArgumentException e) {
+            // nop - exception is correct!
+        }
+        WifiAwareNetworkSpecifier nsb =
+                (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+                        .setDiscoverySession(publishSession).setPeerHandle(peerHandle)
+                        .setPmk(pmk).setTransportProtocol(tpSmallest).build();
+        nsb =
+                (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+                        .setDiscoverySession(
+                                publishSession).setPeerHandle(peerHandle).setPmk(
+                        pmk).setTransportProtocol(tpLargest).build();
+    }
+
+    /**
+     * Validate that get an exception when creating a network specifier with transport protocol
+     * information without also requesting a secure link.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testNetworkSpecifierBuilderInvalidTransportProtocolOnInsecure() throws Exception {
+        final PeerHandle peerHandle = new PeerHandle(123412);
+        final int transportProtocol = 5;
+
+        DiscoverySession publishSession = executeSessionStartup(true);
+
+        WifiAwareNetworkSpecifier nsb =
+                (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+                        .setDiscoverySession(publishSession).setPeerHandle(peerHandle)
+                        .setTransportProtocol(transportProtocol).build();
+    }
+
+    /**
+     * Validate that get an exception when creating a network specifier with transport protocol
+     * information on a responder.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testNetworkSpecifierBuilderInvalidTransportProtocolOnResponder() throws Exception {
+        final PeerHandle peerHandle = new PeerHandle(123412);
+        final int transportProtocol = 5;
+
+        DiscoverySession subscribeSession = executeSessionStartup(false);
+
+        WifiAwareNetworkSpecifier nsb =
+                (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+                        .setDiscoverySession(subscribeSession).setPeerHandle(peerHandle)
+                        .setTransportProtocol(transportProtocol).build();
+    }
+
+    /*
+     * Utilities
+     */
+
     private void executeNetworkSpecifierDirect(byte[] someMac, boolean doPmk, byte[] pmk,
             String passphrase, boolean doInitiator) throws Exception {
         final int clientId = 134;
@@ -1356,7 +1508,83 @@
         }
     }
 
-    // WifiAwareNetworkInfo tests
+    private DiscoverySession executeSessionStartup(boolean isPublish) throws Exception {
+        final int clientId = 4565;
+        final int sessionId = 123;
+        final PeerHandle peerHandle = new PeerHandle(123412);
+        final int port = 5;
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+        final PublishConfig publishConfig = new PublishConfig.Builder().build();
+
+        ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
+                WifiAwareSession.class);
+        ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiAwareEventCallback.class);
+        ArgumentCaptor<IWifiAwareDiscoverySessionCallback> sessionProxyCallback = ArgumentCaptor
+                .forClass(IWifiAwareDiscoverySessionCallback.class);
+        ArgumentCaptor<PublishDiscoverySession> publishSession = ArgumentCaptor
+                .forClass(PublishDiscoverySession.class);
+        ArgumentCaptor<SubscribeDiscoverySession> subscribeSession = ArgumentCaptor
+                .forClass(SubscribeDiscoverySession.class);
+
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockAwareService,
+                mockPublishSession, mockRttListener);
+
+        // (1) connect successfully
+        mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
+        inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+                eq(configRequest), eq(false));
+        clientProxyCallback.getValue().onConnectSuccess(clientId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
+        WifiAwareSession session = sessionCaptor.getValue();
+
+        if (isPublish) {
+            // (2) publish successfully
+            session.publish(publishConfig, mockSessionCallback, mMockLooperHandler);
+            inOrder.verify(mockAwareService).publish(any(), eq(clientId), eq(publishConfig),
+                    sessionProxyCallback.capture());
+            sessionProxyCallback.getValue().onSessionStarted(sessionId);
+            mMockLooper.dispatchAll();
+            inOrder.verify(mockSessionCallback).onPublishStarted(publishSession.capture());
+            return publishSession.getValue();
+        } else {
+            // (2) subscribe successfully
+            session.subscribe(subscribeConfig, mockSessionCallback, mMockLooperHandler);
+            inOrder.verify(mockAwareService).subscribe(any(), eq(clientId), eq(subscribeConfig),
+                    sessionProxyCallback.capture());
+            sessionProxyCallback.getValue().onSessionStarted(sessionId);
+            mMockLooper.dispatchAll();
+            inOrder.verify(mockSessionCallback).onSubscribeStarted(subscribeSession.capture());
+            return subscribeSession.getValue();
+        }
+    }
+
+    // WifiAwareNetworkSpecifier && WifiAwareNetworkInfo tests
+
+    @Test
+    public void testWifiAwareNetworkSpecifierParcel() {
+        WifiAwareNetworkSpecifier ns = new WifiAwareNetworkSpecifier(NETWORK_SPECIFIER_TYPE_IB,
+                WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER, 5, 568, 334,
+                HexEncoding.decode("000102030405".toCharArray(), false),
+                "01234567890123456789012345678901".getBytes(), "blah blah", 666, 4, 10001);
+
+        Parcel parcelW = Parcel.obtain();
+        ns.writeToParcel(parcelW, 0);
+        byte[] bytes = parcelW.marshall();
+        parcelW.recycle();
+
+        Parcel parcelR = Parcel.obtain();
+        parcelR.unmarshall(bytes, 0, bytes.length);
+        parcelR.setDataPosition(0);
+        WifiAwareNetworkSpecifier rereadNs =
+                WifiAwareNetworkSpecifier.CREATOR.createFromParcel(parcelR);
+
+        assertEquals(ns, rereadNs);
+        assertEquals(ns.hashCode(), rereadNs.hashCode());
+    }
 
     @Test
     public void testWifiAwareNetworkCapabilitiesParcel() throws UnknownHostException {
@@ -1364,9 +1592,11 @@
                 "11:22:33:44:55:66").getLinkLocalIpv6FromEui48Mac();
         // note: dummy scope = 5
         final Inet6Address inet6Scoped = Inet6Address.getByAddress(null, inet6.getAddress(), 5);
+        final int port = 5;
+        final int transportProtocol = 6;
 
         assertEquals(inet6Scoped.toString(), "/fe80::1322:33ff:fe44:5566%5");
-        WifiAwareNetworkInfo cap = new WifiAwareNetworkInfo(inet6Scoped);
+        WifiAwareNetworkInfo cap = new WifiAwareNetworkInfo(inet6Scoped, port, transportProtocol);
 
         Parcel parcelW = Parcel.obtain();
         cap.writeToParcel(parcelW, 0);
@@ -1380,6 +1610,7 @@
                 WifiAwareNetworkInfo.CREATOR.createFromParcel(parcelR);
 
         assertEquals(cap.getPeerIpv6Addr().toString(), "/fe80::1322:33ff:fe44:5566%5");
+        assertEquals(cap, rereadCap);
         assertEquals(cap.hashCode(), rereadCap.hashCode());
     }
 
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
index a9d4b8f..1ecc3fe 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
@@ -16,6 +16,7 @@
 
 package android.net.wifi.hotspot2.pps;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
@@ -44,17 +45,16 @@
      * @param userCred Instance of UserCredential
      * @param certCred Instance of CertificateCredential
      * @param simCred Instance of SimCredential
-     * @param caCert CA certificate
      * @param clientCertificateChain Chain of client certificates
      * @param clientPrivateKey Client private key
+     * @param caCerts CA certificates
      * @return {@link Credential}
      */
     private static Credential createCredential(Credential.UserCredential userCred,
-                                               Credential.CertificateCredential certCred,
-                                               Credential.SimCredential simCred,
-                                               X509Certificate caCert,
-                                               X509Certificate[] clientCertificateChain,
-                                               PrivateKey clientPrivateKey) {
+            Credential.CertificateCredential certCred,
+            Credential.SimCredential simCred,
+            X509Certificate[] clientCertificateChain, PrivateKey clientPrivateKey,
+            X509Certificate... caCerts) {
         Credential cred = new Credential();
         cred.setCreationTimeInMillis(123455L);
         cred.setExpirationTimeInMillis(2310093L);
@@ -63,7 +63,11 @@
         cred.setUserCredential(userCred);
         cred.setCertCredential(certCred);
         cred.setSimCredential(simCred);
-        cred.setCaCertificate(caCert);
+        if (caCerts != null && caCerts.length == 1) {
+            cred.setCaCertificate(caCerts[0]);
+        } else {
+            cred.setCaCertificates(caCerts);
+        }
         cred.setClientCertificateChain(clientCertificateChain);
         cred.setClientPrivateKey(clientPrivateKey);
         return cred;
@@ -80,8 +84,8 @@
         certCred.setCertType("x509v3");
         certCred.setCertSha256Fingerprint(
                 MessageDigest.getInstance("SHA-256").digest(FakeKeys.CLIENT_CERT.getEncoded()));
-        return createCredential(null, certCred, null, FakeKeys.CA_CERT0,
-                new X509Certificate[] {FakeKeys.CLIENT_CERT}, FakeKeys.RSA_KEY1);
+        return createCredential(null, certCred, null, new X509Certificate[] {FakeKeys.CLIENT_CERT},
+                FakeKeys.RSA_KEY1, FakeKeys.CA_CERT0, FakeKeys.CA_CERT1);
     }
 
     /**
@@ -93,7 +97,7 @@
         Credential.SimCredential simCred = new Credential.SimCredential();
         simCred.setImsi("1234*");
         simCred.setEapType(EAPConstants.EAP_SIM);
-        return createCredential(null, null, simCred, null, null, null);
+        return createCredential(null, null, simCred, null, null, (X509Certificate[]) null);
     }
 
     /**
@@ -110,7 +114,7 @@
         userCred.setSoftTokenApp("TestApp");
         userCred.setEapType(EAPConstants.EAP_TTLS);
         userCred.setNonEapInnerMethod("MS-CHAP");
-        return createCredential(userCred, null, null, FakeKeys.CA_CERT0, null, null);
+        return createCredential(userCred, null, null, null, null, FakeKeys.CA_CERT0);
     }
 
     private static void verifyParcel(Credential writeCred) {
@@ -120,6 +124,7 @@
         parcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
         Credential readCred = Credential.CREATOR.createFromParcel(parcel);
         assertTrue(readCred.equals(writeCred));
+        assertEquals(writeCred.hashCode(), readCred.hashCode());
     }
 
     /**