Merge "Move ShadeController to SystemUIFactory to allow customization."
diff --git a/api/current.txt b/api/current.txt
index fad607c..29d459e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11598,8 +11598,11 @@
     field public static final java.lang.String FEATURE_MICROPHONE = "android.hardware.microphone";
     field public static final java.lang.String FEATURE_MIDI = "android.software.midi";
     field public static final java.lang.String FEATURE_NFC = "android.hardware.nfc";
+    field public static final java.lang.String FEATURE_NFC_BEAM = "android.sofware.nfc.beam";
     field public static final java.lang.String FEATURE_NFC_HOST_CARD_EMULATION = "android.hardware.nfc.hce";
     field public static final java.lang.String FEATURE_NFC_HOST_CARD_EMULATION_NFCF = "android.hardware.nfc.hcef";
+    field public static final java.lang.String FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE = "android.hardware.nfc.ese";
+    field public static final java.lang.String FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC = "android.hardware.nfc.uicc";
     field public static final java.lang.String FEATURE_OPENGLES_EXTENSION_PACK = "android.hardware.opengles.aep";
     field public static final java.lang.String FEATURE_PC = "android.hardware.type.pc";
     field public static final java.lang.String FEATURE_PICTURE_IN_PICTURE = "android.software.picture_in_picture";
@@ -29751,7 +29754,10 @@
     method public void onIdentityChanged(byte[]);
   }
 
-  public class PeerHandle {
+  public final class PeerHandle 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.net.wifi.aware.PeerHandle> CREATOR;
   }
 
   public final class PublishConfig implements android.os.Parcelable {
@@ -30286,15 +30292,16 @@
     method public deprecated void enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage);
     method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
     method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
+    method public java.util.List<java.lang.String> getSupportedOffHostSecureElements();
     method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
-    method public boolean invokeBeam(android.app.Activity);
+    method public deprecated boolean invokeBeam(android.app.Activity);
     method public boolean isEnabled();
-    method public boolean isNdefPushEnabled();
-    method public void setBeamPushUris(android.net.Uri[], android.app.Activity);
-    method public void setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity);
-    method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...);
-    method public void setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...);
-    method public void setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...);
+    method public deprecated boolean isNdefPushEnabled();
+    method public deprecated void setBeamPushUris(android.net.Uri[], android.app.Activity);
+    method public deprecated void setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity);
+    method public deprecated void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...);
+    method public deprecated void setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...);
+    method public deprecated void setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...);
     field public static final java.lang.String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
     field public static final java.lang.String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
     field public static final java.lang.String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED";
@@ -30321,15 +30328,15 @@
     field public static final int STATE_TURNING_ON = 2; // 0x2
   }
 
-  public static abstract interface NfcAdapter.CreateBeamUrisCallback {
+  public static abstract deprecated interface NfcAdapter.CreateBeamUrisCallback {
     method public abstract android.net.Uri[] createBeamUris(android.nfc.NfcEvent);
   }
 
-  public static abstract interface NfcAdapter.CreateNdefMessageCallback {
+  public static abstract deprecated interface NfcAdapter.CreateNdefMessageCallback {
     method public abstract android.nfc.NdefMessage createNdefMessage(android.nfc.NfcEvent);
   }
 
-  public static abstract interface NfcAdapter.OnNdefPushCompleteCallback {
+  public static abstract deprecated interface NfcAdapter.OnNdefPushCompleteCallback {
     method public abstract void onNdefPushComplete(android.nfc.NfcEvent);
   }
 
@@ -30377,8 +30384,10 @@
     method public boolean isDefaultServiceForCategory(android.content.ComponentName, java.lang.String);
     method public boolean registerAidsForService(android.content.ComponentName, java.lang.String, java.util.List<java.lang.String>);
     method public boolean removeAidsForService(android.content.ComponentName, java.lang.String);
+    method public boolean setOffHostForService(android.content.ComponentName, java.lang.String);
     method public boolean setPreferredService(android.app.Activity, android.content.ComponentName);
     method public boolean supportsAidPrefixRegistration();
+    method public boolean unsetOffHostForService(android.content.ComponentName);
     method public boolean unsetPreferredService(android.app.Activity);
     field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
     field public static final java.lang.String CATEGORY_OTHER = "other";
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b72089f..2aeb68d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1939,6 +1939,30 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device supports uicc-
+     * based NFC card emulation.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC =
+                                                                       "android.hardware.nfc.uicc";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device supports eSE-
+     * based NFC card emulation.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE = "android.hardware.nfc.ese";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The Beam API is enabled on the device.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_NFC_BEAM = "android.sofware.nfc.beam";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device supports any
      * one of the {@link #FEATURE_NFC}, {@link #FEATURE_NFC_HOST_CARD_EMULATION},
      * or {@link #FEATURE_NFC_HOST_CARD_EMULATION_NFCF} features.
diff --git a/core/java/android/nfc/INfcCardEmulation.aidl b/core/java/android/nfc/INfcCardEmulation.aidl
index d1b132c..dd2c0d4 100644
--- a/core/java/android/nfc/INfcCardEmulation.aidl
+++ b/core/java/android/nfc/INfcCardEmulation.aidl
@@ -31,6 +31,8 @@
     boolean setDefaultServiceForCategory(int userHandle, in ComponentName service, String category);
     boolean setDefaultForNextTap(int userHandle, in ComponentName service);
     boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup);
+    boolean setOffHostForService(int userHandle, in ComponentName service, in String offHostSecureElement);
+    boolean unsetOffHostForService(int userHandle, in ComponentName service);
     AidGroup getAidGroupForService(int userHandle, in ComponentName service, String category);
     boolean removeAidGroupForService(int userHandle, in ComponentName service, String category);
     List<ApduServiceInfo> getServices(int userHandle, in String category);
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 21fed48..e55e036 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -16,6 +16,7 @@
 
 package android.nfc;
 
+import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
@@ -42,7 +43,9 @@
 import android.util.Log;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 
 /**
  * Represents the local NFC adapter.
@@ -322,6 +325,7 @@
     // Guarded by NfcAdapter.class
     static boolean sIsInitialized = false;
     static boolean sHasNfcFeature;
+    static boolean sHasBeamFeature;
 
     // Final after first constructor, except for
     // attemptDeadServiceRecovery() when NFC crashes - we accept a best effort
@@ -369,7 +373,9 @@
      * A callback to be invoked when the system successfully delivers your {@link NdefMessage}
      * to another device.
      * @see #setOnNdefPushCompleteCallback
+     * @deprecated this feature is deprecated.
      */
+    @java.lang.Deprecated
     public interface OnNdefPushCompleteCallback {
         /**
          * Called on successful NDEF push.
@@ -392,7 +398,9 @@
      * content currently visible to the user. Alternatively, you can call {@link
      * #setNdefPushMessage setNdefPushMessage()} if the {@link NdefMessage} always contains the
      * same data.
+     * @deprecated this feature is deprecated.
      */
+    @java.lang.Deprecated
     public interface CreateNdefMessageCallback {
         /**
          * Called to provide a {@link NdefMessage} to push.
@@ -418,7 +426,10 @@
     }
 
 
-    // TODO javadoc
+     /**
+     * @deprecated this feature is deprecated.
+     */
+    @java.lang.Deprecated
     public interface CreateBeamUrisCallback {
         public Uri[] createBeamUris(NfcEvent event);
     }
@@ -446,6 +457,25 @@
         public boolean onUnlockAttempted(Tag tag);
     }
 
+    /**
+     * Helper to check if this device has FEATURE_NFC_BEAM, but without using
+     * a context.
+     * Equivalent to
+     * context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC_BEAM)
+     */
+    private static boolean hasBeamFeature() {
+        IPackageManager pm = ActivityThread.getPackageManager();
+        if (pm == null) {
+            Log.e(TAG, "Cannot get package manager, assuming no Android Beam feature");
+            return false;
+        }
+        try {
+            return pm.hasSystemFeature(PackageManager.FEATURE_NFC_BEAM, 0);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Package manager query failed, assuming no Android Beam feature", e);
+            return false;
+        }
+    }
 
     /**
      * Helper to check if this device has FEATURE_NFC, but without using
@@ -488,6 +518,35 @@
     }
 
     /**
+     * Return list of Secure Elements which support off host card emulation.
+     *
+     * @return List<String> containing secure elements on the device which supports
+     *                      off host card emulation. eSE for Embedded secure element,
+     *                      SIM for UICC and so on.
+     */
+    public @NonNull List<String> getSupportedOffHostSecureElements() {
+        List<String> offHostSE = new ArrayList<String>();
+        IPackageManager pm = ActivityThread.getPackageManager();
+        if (pm == null) {
+            Log.e(TAG, "Cannot get package manager, assuming no off-host CE feature");
+            return offHostSE;
+        }
+        try {
+            if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC, 0)) {
+                offHostSE.add("SIM");
+            }
+            if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE, 0)) {
+                offHostSE.add("eSE");
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Package manager query failed, assuming no off-host CE feature", e);
+            offHostSE.clear();
+            return offHostSE;
+        }
+        return offHostSE;
+    }
+
+    /**
      * Returns the NfcAdapter for application context,
      * or throws if NFC is not available.
      * @hide
@@ -496,6 +555,7 @@
     public static synchronized NfcAdapter getNfcAdapter(Context context) {
         if (!sIsInitialized) {
             sHasNfcFeature = hasNfcFeature();
+            sHasBeamFeature = hasBeamFeature();
             boolean hasHceFeature = hasNfcHceFeature();
             /* is this device meant to have NFC */
             if (!sHasNfcFeature && !hasHceFeature) {
@@ -921,12 +981,17 @@
      * @param uris an array of Uri(s) to push over Android Beam
      * @param activity activity for which the Uri(s) will be pushed
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     * @deprecated this feature is deprecated.
      */
+    @java.lang.Deprecated
     public void setBeamPushUris(Uri[] uris, Activity activity) {
         synchronized (NfcAdapter.class) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
+            if (!sHasBeamFeature) {
+                return;
+            }
         }
         if (activity == null) {
             throw new NullPointerException("activity cannot be null");
@@ -1003,12 +1068,17 @@
      * @param callback callback, or null to disable
      * @param activity activity for which the Uri(s) will be pushed
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     * @deprecated this feature is deprecated.
      */
+    @java.lang.Deprecated
     public void setBeamPushUrisCallback(CreateBeamUrisCallback callback, Activity activity) {
         synchronized (NfcAdapter.class) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
+            if (!sHasBeamFeature) {
+                return;
+            }
         }
         if (activity == null) {
             throw new NullPointerException("activity cannot be null");
@@ -1087,13 +1157,18 @@
      *        to only register one at a time, and to do so in that activity's
      *        {@link Activity#onCreate}
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     * @deprecated this feature is deprecated.
      */
+    @java.lang.Deprecated
     public void setNdefPushMessage(NdefMessage message, Activity activity,
             Activity ... activities) {
         synchronized (NfcAdapter.class) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
+            if (!sHasBeamFeature) {
+                return;
+            }
         }
         int targetSdkVersion = getSdkVersion();
         try {
@@ -1200,13 +1275,18 @@
      *        to only register one at a time, and to do so in that activity's
      *        {@link Activity#onCreate}
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     * @deprecated this feature is deprecated.
      */
+    @java.lang.Deprecated
     public void setNdefPushMessageCallback(CreateNdefMessageCallback callback, Activity activity,
             Activity ... activities) {
         synchronized (NfcAdapter.class) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
+            if (!sHasBeamFeature) {
+                return;
+            }
         }
         int targetSdkVersion = getSdkVersion();
         try {
@@ -1281,13 +1361,18 @@
      *        to only register one at a time, and to do so in that activity's
      *        {@link Activity#onCreate}
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     * @deprecated this feature is deprecated.
      */
+    @java.lang.Deprecated
     public void setOnNdefPushCompleteCallback(OnNdefPushCompleteCallback callback,
             Activity activity, Activity ... activities) {
         synchronized (NfcAdapter.class) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
+            if (!sHasBeamFeature) {
+                return;
+            }
         }
         int targetSdkVersion = getSdkVersion();
         try {
@@ -1492,12 +1577,17 @@
      * @param activity the current foreground Activity that has registered data to share
      * @return whether the Beam animation was successfully invoked
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     * @deprecated this feature is deprecated.
      */
+    @java.lang.Deprecated
     public boolean invokeBeam(Activity activity) {
         synchronized (NfcAdapter.class) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
+            if (!sHasBeamFeature) {
+                return false;
+            }
         }
         if (activity == null) {
             throw new NullPointerException("activity may not be null.");
@@ -1561,6 +1651,9 @@
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
+            if (!sHasBeamFeature) {
+                return;
+            }
         }
         if (activity == null || message == null) {
             throw new NullPointerException();
@@ -1595,6 +1688,9 @@
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
+            if (!sHasBeamFeature) {
+                return;
+            }
         }
         if (activity == null) {
             throw new NullPointerException();
@@ -1668,12 +1764,18 @@
      * @see android.provider.Settings#ACTION_NFCSHARING_SETTINGS
      * @return true if NDEF Push feature is enabled
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     * @deprecated this feature is deprecated.
      */
+    @java.lang.Deprecated
+
     public boolean isNdefPushEnabled() {
         synchronized (NfcAdapter.class) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
+            if (!sHasBeamFeature) {
+                return false;
+            }
         }
         try {
             return sService.isNdefPushEnabled();
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index e8d801c..911ec84 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -18,11 +18,10 @@
 
 import android.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
-import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
 import android.content.res.TypedArray;
@@ -30,7 +29,6 @@
 import android.graphics.drawable.Drawable;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.ResultReceiver;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Xml;
@@ -69,6 +67,18 @@
     final boolean mOnHost;
 
     /**
+     * Offhost reader name.
+     * eg: SIM, eSE etc
+     */
+    String mOffHostName;
+
+    /**
+     * Offhost reader name from manifest file.
+     * Used for unsetOffHostSecureElement()
+     */
+    final String mStaticOffHostName;
+
+    /**
      * Mapping from category to static AID group
      */
     @UnsupportedAppUsage
@@ -104,15 +114,17 @@
      * @hide
      */
     @UnsupportedAppUsage
-    public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
+    public ApduServiceInfo(ResolveInfo info, String description,
             ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups,
             boolean requiresUnlock, int bannerResource, int uid,
-            String settingsActivityName) {
+            String settingsActivityName, String offHost, String staticOffHost) {
         this.mService = info;
         this.mDescription = description;
         this.mStaticAidGroups = new HashMap<String, AidGroup>();
         this.mDynamicAidGroups = new HashMap<String, AidGroup>();
-        this.mOnHost = onHost;
+        this.mOffHostName = offHost;
+        this.mStaticOffHostName = staticOffHost;
+        this.mOnHost = (offHost == null);
         this.mRequiresDeviceUnlock = requiresUnlock;
         for (AidGroup aidGroup : staticAidGroups) {
             this.mStaticAidGroups.put(aidGroup.category, aidGroup);
@@ -174,6 +186,8 @@
                         com.android.internal.R.styleable.HostApduService_apduServiceBanner, -1);
                 mSettingsActivityName = sa.getString(
                         com.android.internal.R.styleable.HostApduService_settingsActivity);
+                mOffHostName = null;
+                mStaticOffHostName = mOffHostName;
                 sa.recycle();
             } else {
                 TypedArray sa = res.obtainAttributes(attrs,
@@ -186,6 +200,16 @@
                         com.android.internal.R.styleable.OffHostApduService_apduServiceBanner, -1);
                 mSettingsActivityName = sa.getString(
                         com.android.internal.R.styleable.HostApduService_settingsActivity);
+                mOffHostName = sa.getString(
+                        com.android.internal.R.styleable.OffHostApduService_secureElementName);
+                if (mOffHostName != null) {
+                    if (mOffHostName.equals("eSE")) {
+                        mOffHostName = "eSE1";
+                    } else if (mOffHostName.equals("SIM")) {
+                        mOffHostName = "SIM1";
+                    }
+                }
+                mStaticOffHostName = mOffHostName;
                 sa.recycle();
             }
 
@@ -289,6 +313,10 @@
                 mService.serviceInfo.name);
     }
 
+    public String getOffHostSecureElement() {
+        return mOffHostName;
+    }
+
     /**
      * Returns a consolidated list of AIDs from the AID groups
      * registered by this service. Note that if a service has both
@@ -404,6 +432,20 @@
         mDynamicAidGroups.put(aidGroup.getCategory(), aidGroup);
     }
 
+    @UnsupportedAppUsage
+    public void setOffHostSecureElement(String offHost) {
+        mOffHostName = offHost;
+    }
+
+    /**
+     * Resets the off host Secure Element to statically defined
+     * by the service in the manifest file.
+     */
+    @UnsupportedAppUsage
+    public void unsetOffHostSecureElement() {
+        mOffHostName = mStaticOffHostName;
+    }
+
     public CharSequence loadLabel(PackageManager pm) {
         return mService.loadLabel(pm);
     }
@@ -481,6 +523,8 @@
         mService.writeToParcel(dest, flags);
         dest.writeString(mDescription);
         dest.writeInt(mOnHost ? 1 : 0);
+        dest.writeString(mOffHostName);
+        dest.writeString(mStaticOffHostName);
         dest.writeInt(mStaticAidGroups.size());
         if (mStaticAidGroups.size() > 0) {
             dest.writeTypedList(new ArrayList<AidGroup>(mStaticAidGroups.values()));
@@ -503,6 +547,8 @@
             ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source);
             String description = source.readString();
             boolean onHost = source.readInt() != 0;
+            String offHostName = source.readString();
+            String staticOffHostName = source.readString();
             ArrayList<AidGroup> staticAidGroups = new ArrayList<AidGroup>();
             int numStaticGroups = source.readInt();
             if (numStaticGroups > 0) {
@@ -517,9 +563,9 @@
             int bannerResource = source.readInt();
             int uid = source.readInt();
             String settingsActivityName = source.readString();
-            return new ApduServiceInfo(info, onHost, description, staticAidGroups,
+            return new ApduServiceInfo(info, description, staticAidGroups,
                     dynamicAidGroups, requiresUnlock, bannerResource, uid,
-                    settingsActivityName);
+                    settingsActivityName, offHostName, staticOffHostName);
         }
 
         @Override
@@ -531,6 +577,14 @@
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("    " + getComponent() +
                 " (Description: " + getDescription() + ")");
+        if (mOnHost) {
+            pw.println("    On Host Service");
+        } else {
+            pw.println("    Off-host Service");
+            pw.println("        " + "Current off-host SE" + mOffHostName
+                    + " static off-host: " + mOffHostName);
+        }
+        pw.println("    Static off-host Secure Element:");
         pw.println("    Static AID groups:");
         for (AidGroup group : mStaticAidGroups.values()) {
             pw.println("        Category: " + group.category);
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index 15d02f2..01932ab 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -27,7 +27,6 @@
 import android.nfc.INfcCardEmulation;
 import android.nfc.NfcAdapter;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.util.Log;
@@ -345,6 +344,108 @@
     }
 
     /**
+     * Unsets the off-host Secure Element for the given service.
+     *
+     * <p>Note that this will only remove Secure Element that was dynamically
+     * set using the {@link #setOffHostForService(ComponentName, String)}
+     * and resets it to a value that was statically assigned using manifest.
+     *
+     * <p>Note that you can only unset off-host SE for a service that
+     * is running under the same UID as the caller of this API. Typically
+     * this means you need to call this from the same
+     * package as the service itself, though UIDs can also
+     * be shared between packages using shared UIDs.
+     *
+     * @param service The component name of the service
+     * @return whether the registration was successful.
+     */
+    public boolean unsetOffHostForService(ComponentName service) {
+        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
+        if (adapter == null) {
+            return false;
+        }
+
+        try {
+            return sService.unsetOffHostForService(mContext.getUserId(), service);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.unsetOffHostForService(mContext.getUserId(), service);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Sets the off-host Secure Element for the given service.
+     *
+     * <p>If off-host SE was initially set (either statically
+     * through the manifest, or dynamically by using this API),
+     * it will be replaced with this one. All AIDs registered by
+     * this service will be re-routed to this Secure Element if
+     * successful.
+     *
+     * <p>Note that you can only set off-host SE for a service that
+     * is running under the same UID as the caller of this API. Typically
+     * this means you need to call this from the same
+     * package as the service itself, though UIDs can also
+     * be shared between packages using shared UIDs.
+     *
+     * <p>Registeration will be successful only if the Secure Element
+     * exists on the device.
+     *
+     * @param service The component name of the service
+     * @param offHostSecureElement Secure Element to register the AID to
+     * @return whether the registration was successful.
+     */
+    public boolean setOffHostForService(ComponentName service, String offHostSecureElement) {
+        boolean validSecureElement = false;
+
+        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
+        if (adapter == null || offHostSecureElement == null) {
+            return false;
+        }
+
+        List<String> validSE = adapter.getSupportedOffHostSecureElements();
+        if ((offHostSecureElement.startsWith("eSE") && !validSE.contains("eSE"))
+                || (offHostSecureElement.startsWith("SIM") && !validSE.contains("SIM"))) {
+            return false;
+        }
+
+        if (offHostSecureElement.equals("eSE")) {
+            offHostSecureElement = "eSE1";
+        } else if (offHostSecureElement.equals("SIM")) {
+            offHostSecureElement = "SIM1";
+        }
+
+        try {
+            return sService.setOffHostForService(mContext.getUserId(), service,
+                offHostSecureElement);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.setOffHostForService(mContext.getUserId(), service,
+                        offHostSecureElement);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
      * Retrieves the currently registered AIDs for the specified
      * category for a service.
      *
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 3f8f882..885b3e9 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -250,7 +250,7 @@
         }
     }
 
-    static String typeToString(int type) {
+    public static String typeToString(int type) {
         switch (type) {
             case TYPE_TOP_BAR:
                 return "TYPE_TOP_BAR";
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 91faa55..183c2e8 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3755,6 +3755,8 @@
         <!-- Component name of an activity that allows the user to modify
              the settings for this service. -->
         <attr name="settingsActivity"/>
+        <!-- Secure Element which the AIDs should be routed to -->
+        <attr name="secureElementName"/>
     </declare-styleable>
 
     <!-- Specify one or more <code>aid-group</code> elements inside a
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ContextCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ContextCompat.java
new file mode 100644
index 0000000..51fcb0a
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ContextCompat.java
@@ -0,0 +1,34 @@
+/*
+ * 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.shared.system;
+
+import android.content.Context;
+
+/**
+ * Wraps a context to expose some methods for launcher to call.
+ */
+public class ContextCompat {
+    private final Context mWrapped;
+
+    public ContextCompat(Context context) {
+        mWrapped = context;
+    }
+
+    public int getUserId() {
+        return mWrapped.getUserId();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 3d2f838..3cc9bb6 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -278,6 +278,7 @@
     Lazy<NotificationAlertingManager> mNotificationAlertingManager;
     @Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager;
     @Inject Lazy<AutoHideController> mAutoHideController;
+    @Inject Lazy<ForegroundServiceNotificationListener> mForegroundServiceNotificationListener;
     @Inject @Named(BG_LOOPER_NAME) Lazy<Looper> mBgLooper;
     @Inject @Named(BG_HANDLER_NAME) Lazy<Handler> mBgHandler;
     @Inject @Named(MAIN_HANDLER_NAME) Lazy<Handler> mMainHandler;
@@ -450,6 +451,8 @@
         mProviders.put(BubbleController.class, mBubbleController::get);
         mProviders.put(NotificationEntryManager.class, mNotificationEntryManager::get);
         mProviders.put(NotificationAlertingManager.class, mNotificationAlertingManager::get);
+        mProviders.put(ForegroundServiceNotificationListener.class,
+                mForegroundServiceNotificationListener::get);
 
         // TODO(b/118592525): to support multi-display , we start to add something which is
         //                    per-display, while others may be global. I think it's time to add
diff --git a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
index 1ee1dcf..f324a05b 100644
--- a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
@@ -219,12 +219,6 @@
     /**
      */
     @Binds
-    public abstract ForegroundServiceController provideForegroundService(
-            ForegroundServiceControllerImpl controllerImpl);
-
-    /**
-     */
-    @Binds
     public abstract PowerUI.WarningsUI provideWarningsUi(PowerNotificationWarnings controllerImpl);
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
index ae6ee2a..df0d787 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
@@ -15,65 +15,158 @@
 package com.android.systemui;
 
 import android.annotation.Nullable;
+import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.util.ArraySet;
+import android.util.SparseArray;
 
-public interface ForegroundServiceController {
-    /**
-     * @param sbn notification that was just posted
-     * @param importance
-     */
-    void addNotification(StatusBarNotification sbn, int importance);
+import com.android.internal.messages.nano.SystemMessageProto;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Tracks state of foreground services and notifications related to foreground services per user.
+ */
+@Singleton
+public class ForegroundServiceController {
+
+    private final SparseArray<ForegroundServicesUserState> mUserServices = new SparseArray<>();
+    private final Object mMutex = new Object();
+
+    @Inject
+    public ForegroundServiceController() {
+    }
 
     /**
-     * @param sbn notification that was just changed in some way
-     * @param newImportance
-     */
-    void updateNotification(StatusBarNotification sbn, int newImportance);
-
-    /**
-     * @param sbn notification that was just canceled
-     */
-    boolean removeNotification(StatusBarNotification sbn);
-
-    /**
-     * @param userId
      * @return true if this user has services missing notifications and therefore needs a
      * disclosure notification.
      */
-    boolean isDungeonNeededForUser(int userId);
-
-    /**
-     * @param sbn
-     * @return true if sbn is the system-provided "dungeon" (list of running foreground services).
-     */
-    boolean isDungeonNotification(StatusBarNotification sbn);
-
-    /**
-     * @return true if sbn is one of the window manager "drawing over other apps" notifications
-     */
-    boolean isSystemAlertNotification(StatusBarNotification sbn);
-
-    /**
-     * Returns the key of the foreground service from this package using the standard template,
-     * if one exists.
-     */
-    @Nullable String getStandardLayoutKey(int userId, String pkg);
+    public boolean isDisclosureNeededForUser(int userId) {
+        synchronized (mMutex) {
+            final ForegroundServicesUserState services = mUserServices.get(userId);
+            if (services == null) return false;
+            return services.isDisclosureNeeded();
+        }
+    }
 
     /**
      * @return true if this user/pkg has a missing or custom layout notification and therefore needs
      * a disclosure notification for system alert windows.
      */
-    boolean isSystemAlertWarningNeeded(int userId, String pkg);
+    public boolean isSystemAlertWarningNeeded(int userId, String pkg) {
+        synchronized (mMutex) {
+            final ForegroundServicesUserState services = mUserServices.get(userId);
+            if (services == null) return false;
+            return services.getStandardLayoutKey(pkg) == null;
+        }
+    }
+
+    /**
+     * Returns the key of the foreground service from this package using the standard template,
+     * if one exists.
+     */
+    @Nullable
+    public String getStandardLayoutKey(int userId, String pkg) {
+        synchronized (mMutex) {
+            final ForegroundServicesUserState services = mUserServices.get(userId);
+            if (services == null) return null;
+            return services.getStandardLayoutKey(pkg);
+        }
+    }
+
+    /**
+     * Gets active app ops for this user and package
+     */
+    @Nullable
+    public ArraySet<Integer> getAppOps(int userId, String pkg) {
+        synchronized (mMutex) {
+            final ForegroundServicesUserState services = mUserServices.get(userId);
+            if (services == null) {
+                return null;
+            }
+            return services.getFeatures(pkg);
+        }
+    }
 
     /**
      * Records active app ops. App Ops are stored in FSC in addition to NotificationData in
      * case they change before we have a notification to tag.
      */
-    void onAppOpChanged(int code, int uid, String packageName, boolean active);
+    public void onAppOpChanged(int code, int uid, String packageName, boolean active) {
+        int userId = UserHandle.getUserId(uid);
+        synchronized (mMutex) {
+            ForegroundServicesUserState userServices = mUserServices.get(userId);
+            if (userServices == null) {
+                userServices = new ForegroundServicesUserState();
+                mUserServices.put(userId, userServices);
+            }
+            if (active) {
+                userServices.addOp(packageName, code);
+            } else {
+                userServices.removeOp(packageName, code);
+            }
+        }
+    }
 
     /**
-     * Gets active app ops for this user and package
+     * Looks up the {@link ForegroundServicesUserState} for the given {@code userId}, then performs
+     * the given {@link UserStateUpdateCallback} on it.  If no state exists for the user ID, creates
+     * a new one if {@code createIfNotFound} is true, then performs the update on the new state.
+     * If {@code createIfNotFound} is false, no update is performed.
+     *
+     * @return false if no user state was found and none was created; true otherwise.
      */
-    @Nullable ArraySet<Integer> getAppOps(int userId, String packageName);
+    boolean updateUserState(int userId,
+            UserStateUpdateCallback updateCallback,
+            boolean createIfNotFound) {
+        synchronized (mMutex) {
+            ForegroundServicesUserState userState = mUserServices.get(userId);
+            if (userState == null) {
+                if (createIfNotFound) {
+                    userState = new ForegroundServicesUserState();
+                    mUserServices.put(userId, userState);
+                } else {
+                    return false;
+                }
+            }
+            return updateCallback.updateUserState(userState);
+        }
+    }
+
+    /**
+     * @return true if {@code sbn} is the system-provided disclosure notification containing the
+     * list of running foreground services.
+     */
+    public boolean isDisclosureNotification(StatusBarNotification sbn) {
+        return sbn.getId() == SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES
+                && sbn.getTag() == null
+                && sbn.getPackageName().equals("android");
+    }
+
+    /**
+     * @return true if sbn is one of the window manager "drawing over other apps" notifications
+     */
+    public boolean isSystemAlertNotification(StatusBarNotification sbn) {
+        return sbn.getPackageName().equals("android")
+                && sbn.getTag() != null
+                && sbn.getTag().contains("AlertWindowNotification");
+    }
+
+    /**
+     * Callback provided to {@link #updateUserState(int, UserStateUpdateCallback, boolean)}
+     * to perform the update.
+     */
+    interface UserStateUpdateCallback {
+        /**
+         * Perform update operations on the provided {@code userState}.
+         *
+         * @return true if the update succeeded.
+         */
+        boolean updateUserState(ForegroundServicesUserState userState);
+
+        /** Called if the state was not found and was not created. */
+        default void userStateNotFound(int userId) {
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java
deleted file mode 100644
index ae446dd..0000000
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java
+++ /dev/null
@@ -1,309 +0,0 @@
-/*
- * Copyright (C) 2017 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;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.content.Context;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.internal.messages.nano.SystemMessageProto;
-
-import java.util.Arrays;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * Foreground service controller, a/k/a Dianne's Dungeon.
- */
-@Singleton
-public class ForegroundServiceControllerImpl
-        implements ForegroundServiceController {
-
-    // shelf life of foreground services before they go bad
-    public static final long FG_SERVICE_GRACE_MILLIS = 5000;
-
-    private static final String TAG = "FgServiceController";
-    private static final boolean DBG = false;
-
-    private final Context mContext;
-    private final SparseArray<UserServices> mUserServices = new SparseArray<>();
-    private final Object mMutex = new Object();
-
-    @Inject
-    public ForegroundServiceControllerImpl(Context context) {
-        mContext = context;
-    }
-
-    @Override
-    public boolean isDungeonNeededForUser(int userId) {
-        synchronized (mMutex) {
-            final UserServices services = mUserServices.get(userId);
-            if (services == null) return false;
-            return services.isDungeonNeeded();
-        }
-    }
-
-    @Override
-    public boolean isSystemAlertWarningNeeded(int userId, String pkg) {
-        synchronized (mMutex) {
-            final UserServices services = mUserServices.get(userId);
-            if (services == null) return false;
-            return services.getStandardLayoutKey(pkg) == null;
-        }
-    }
-
-    @Override
-    public String getStandardLayoutKey(int userId, String pkg) {
-        synchronized (mMutex) {
-            final UserServices services = mUserServices.get(userId);
-            if (services == null) return null;
-            return services.getStandardLayoutKey(pkg);
-        }
-    }
-
-    @Override
-    public ArraySet<Integer> getAppOps(int userId, String pkg) {
-        synchronized (mMutex) {
-            final UserServices services = mUserServices.get(userId);
-            if (services == null) {
-                return null;
-            }
-            return services.getFeatures(pkg);
-        }
-    }
-
-    @Override
-    public void onAppOpChanged(int code, int uid, String packageName, boolean active) {
-        int userId = UserHandle.getUserId(uid);
-        synchronized (mMutex) {
-            UserServices userServices = mUserServices.get(userId);
-            if (userServices == null) {
-                userServices = new UserServices();
-                mUserServices.put(userId, userServices);
-            }
-            if (active) {
-                userServices.addOp(packageName, code);
-            } else {
-                userServices.removeOp(packageName, code);
-            }
-        }
-    }
-
-    @Override
-    public void addNotification(StatusBarNotification sbn, int importance) {
-        updateNotification(sbn, importance);
-    }
-
-    @Override
-    public boolean removeNotification(StatusBarNotification sbn) {
-        synchronized (mMutex) {
-            final UserServices userServices = mUserServices.get(sbn.getUserId());
-            if (userServices == null) {
-                if (DBG) {
-                    Log.w(TAG, String.format(
-                            "user %d with no known notifications got removeNotification for %s",
-                            sbn.getUserId(), sbn));
-                }
-                return false;
-            }
-            if (isDungeonNotification(sbn)) {
-                // if you remove the dungeon entirely, we take that to mean there are
-                // no running services
-                userServices.setRunningServices(null, 0);
-                return true;
-            } else {
-                // this is safe to call on any notification, not just FLAG_FOREGROUND_SERVICE
-                return userServices.removeNotification(sbn.getPackageName(), sbn.getKey());
-            }
-        }
-    }
-
-    @Override
-    public void updateNotification(StatusBarNotification sbn, int newImportance) {
-        synchronized (mMutex) {
-            UserServices userServices = mUserServices.get(sbn.getUserId());
-            if (userServices == null) {
-                userServices = new UserServices();
-                mUserServices.put(sbn.getUserId(), userServices);
-            }
-
-            if (isDungeonNotification(sbn)) {
-                final Bundle extras = sbn.getNotification().extras;
-                if (extras != null) {
-                    final String[] svcs = extras.getStringArray(Notification.EXTRA_FOREGROUND_APPS);
-                    userServices.setRunningServices(svcs, sbn.getNotification().when);
-                }
-            } else {
-                userServices.removeNotification(sbn.getPackageName(), sbn.getKey());
-                if (0 != (sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE)) {
-                    if (newImportance > NotificationManager.IMPORTANCE_MIN) {
-                        userServices.addImportantNotification(sbn.getPackageName(), sbn.getKey());
-                    }
-                    final Notification.Builder builder = Notification.Builder.recoverBuilder(
-                            mContext, sbn.getNotification());
-                    if (builder.usesStandardHeader()) {
-                        userServices.addStandardLayoutNotification(
-                                sbn.getPackageName(), sbn.getKey());
-                    }
-                }
-            }
-        }
-    }
-
-    @Override
-    public boolean isDungeonNotification(StatusBarNotification sbn) {
-        return sbn.getId() == SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES
-                && sbn.getTag() == null
-                && sbn.getPackageName().equals("android");
-    }
-
-    @Override
-    public boolean isSystemAlertNotification(StatusBarNotification sbn) {
-        return sbn.getPackageName().equals("android")
-                && sbn.getTag() != null
-                && sbn.getTag().contains("AlertWindowNotification");
-    }
-
-    /**
-     * Struct to track relevant packages and notifications for a userid's foreground services.
-     */
-    private static class UserServices {
-        private String[] mRunning = null;
-        private long mServiceStartTime = 0;
-        // package -> sufficiently important posted notification keys
-        private ArrayMap<String, ArraySet<String>> mImportantNotifications = new ArrayMap<>(1);
-        // package -> standard layout posted notification keys
-        private ArrayMap<String, ArraySet<String>> mStandardLayoutNotifications = new ArrayMap<>(1);
-
-        // package -> app ops
-        private ArrayMap<String, ArraySet<Integer>> mAppOps = new ArrayMap<>(1);
-
-        public void setRunningServices(String[] pkgs, long serviceStartTime) {
-            mRunning = pkgs != null ? Arrays.copyOf(pkgs, pkgs.length) : null;
-            mServiceStartTime = serviceStartTime;
-        }
-
-        public void addOp(String pkg, int op) {
-            if (mAppOps.get(pkg) == null) {
-                mAppOps.put(pkg, new ArraySet<>(3));
-            }
-            mAppOps.get(pkg).add(op);
-        }
-
-        public boolean removeOp(String pkg, int op) {
-            final boolean found;
-            final ArraySet<Integer> keys = mAppOps.get(pkg);
-            if (keys == null) {
-                found = false;
-            } else {
-                found = keys.remove(op);
-                if (keys.size() == 0) {
-                    mAppOps.remove(pkg);
-                }
-            }
-            return found;
-        }
-
-        public void addImportantNotification(String pkg, String key) {
-            addNotification(mImportantNotifications, pkg, key);
-        }
-
-        public boolean removeImportantNotification(String pkg, String key) {
-            return removeNotification(mImportantNotifications, pkg, key);
-        }
-
-        public void addStandardLayoutNotification(String pkg, String key) {
-            addNotification(mStandardLayoutNotifications, pkg, key);
-        }
-
-        public boolean removeStandardLayoutNotification(String pkg, String key) {
-            return removeNotification(mStandardLayoutNotifications, pkg, key);
-        }
-
-        public boolean removeNotification(String pkg, String key) {
-            boolean removed = false;
-            removed |= removeImportantNotification(pkg, key);
-            removed |= removeStandardLayoutNotification(pkg, key);
-            return removed;
-        }
-
-        public void addNotification(ArrayMap<String, ArraySet<String>> map, String pkg,
-                String key) {
-            if (map.get(pkg) == null) {
-                map.put(pkg, new ArraySet<>());
-            }
-            map.get(pkg).add(key);
-        }
-
-        public boolean removeNotification(ArrayMap<String, ArraySet<String>> map,
-                String pkg, String key) {
-            final boolean found;
-            final ArraySet<String> keys = map.get(pkg);
-            if (keys == null) {
-                found = false;
-            } else {
-                found = keys.remove(key);
-                if (keys.size() == 0) {
-                    map.remove(pkg);
-                }
-            }
-            return found;
-        }
-
-        public boolean isDungeonNeeded() {
-            if (mRunning != null
-                && System.currentTimeMillis() - mServiceStartTime >= FG_SERVICE_GRACE_MILLIS) {
-
-                for (String pkg : mRunning) {
-                    final ArraySet<String> set = mImportantNotifications.get(pkg);
-                    if (set == null || set.size() == 0) {
-                        return true;
-                    }
-                }
-            }
-            return false;
-        }
-
-        public ArraySet<Integer> getFeatures(String pkg) {
-            return mAppOps.get(pkg);
-        }
-
-        public String getStandardLayoutKey(String pkg) {
-            final ArraySet<String> set = mStandardLayoutNotifications.get(pkg);
-            if (set == null || set.size() == 0) {
-                return null;
-            }
-            return set.valueAt(0);
-        }
-
-        @Override
-        public String toString() {
-            return "UserServices{" +
-                    "mRunning=" + Arrays.toString(mRunning) +
-                    ", mServiceStartTime=" + mServiceStartTime +
-                    ", mImportantNotifications=" + mImportantNotifications +
-                    ", mStandardLayoutNotifications=" + mStandardLayoutNotifications +
-                    '}';
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
new file mode 100644
index 0000000..151a6b0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.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 com.android.systemui;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/** Updates foreground service notification state in response to notification data events. */
+@Singleton
+public class ForegroundServiceNotificationListener {
+
+    private static final String TAG = "FgServiceController";
+    private static final boolean DBG = false;
+
+    private final Context mContext;
+    private final ForegroundServiceController mForegroundServiceController;
+
+    @Inject
+    public ForegroundServiceNotificationListener(Context context,
+            ForegroundServiceController foregroundServiceController,
+            NotificationEntryManager notificationEntryManager) {
+        mContext = context;
+        mForegroundServiceController = foregroundServiceController;
+        notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
+            @Override
+            public void onPendingEntryAdded(NotificationData.Entry entry) {
+                addNotification(entry.notification, entry.importance);
+            }
+
+            @Override
+            public void onEntryUpdated(NotificationData.Entry entry) {
+                updateNotification(entry.notification, entry.importance);
+            }
+
+            @Override
+            public void onEntryRemoved(
+                    NotificationData.Entry entry,
+                    String key,
+                    StatusBarNotification old,
+                    NotificationVisibility visibility,
+                    boolean lifetimeExtended,
+                    boolean removedByUser) {
+                if (entry != null && !lifetimeExtended) {
+                    removeNotification(entry.notification);
+                }
+            }
+        });
+    }
+
+    /**
+     * @param sbn notification that was just posted
+     */
+    private void addNotification(StatusBarNotification sbn, int importance) {
+        updateNotification(sbn, importance);
+    }
+
+    /**
+     * @param sbn notification that was just removed
+     */
+    private void removeNotification(StatusBarNotification sbn) {
+        mForegroundServiceController.updateUserState(
+                sbn.getUserId(),
+                new ForegroundServiceController.UserStateUpdateCallback() {
+                    @Override
+                    public boolean updateUserState(ForegroundServicesUserState userState) {
+                        if (mForegroundServiceController.isDisclosureNotification(sbn)) {
+                            // if you remove the dungeon entirely, we take that to mean there are
+                            // no running services
+                            userState.setRunningServices(null, 0);
+                            return true;
+                        } else {
+                            // this is safe to call on any notification, not just
+                            // FLAG_FOREGROUND_SERVICE
+                            return userState.removeNotification(sbn.getPackageName(), sbn.getKey());
+                        }
+                    }
+
+                    @Override
+                    public void userStateNotFound(int userId) {
+                        if (DBG) {
+                            Log.w(TAG, String.format(
+                                    "user %d with no known notifications got removeNotification "
+                                            + "for %s",
+                                    sbn.getUserId(), sbn));
+                        }
+                    }
+                },
+                false /* don't create */);
+    }
+
+    /**
+     * @param sbn notification that was just changed in some way
+     */
+    private void updateNotification(StatusBarNotification sbn, int newImportance) {
+        mForegroundServiceController.updateUserState(
+                sbn.getUserId(),
+                userState -> {
+                    if (mForegroundServiceController.isDisclosureNotification(sbn)) {
+                        final Bundle extras = sbn.getNotification().extras;
+                        if (extras != null) {
+                            final String[] svcs = extras.getStringArray(
+                                    Notification.EXTRA_FOREGROUND_APPS);
+                            userState.setRunningServices(svcs, sbn.getNotification().when);
+                        }
+                    } else {
+                        userState.removeNotification(sbn.getPackageName(), sbn.getKey());
+                        if (0 != (sbn.getNotification().flags
+                                & Notification.FLAG_FOREGROUND_SERVICE)) {
+                            if (newImportance > NotificationManager.IMPORTANCE_MIN) {
+                                userState.addImportantNotification(sbn.getPackageName(),
+                                        sbn.getKey());
+                            }
+                            final Notification.Builder builder =
+                                    Notification.Builder.recoverBuilder(
+                                            mContext, sbn.getNotification());
+                            if (builder.usesStandardHeader()) {
+                                userState.addStandardLayoutNotification(
+                                        sbn.getPackageName(), sbn.getKey());
+                            }
+                        }
+                    }
+                    return true;
+                },
+                true /* create if not found */);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServicesUserState.java b/packages/SystemUI/src/com/android/systemui/ForegroundServicesUserState.java
new file mode 100644
index 0000000..a8ae654
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServicesUserState.java
@@ -0,0 +1,149 @@
+/*
+ * 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;
+
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import java.util.Arrays;
+
+/**
+ * Struct to track relevant packages and notifications for a userid's foreground services.
+ */
+class ForegroundServicesUserState {
+    // shelf life of foreground services before they go bad
+    private static final long FG_SERVICE_GRACE_MILLIS = 5000;
+
+    private String[] mRunning = null;
+    private long mServiceStartTime = 0;
+    // package -> sufficiently important posted notification keys
+    private ArrayMap<String, ArraySet<String>> mImportantNotifications = new ArrayMap<>(1);
+    // package -> standard layout posted notification keys
+    private ArrayMap<String, ArraySet<String>> mStandardLayoutNotifications = new ArrayMap<>(1);
+
+    // package -> app ops
+    private ArrayMap<String, ArraySet<Integer>> mAppOps = new ArrayMap<>(1);
+
+    public void setRunningServices(String[] pkgs, long serviceStartTime) {
+        mRunning = pkgs != null ? Arrays.copyOf(pkgs, pkgs.length) : null;
+        mServiceStartTime = serviceStartTime;
+    }
+
+    public void addOp(String pkg, int op) {
+        if (mAppOps.get(pkg) == null) {
+            mAppOps.put(pkg, new ArraySet<>(3));
+        }
+        mAppOps.get(pkg).add(op);
+    }
+
+    public boolean removeOp(String pkg, int op) {
+        final boolean found;
+        final ArraySet<Integer> keys = mAppOps.get(pkg);
+        if (keys == null) {
+            found = false;
+        } else {
+            found = keys.remove(op);
+            if (keys.size() == 0) {
+                mAppOps.remove(pkg);
+            }
+        }
+        return found;
+    }
+
+    public void addImportantNotification(String pkg, String key) {
+        addNotification(mImportantNotifications, pkg, key);
+    }
+
+    public boolean removeImportantNotification(String pkg, String key) {
+        return removeNotification(mImportantNotifications, pkg, key);
+    }
+
+    public void addStandardLayoutNotification(String pkg, String key) {
+        addNotification(mStandardLayoutNotifications, pkg, key);
+    }
+
+    public boolean removeStandardLayoutNotification(String pkg, String key) {
+        return removeNotification(mStandardLayoutNotifications, pkg, key);
+    }
+
+    public boolean removeNotification(String pkg, String key) {
+        boolean removed = false;
+        removed |= removeImportantNotification(pkg, key);
+        removed |= removeStandardLayoutNotification(pkg, key);
+        return removed;
+    }
+
+    public void addNotification(ArrayMap<String, ArraySet<String>> map, String pkg,
+            String key) {
+        if (map.get(pkg) == null) {
+            map.put(pkg, new ArraySet<>());
+        }
+        map.get(pkg).add(key);
+    }
+
+    public boolean removeNotification(ArrayMap<String, ArraySet<String>> map,
+            String pkg, String key) {
+        final boolean found;
+        final ArraySet<String> keys = map.get(pkg);
+        if (keys == null) {
+            found = false;
+        } else {
+            found = keys.remove(key);
+            if (keys.size() == 0) {
+                map.remove(pkg);
+            }
+        }
+        return found;
+    }
+
+    public boolean isDisclosureNeeded() {
+        if (mRunning != null
+                && System.currentTimeMillis() - mServiceStartTime
+                >= FG_SERVICE_GRACE_MILLIS) {
+
+            for (String pkg : mRunning) {
+                final ArraySet<String> set = mImportantNotifications.get(pkg);
+                if (set == null || set.size() == 0) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public ArraySet<Integer> getFeatures(String pkg) {
+        return mAppOps.get(pkg);
+    }
+
+    public String getStandardLayoutKey(String pkg) {
+        final ArraySet<String> set = mStandardLayoutNotifications.get(pkg);
+        if (set == null || set.size() == 0) {
+            return null;
+        }
+        return set.valueAt(0);
+    }
+
+    @Override
+    public String toString() {
+        return "UserServices{"
+                + "mRunning=" + Arrays.toString(mRunning)
+                + ", mServiceStartTime=" + mServiceStartTime
+                + ", mImportantNotifications=" + mImportantNotifications
+                + ", mStandardLayoutNotifications=" + mStandardLayoutNotifications
+                + '}';
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 67aa82d..7656564 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -124,6 +124,7 @@
                 unscheduleTimeTick();
                 break;
             case DOZE_REQUEST_PULSE:
+                scheduleTimeTick();
                 pulseWhileDozing(mMachine.getPulseReason());
                 break;
             case INITIALIZED:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index ab97e5e..52b84de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -21,7 +21,6 @@
 import android.app.Notification;
 import android.content.Context;
 import android.os.Handler;
-import android.os.PowerManager;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
@@ -91,9 +90,7 @@
     private Runnable mUpdateNotificationViewsCallback;
 
     private NotificationPresenter mPresenter;
-    protected PowerManager mPowerManager;
     private NotificationListenerService.RankingMap mLatestRankingMap;
-    protected HeadsUpManager mHeadsUpManager;
     protected NotificationData mNotificationData;
     protected NotificationListContainer mListContainer;
     @VisibleForTesting
@@ -129,7 +126,6 @@
 
     public NotificationEntryManager(Context context) {
         mContext = context;
-        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mBubbleController.setDismissListener(this /* bubbleEventListener */);
         mNotificationData = new NotificationData();
         mDeferredNotificationViewUpdateHandler = new Handler();
@@ -162,8 +158,7 @@
             HeadsUpManager headsUpManager) {
         mPresenter = presenter;
         mUpdateNotificationViewsCallback = mPresenter::updateNotificationViews;
-        mHeadsUpManager = headsUpManager;
-        mNotificationData.setHeadsUpManager(mHeadsUpManager);
+        mNotificationData.setHeadsUpManager(headsUpManager);
         mListContainer = listContainer;
 
         mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
@@ -339,8 +334,6 @@
                     extender.setShouldManageLifetime(entry, false /* shouldManage */);
                 }
 
-                mForegroundServiceController.removeNotification(entry.notification);
-
                 if (entry.rowExists()) {
                     entry.removeRow();
                     mListContainer.cleanUpViewStateForEntry(entry);
@@ -458,9 +451,6 @@
         NotificationData.Entry entry = createNotificationEntry(notification, ranking);
         abortExistingInflation(key);
 
-        mForegroundServiceController.addNotification(notification,
-                mNotificationData.getImportance(key));
-
         mPendingNotifications.put(key, entry);
         for (NotificationEntryListener listener : mNotificationEntryListeners) {
             listener.onPendingEntryAdded(entry);
@@ -522,9 +512,6 @@
         getRowBinder().inflateViews(entry, () -> performRemoveNotification(notification),
                 mNotificationData.get(entry.key) != null);
 
-        mForegroundServiceController.updateNotification(notification,
-                mNotificationData.getImportance(key));
-
         updateNotifications();
 
         if (!notification.isClearable()) {
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 5e99c38..700382a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
@@ -117,8 +117,8 @@
             return true;
         }
 
-        if (getFsc().isDungeonNotification(sbn)
-                && !getFsc().isDungeonNeededForUser(sbn.getUserId())) {
+        if (getFsc().isDisclosureNotification(sbn)
+                && !getFsc().isDisclosureNeededForUser(sbn.getUserId())) {
             // this is a foreground-service disclosure for a user that does not need to show one
             return true;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index f1d9549..975aee5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -189,10 +189,13 @@
         boolean isFastNonDismissGesture =
                 gestureFastEnough && !gestureTowardsMenu && !isDismissGesture;
         boolean isMenuRevealingGestureAwayFromMenu = slowSwipedFarEnough || isFastNonDismissGesture;
-        if (isNonDismissGestureTowardsMenu
-                || (!isFalseGesture(ev) && isMenuRevealingGestureAwayFromMenu)) {
+        int menuSnapTarget = menuRow.getMenuSnapTarget();
+        boolean isNonFalseMenuRevealingGesture =
+                !isFalseGesture(ev) && isMenuRevealingGestureAwayFromMenu;
+        if ((isNonDismissGestureTowardsMenu || isNonFalseMenuRevealingGesture)
+                && menuSnapTarget != 0) {
             // Menu has not been snapped to previously and this is menu revealing gesture
-            snapOpen(animView, menuRow.getMenuSnapTarget(), velocity);
+            snapOpen(animView, menuSnapTarget, velocity);
             menuRow.onSnapOpen();
         } else if (isDismissGesture(ev) && !gestureTowardsMenu) {
             dismiss(animView, velocity);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 7b7bcb4..b9372e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -43,6 +43,7 @@
 import com.android.internal.widget.MessagingMessage;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dependency;
+import com.android.systemui.ForegroundServiceNotificationListener;
 import com.android.systemui.InitController;
 import com.android.systemui.R;
 import com.android.systemui.plugins.ActivityStarter;
@@ -228,6 +229,10 @@
             mVisualStabilityManager.setUpWithPresenter(this);
             gutsManager.setUpWithPresenter(this,
                     notifListContainer, mCheckSaveListener, mOnSettingsClickListener);
+            // ForegroundServiceControllerListener adds its listener in its constructor
+            // but we need to request it here in order for it to be instantiated.
+            // TODO: figure out how to do this correctly once Dependency.get() is gone.
+            Dependency.get(ForegroundServiceNotificationListener.class);
 
             onUserSwitched(mLockscreenUserManager.getCurrentUserId());
         });
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
index f278a17..199a3a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.annotation.UserIdInt;
@@ -35,197 +36,95 @@
 import android.widget.RemoteViews;
 
 import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class ForegroundServiceControllerTest extends SysuiTestCase {
-    public static @UserIdInt int USERID_ONE = 10; // UserManagerService.MIN_USER_ID;
-    public static @UserIdInt int USERID_TWO = USERID_ONE + 1;
+    @UserIdInt private static final int USERID_ONE = 10; // UserManagerService.MIN_USER_ID;
+    @UserIdInt private static final int USERID_TWO = USERID_ONE + 1;
 
-    private ForegroundServiceController fsc;
+    private ForegroundServiceController mFsc;
+    private ForegroundServiceNotificationListener mListener;
+    private NotificationEntryListener mEntryListener;
 
     @Before
     public void setUp() throws Exception {
-        fsc = new ForegroundServiceControllerImpl(mContext);
-    }
-
-    @Test
-    public void testNotificationCRUD_dungeon() {
-        StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, "com.example.app1");
-        StatusBarNotification sbn_user2_app2_fg = makeMockFgSBN(USERID_TWO, "com.example.app2");
-        StatusBarNotification sbn_user1_app3_fg = makeMockFgSBN(USERID_ONE, "com.example.app3");
-        StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1",
-                5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
-        StatusBarNotification sbn_user2_app1 = makeMockSBN(USERID_TWO, "com.example.app1",
-                5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
-
-        assertFalse(fsc.removeNotification(sbn_user1_app3_fg));
-        assertFalse(fsc.removeNotification(sbn_user2_app2_fg));
-        assertFalse(fsc.removeNotification(sbn_user1_app1_fg));
-        assertFalse(fsc.removeNotification(sbn_user1_app1));
-        assertFalse(fsc.removeNotification(sbn_user2_app1));
-
-        fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
-        fsc.addNotification(sbn_user2_app2_fg, NotificationManager.IMPORTANCE_DEFAULT);
-        fsc.addNotification(sbn_user1_app3_fg, NotificationManager.IMPORTANCE_DEFAULT);
-        fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
-        fsc.addNotification(sbn_user2_app1, NotificationManager.IMPORTANCE_DEFAULT);
-
-        // these are never added to the tracker
-        assertFalse(fsc.removeNotification(sbn_user1_app1));
-        assertFalse(fsc.removeNotification(sbn_user2_app1));
-
-        fsc.updateNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
-        fsc.updateNotification(sbn_user2_app1, NotificationManager.IMPORTANCE_DEFAULT);
-        // should still not be there
-        assertFalse(fsc.removeNotification(sbn_user1_app1));
-        assertFalse(fsc.removeNotification(sbn_user2_app1));
-
-        fsc.updateNotification(sbn_user2_app2_fg, NotificationManager.IMPORTANCE_DEFAULT);
-        fsc.updateNotification(sbn_user1_app3_fg, NotificationManager.IMPORTANCE_DEFAULT);
-        fsc.updateNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
-
-        assertTrue(fsc.removeNotification(sbn_user1_app3_fg));
-        assertFalse(fsc.removeNotification(sbn_user1_app3_fg));
-
-        assertTrue(fsc.removeNotification(sbn_user2_app2_fg));
-        assertFalse(fsc.removeNotification(sbn_user2_app2_fg));
-
-        assertTrue(fsc.removeNotification(sbn_user1_app1_fg));
-        assertFalse(fsc.removeNotification(sbn_user1_app1_fg));
-
-        assertFalse(fsc.removeNotification(sbn_user1_app1));
-        assertFalse(fsc.removeNotification(sbn_user2_app1));
-    }
-
-    @Test
-    public void testNotificationCRUD_stdLayout() {
-        StatusBarNotification sbn_user1_app1_fg =
-                makeMockFgSBN(USERID_ONE, "com.example.app1", 0, true);
-        StatusBarNotification sbn_user2_app2_fg =
-                makeMockFgSBN(USERID_TWO, "com.example.app2", 1, true);
-        StatusBarNotification sbn_user1_app3_fg =
-                makeMockFgSBN(USERID_ONE, "com.example.app3", 2, true);
-        StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1",
-                5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
-        StatusBarNotification sbn_user2_app1 = makeMockSBN(USERID_TWO, "com.example.app1",
-                5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
-
-        assertFalse(fsc.removeNotification(sbn_user1_app3_fg));
-        assertFalse(fsc.removeNotification(sbn_user2_app2_fg));
-        assertFalse(fsc.removeNotification(sbn_user1_app1_fg));
-        assertFalse(fsc.removeNotification(sbn_user1_app1));
-        assertFalse(fsc.removeNotification(sbn_user2_app1));
-
-        fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN);
-        fsc.addNotification(sbn_user2_app2_fg, NotificationManager.IMPORTANCE_MIN);
-        fsc.addNotification(sbn_user1_app3_fg, NotificationManager.IMPORTANCE_MIN);
-        fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_MIN);
-        fsc.addNotification(sbn_user2_app1, NotificationManager.IMPORTANCE_MIN);
-
-        // these are never added to the tracker
-        assertFalse(fsc.removeNotification(sbn_user1_app1));
-        assertFalse(fsc.removeNotification(sbn_user2_app1));
-
-        fsc.updateNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_MIN);
-        fsc.updateNotification(sbn_user2_app1, NotificationManager.IMPORTANCE_MIN);
-        // should still not be there
-        assertFalse(fsc.removeNotification(sbn_user1_app1));
-        assertFalse(fsc.removeNotification(sbn_user2_app1));
-
-        fsc.updateNotification(sbn_user2_app2_fg, NotificationManager.IMPORTANCE_MIN);
-        fsc.updateNotification(sbn_user1_app3_fg, NotificationManager.IMPORTANCE_MIN);
-        fsc.updateNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN);
-
-        assertTrue(fsc.removeNotification(sbn_user1_app3_fg));
-        assertFalse(fsc.removeNotification(sbn_user1_app3_fg));
-
-        assertTrue(fsc.removeNotification(sbn_user2_app2_fg));
-        assertFalse(fsc.removeNotification(sbn_user2_app2_fg));
-
-        assertTrue(fsc.removeNotification(sbn_user1_app1_fg));
-        assertFalse(fsc.removeNotification(sbn_user1_app1_fg));
-
-        assertFalse(fsc.removeNotification(sbn_user1_app1));
-        assertFalse(fsc.removeNotification(sbn_user2_app1));
+        mFsc = new ForegroundServiceController();
+        NotificationEntryManager notificationEntryManager = mock(NotificationEntryManager.class);
+        mListener = new ForegroundServiceNotificationListener(
+                mContext, mFsc, notificationEntryManager);
+        ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
+                ArgumentCaptor.forClass(NotificationEntryListener.class);
+        verify(notificationEntryManager).addNotificationEntryListener(
+                entryListenerCaptor.capture());
+        mEntryListener = entryListenerCaptor.getValue();
     }
 
     @Test
     public void testAppOpsCRUD() {
         // no crash on remove that doesn't exist
-        fsc.onAppOpChanged(9, 1000, "pkg1", false);
-        assertNull(fsc.getAppOps(0, "pkg1"));
+        mFsc.onAppOpChanged(9, 1000, "pkg1", false);
+        assertNull(mFsc.getAppOps(0, "pkg1"));
 
         // multiuser & multipackage
-        fsc.onAppOpChanged(8, 50, "pkg1", true);
-        fsc.onAppOpChanged(1, 60, "pkg3", true);
-        fsc.onAppOpChanged(7, 500000, "pkg2", true);
+        mFsc.onAppOpChanged(8, 50, "pkg1", true);
+        mFsc.onAppOpChanged(1, 60, "pkg3", true);
+        mFsc.onAppOpChanged(7, 500000, "pkg2", true);
 
-        assertEquals(1, fsc.getAppOps(0, "pkg1").size());
-        assertTrue(fsc.getAppOps(0, "pkg1").contains(8));
+        assertEquals(1, mFsc.getAppOps(0, "pkg1").size());
+        assertTrue(mFsc.getAppOps(0, "pkg1").contains(8));
 
-        assertEquals(1, fsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size());
-        assertTrue(fsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7));
+        assertEquals(1, mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size());
+        assertTrue(mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7));
 
-        assertEquals(1, fsc.getAppOps(0, "pkg3").size());
-        assertTrue(fsc.getAppOps(0, "pkg3").contains(1));
+        assertEquals(1, mFsc.getAppOps(0, "pkg3").size());
+        assertTrue(mFsc.getAppOps(0, "pkg3").contains(1));
 
         // multiple ops for the same package
-        fsc.onAppOpChanged(9, 50, "pkg1", true);
-        fsc.onAppOpChanged(5, 50, "pkg1", true);
+        mFsc.onAppOpChanged(9, 50, "pkg1", true);
+        mFsc.onAppOpChanged(5, 50, "pkg1", true);
 
-        assertEquals(3, fsc.getAppOps(0, "pkg1").size());
-        assertTrue(fsc.getAppOps(0, "pkg1").contains(8));
-        assertTrue(fsc.getAppOps(0, "pkg1").contains(9));
-        assertTrue(fsc.getAppOps(0, "pkg1").contains(5));
+        assertEquals(3, mFsc.getAppOps(0, "pkg1").size());
+        assertTrue(mFsc.getAppOps(0, "pkg1").contains(8));
+        assertTrue(mFsc.getAppOps(0, "pkg1").contains(9));
+        assertTrue(mFsc.getAppOps(0, "pkg1").contains(5));
 
-        assertEquals(1, fsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size());
-        assertTrue(fsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7));
+        assertEquals(1, mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size());
+        assertTrue(mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7));
 
         // remove one of the multiples
-        fsc.onAppOpChanged(9, 50, "pkg1", false);
-        assertEquals(2, fsc.getAppOps(0, "pkg1").size());
-        assertTrue(fsc.getAppOps(0, "pkg1").contains(8));
-        assertTrue(fsc.getAppOps(0, "pkg1").contains(5));
+        mFsc.onAppOpChanged(9, 50, "pkg1", false);
+        assertEquals(2, mFsc.getAppOps(0, "pkg1").size());
+        assertTrue(mFsc.getAppOps(0, "pkg1").contains(8));
+        assertTrue(mFsc.getAppOps(0, "pkg1").contains(5));
 
         // remove last op
-        fsc.onAppOpChanged(1, 60, "pkg3", false);
-        assertNull(fsc.getAppOps(0, "pkg3"));
+        mFsc.onAppOpChanged(1, 60, "pkg3", false);
+        assertNull(mFsc.getAppOps(0, "pkg3"));
     }
 
     @Test
-    public void testDungeonPredicate() {
+    public void testDisclosurePredicate() {
         StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1",
                 5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
-        StatusBarNotification sbn_user1_dungeon = makeMockSBN(USERID_ONE, "android",
+        StatusBarNotification sbn_user1_disclosure = makeMockSBN(USERID_ONE, "android",
                 SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
                 null, Notification.FLAG_NO_CLEAR);
 
-        assertTrue(fsc.isDungeonNotification(sbn_user1_dungeon));
-        assertFalse(fsc.isDungeonNotification(sbn_user1_app1));
+        assertTrue(mFsc.isDisclosureNotification(sbn_user1_disclosure));
+        assertFalse(mFsc.isDisclosureNotification(sbn_user1_app1));
     }
 
     @Test
-    public void testDungeonCRUD() {
-        StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1",
-                5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
-        StatusBarNotification sbn_user1_dungeon = makeMockSBN(USERID_ONE, "android",
-                SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
-                null, Notification.FLAG_NO_CLEAR);
-
-        fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
-        fsc.addNotification(sbn_user1_dungeon, NotificationManager.IMPORTANCE_DEFAULT);
-
-        fsc.removeNotification(sbn_user1_dungeon);
-        assertFalse(fsc.removeNotification(sbn_user1_app1));
-    }
-
-    @Test
-    public void testNeedsDungeonAfterRemovingUnrelatedNotification() {
+    public void testNeedsDisclosureAfterRemovingUnrelatedNotification() {
         final String PKG1 = "com.example.app100";
 
         StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1,
@@ -233,21 +132,21 @@
         StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, PKG1);
 
         // first add a normal notification
-        fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
+        entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
         // nothing required yet
-        assertFalse(fsc.isDungeonNeededForUser(USERID_ONE));
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
         // now the app starts a fg service
-        fsc.addNotification(makeMockDungeon(USERID_ONE, new String[]{ PKG1 }),
+        entryAdded(makeMockDisclosure(USERID_ONE, new String[]{PKG1}),
                 NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); // should be required!
+        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
         // add the fg notification
-        fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
-        assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); // app1 has got it covered
+        entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has got it covered
         // remove the boring notification
-        fsc.removeNotification(sbn_user1_app1);
-        assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); // app1 has STILL got it covered
-        assertTrue(fsc.removeNotification(sbn_user1_app1_fg));
-        assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); // should be required!
+        entryRemoved(sbn_user1_app1);
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has STILL got it covered
+        entryRemoved(sbn_user1_app1_fg);
+        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
     }
 
     @Test
@@ -257,115 +156,117 @@
 
         StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1,
                 5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
-        fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
+        entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
 
         // no services are "running"
-        fsc.addNotification(makeMockDungeon(USERID_ONE, null),
+        entryAdded(makeMockDisclosure(USERID_ONE, null),
                 NotificationManager.IMPORTANCE_DEFAULT);
 
-        assertFalse(fsc.isDungeonNeededForUser(USERID_ONE));
-        assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
 
-        fsc.updateNotification(makeMockDungeon(USERID_ONE, new String[]{PKG1}),
+        entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG1}),
                 NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); // should be required!
-        assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
+        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
 
         // switch to different package
-        fsc.updateNotification(makeMockDungeon(USERID_ONE, new String[]{PKG2}),
+        entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG2}),
                 NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(fsc.isDungeonNeededForUser(USERID_ONE));
-        assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
+        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
 
-        fsc.updateNotification(makeMockDungeon(USERID_TWO, new String[]{PKG1}),
+        entryUpdated(makeMockDisclosure(USERID_TWO, new String[]{PKG1}),
                 NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(fsc.isDungeonNeededForUser(USERID_ONE));
-        assertTrue(fsc.isDungeonNeededForUser(USERID_TWO)); // finally user2 needs one too
+        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
+        assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO)); // finally user2 needs one too
 
-        fsc.updateNotification(makeMockDungeon(USERID_ONE, new String[]{PKG2, PKG1}),
+        entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG2, PKG1}),
                 NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(fsc.isDungeonNeededForUser(USERID_ONE));
-        assertTrue(fsc.isDungeonNeededForUser(USERID_TWO));
+        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
+        assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO));
 
-        fsc.removeNotification(makeMockDungeon(USERID_ONE, null /*unused*/));
-        assertFalse(fsc.isDungeonNeededForUser(USERID_ONE));
-        assertTrue(fsc.isDungeonNeededForUser(USERID_TWO));
+        entryRemoved(makeMockDisclosure(USERID_ONE, null /*unused*/));
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
+        assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO));
 
-        fsc.removeNotification(makeMockDungeon(USERID_TWO, null /*unused*/));
-        assertFalse(fsc.isDungeonNeededForUser(USERID_ONE));
-        assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
+        entryRemoved(makeMockDisclosure(USERID_TWO, null /*unused*/));
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
     }
 
     @Test
-    public void testDungeonBasic() {
+    public void testDisclosureBasic() {
         final String PKG1 = "com.example.app0";
 
         StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1,
                 5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
         StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, PKG1);
 
-        fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); // not fg
-        fsc.addNotification(makeMockDungeon(USERID_ONE, new String[]{ PKG1 }),
+        entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); // not fg
+        entryAdded(makeMockDisclosure(USERID_ONE, new String[]{PKG1}),
                 NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); // should be required!
-        fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
-        assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); // app1 has got it covered
-        assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
+        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
+        entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has got it covered
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
 
         // let's take out the other notification and see what happens.
 
-        fsc.removeNotification(sbn_user1_app1);
-        assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); // still covered by sbn_user1_app1_fg
-        assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
+        entryRemoved(sbn_user1_app1);
+        assertFalse(
+                mFsc.isDisclosureNeededForUser(USERID_ONE)); // still covered by sbn_user1_app1_fg
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
 
         // let's attempt to downgrade the notification from FLAG_FOREGROUND and see what we get
         StatusBarNotification sbn_user1_app1_fg_sneaky = makeMockFgSBN(USERID_ONE, PKG1);
         sbn_user1_app1_fg_sneaky.getNotification().flags = 0;
-        fsc.updateNotification(sbn_user1_app1_fg_sneaky, NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); // should be required!
-        assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
+        entryUpdated(sbn_user1_app1_fg_sneaky,
+                NotificationManager.IMPORTANCE_DEFAULT);
+        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
 
         // ok, ok, we'll put it back
         sbn_user1_app1_fg_sneaky.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
-        fsc.updateNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
-        assertFalse(fsc.isDungeonNeededForUser(USERID_ONE));
-        assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
+        entryUpdated(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
 
-        assertTrue(fsc.removeNotification(sbn_user1_app1_fg_sneaky));
-        assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); // should be required!
-        assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
+        entryRemoved(sbn_user1_app1_fg_sneaky);
+        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
 
         // now let's test an upgrade
-        fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(fsc.isDungeonNeededForUser(USERID_ONE));
-        assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
+        entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
+        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
         sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
-        fsc.updateNotification(sbn_user1_app1,
+        entryUpdated(sbn_user1_app1,
                 NotificationManager.IMPORTANCE_DEFAULT); // this is now a fg notification
 
-        assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
-        assertFalse(fsc.isDungeonNeededForUser(USERID_ONE));
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
 
         // remove it, make sure we're out of compliance again
-        assertTrue(fsc.removeNotification(sbn_user1_app1)); // was fg, should return true
-        assertFalse(fsc.removeNotification(sbn_user1_app1));
-        assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
-        assertTrue(fsc.isDungeonNeededForUser(USERID_ONE));
+        entryRemoved(sbn_user1_app1); // was fg, should return true
+        entryRemoved(sbn_user1_app1);
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
+        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
 
         // importance upgrade
-        fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN);
-        assertTrue(fsc.isDungeonNeededForUser(USERID_ONE));
-        assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
+        entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN);
+        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
         sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
-        fsc.updateNotification(sbn_user1_app1_fg,
+        entryUpdated(sbn_user1_app1_fg,
                 NotificationManager.IMPORTANCE_DEFAULT); // this is now a fg notification
 
         // finally, let's turn off the service
-        fsc.addNotification(makeMockDungeon(USERID_ONE, null),
+        entryAdded(makeMockDisclosure(USERID_ONE, null),
                 NotificationManager.IMPORTANCE_DEFAULT);
 
-        assertFalse(fsc.isDungeonNeededForUser(USERID_ONE));
-        assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
+        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
     }
 
     @Test
@@ -375,8 +276,8 @@
         StatusBarNotification sbn_user1_overlay = makeMockSBN(USERID_ONE, "android",
                 0, "AlertWindowNotification", Notification.FLAG_NO_CLEAR);
 
-        assertTrue(fsc.isSystemAlertNotification(sbn_user1_overlay));
-        assertFalse(fsc.isSystemAlertNotification(sbn_user1_app1));
+        assertTrue(mFsc.isSystemAlertNotification(sbn_user1_overlay));
+        assertFalse(mFsc.isSystemAlertNotification(sbn_user1_app1));
     }
 
     @Test
@@ -386,54 +287,54 @@
         StatusBarNotification sbn_user1_app1 = makeMockFgSBN(USERID_ONE, PKG1, 0, true);
         sbn_user1_app1.getNotification().flags = 0;
         StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, PKG1, 1, true);
-        fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_MIN); // not fg
-        assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required!
-        fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN);
-        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // app1 has got it covered
-        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "otherpkg"));
+        entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_MIN); // not fg
+        assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required!
+        entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN);
+        assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // app1 has got it covered
+        assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, "otherpkg"));
         // let's take out the non-fg notification and see what happens.
-        fsc.removeNotification(sbn_user1_app1);
+        entryRemoved(sbn_user1_app1);
         // still covered by sbn_user1_app1_fg
-        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1));
-        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "anyPkg"));
+        assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1));
+        assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, "anyPkg"));
 
         // let's attempt to downgrade the notification from FLAG_FOREGROUND and see what we get
         StatusBarNotification sbn_user1_app1_fg_sneaky = makeMockFgSBN(USERID_ONE, PKG1, 1, true);
         sbn_user1_app1_fg_sneaky.getNotification().flags = 0;
-        fsc.updateNotification(sbn_user1_app1_fg_sneaky, NotificationManager.IMPORTANCE_MIN);
-        assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required!
-        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "anything"));
+        entryUpdated(sbn_user1_app1_fg_sneaky, NotificationManager.IMPORTANCE_MIN);
+        assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required!
+        assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, "anything"));
         // ok, ok, we'll put it back
         sbn_user1_app1_fg_sneaky.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
-        fsc.updateNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN);
-        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1));
-        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "whatever"));
+        entryUpdated(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN);
+        assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1));
+        assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, "whatever"));
 
-        assertTrue(fsc.removeNotification(sbn_user1_app1_fg_sneaky));
-        assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required!
-        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "a"));
+        entryRemoved(sbn_user1_app1_fg_sneaky);
+        assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required!
+        assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, "a"));
 
         // let's try a custom layout
         sbn_user1_app1_fg_sneaky = makeMockFgSBN(USERID_ONE, PKG1, 1, false);
-        fsc.updateNotification(sbn_user1_app1_fg_sneaky, NotificationManager.IMPORTANCE_MIN);
-        assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required!
-        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "anything"));
+        entryUpdated(sbn_user1_app1_fg_sneaky, NotificationManager.IMPORTANCE_MIN);
+        assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1)); // should be required!
+        assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, "anything"));
         // now let's test an upgrade (non fg to fg)
-        fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_MIN);
-        assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1));
-        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, "b"));
+        entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_MIN);
+        assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1));
+        assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, "b"));
         sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
-        fsc.updateNotification(sbn_user1_app1,
+        entryUpdated(sbn_user1_app1,
                 NotificationManager.IMPORTANCE_MIN); // this is now a fg notification
 
-        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, PKG1));
-        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1));
+        assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, PKG1));
+        assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1));
 
         // remove it, make sure we're out of compliance again
-        assertTrue(fsc.removeNotification(sbn_user1_app1)); // was fg, should return true
-        assertFalse(fsc.removeNotification(sbn_user1_app1));
-        assertFalse(fsc.isSystemAlertWarningNeeded(USERID_TWO, PKG1));
-        assertTrue(fsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1));
+        entryRemoved(sbn_user1_app1); // was fg, should return true
+        entryRemoved(sbn_user1_app1);
+        assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_TWO, PKG1));
+        assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, PKG1));
     }
 
     private StatusBarNotification makeMockSBN(int userid, String pkg, int id, String tag,
@@ -475,7 +376,7 @@
         return makeMockSBN(userid, pkg, 1000, "foo", Notification.FLAG_FOREGROUND_SERVICE);
     }
 
-    private StatusBarNotification makeMockDungeon(int userid, String[] pkgs) {
+    private StatusBarNotification makeMockDisclosure(int userid, String[] pkgs) {
         final Notification n = mock(Notification.class);
         n.flags = Notification.FLAG_ONGOING_EVENT;
         final Bundle extras = new Bundle();
@@ -488,4 +389,21 @@
         sbn.getNotification().extras = extras;
         return sbn;
     }
+
+    private void entryRemoved(StatusBarNotification notification) {
+        mEntryListener.onEntryRemoved(new NotificationData.Entry(notification),
+                null, null, null, false, false);
+    }
+
+    private void entryAdded(StatusBarNotification notification, int importance) {
+        NotificationData.Entry entry = new NotificationData.Entry(notification);
+        entry.importance = importance;
+        mEntryListener.onPendingEntryAdded(entry);
+    }
+
+    private void entryUpdated(StatusBarNotification notification, int importance) {
+        NotificationData.Entry entry = new NotificationData.Entry(notification);
+        entry.importance = importance;
+        mEntryListener.onEntryUpdated(entry);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index a2d408e..57c9d29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -256,7 +256,6 @@
 
         // Check that no inflation error occurred.
         verify(mEntryListener, never()).onInflationError(any(), any());
-        verify(mForegroundServiceController).addNotification(eq(mSbn), anyInt());
 
         // Row inflation:
         ArgumentCaptor<NotificationData.Entry> entryCaptor = ArgumentCaptor.forClass(
@@ -292,7 +291,6 @@
         verify(mEntryListener, never()).onInflationError(any(), any());
 
         verify(mPresenter).updateNotificationViews();
-        verify(mForegroundServiceController).updateNotification(eq(mSbn), anyInt());
         verify(mEntryListener).onEntryUpdated(mEntry);
         assertNotNull(mEntry.getRow());
         assertEquals(mEntry.userSentiment,
@@ -310,7 +308,6 @@
 
         verify(mEntryListener, never()).onInflationError(any(), any());
 
-        verify(mForegroundServiceController).removeNotification(mSbn);
         verify(mListContainer).cleanUpViewStateForEntry(mEntry);
         verify(mPresenter).updateNotificationViews();
         verify(mEntryListener).onEntryRemoved(mEntry, mSbn.getKey(), mSbn,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 06be5f6..b66d0ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -35,12 +35,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.os.Handler;
-import android.os.IPowerManager;
-import android.os.Looper;
-import android.os.PowerManager;
 import android.provider.Settings;
-import android.service.dreams.IDreamManager;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -104,8 +99,6 @@
     @Mock private NotificationData mNotificationData;
     @Mock private NotificationRemoteInputManager mRemoteInputManager;
     @Mock private RemoteInputController mRemoteInputController;
-    @Mock private IDreamManager mDreamManager;
-    private PowerManager mPowerManager;
     private TestableNotificationEntryManager mEntryManager;
     private int mOriginalInterruptionModelSetting;
 
@@ -122,12 +115,7 @@
         mDependency.injectMockDependency(ShadeController.class);
         when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
 
-        IPowerManager powerManagerService = mock(IPowerManager.class);
-        mPowerManager = new PowerManager(mContext, powerManagerService,
-                Handler.createAsync(Looper.myLooper()));
-
-        mEntryManager = new TestableNotificationEntryManager(mPowerManager,
-                mContext);
+        mEntryManager = new TestableNotificationEntryManager(mContext);
         mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
         Dependency.get(InitController.class).executePostInitTasks();
         mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, mHeadsUpManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index d57d565..a45a5c4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -205,7 +205,7 @@
 
         mMetricsLogger = new FakeMetricsLogger();
         mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
-        mEntryManager = new TestableNotificationEntryManager(mPowerManager, mContext);
+        mEntryManager = new TestableNotificationEntryManager(mContext);
         mNotificationLogger = new NotificationLogger(mNotificationListener,
                 Dependency.get(UiOffloadThread.class), mEntryManager, mStatusBarStateController);
         mDependency.injectTestDependency(NotificationLogger.class, mNotificationLogger);
@@ -761,9 +761,8 @@
 
     public static class TestableNotificationEntryManager extends NotificationEntryManager {
 
-        public TestableNotificationEntryManager(PowerManager powerManager, Context context) {
+        public TestableNotificationEntryManager(Context context) {
             super(context);
-            mPowerManager = powerManager;
         }
 
         public void setUpForTest(NotificationPresenter presenter,
diff --git a/packages/overlays/AccentColorBlackOverlay/res/values/strings.xml b/packages/overlays/AccentColorBlackOverlay/res/values/strings.xml
index baf09b1..da10361 100644
--- a/packages/overlays/AccentColorBlackOverlay/res/values/strings.xml
+++ b/packages/overlays/AccentColorBlackOverlay/res/values/strings.xml
@@ -18,6 +18,6 @@
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Black accent color name application label. [CHAR LIMIT=50] -->
-    <string name="accent_color_black_overlay" translatable="false">Black Accent Color</string>
+    <string name="accent_color_black_overlay" translatable="false">Black</string>
 </resources>
 
diff --git a/packages/overlays/AccentColorGreenOverlay/res/values/strings.xml b/packages/overlays/AccentColorGreenOverlay/res/values/strings.xml
index 4de344c..623a1da 100644
--- a/packages/overlays/AccentColorGreenOverlay/res/values/strings.xml
+++ b/packages/overlays/AccentColorGreenOverlay/res/values/strings.xml
@@ -18,6 +18,6 @@
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Green accent color name application label. [CHAR LIMIT=50] -->
-    <string name="accent_color_green_overlay" translatable="false">Green Accent Color</string>
+    <string name="accent_color_green_overlay" translatable="false">Green</string>
 </resources>
 
diff --git a/packages/overlays/AccentColorPurpleOverlay/res/values/strings.xml b/packages/overlays/AccentColorPurpleOverlay/res/values/strings.xml
index d1eb95a..d1c7168 100644
--- a/packages/overlays/AccentColorPurpleOverlay/res/values/strings.xml
+++ b/packages/overlays/AccentColorPurpleOverlay/res/values/strings.xml
@@ -18,6 +18,6 @@
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Purple accent color name application label. [CHAR LIMIT=50] -->
-    <string name="accent_color_purple_overlay" translatable="false">Purple Accent Color</string>
+    <string name="accent_color_purple_overlay" translatable="false">Purple</string>
 </resources>
 
diff --git a/packages/overlays/IconShapeRoundedRectOverlay/res/values/strings.xml b/packages/overlays/IconShapeRoundedRectOverlay/res/values/strings.xml
index dc5c196..3c4c24d 100644
--- a/packages/overlays/IconShapeRoundedRectOverlay/res/values/strings.xml
+++ b/packages/overlays/IconShapeRoundedRectOverlay/res/values/strings.xml
@@ -18,6 +18,6 @@
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Rounded corner rectangle overlay -->
-    <string name="icon_shape_roundedrect_overlay" translatable="false">RoundedRectangle Icons</string>
+    <string name="icon_shape_roundedrect_overlay" translatable="false">Rounded Rectangle</string>
 
 </resources>
diff --git a/packages/overlays/IconShapeSquareOverlay/res/values/strings.xml b/packages/overlays/IconShapeSquareOverlay/res/values/strings.xml
index 4fd39ff26..5772165 100644
--- a/packages/overlays/IconShapeSquareOverlay/res/values/strings.xml
+++ b/packages/overlays/IconShapeSquareOverlay/res/values/strings.xml
@@ -18,6 +18,6 @@
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Square icon overlay -->
-    <string name="icon_shape_square_overlay" translatable="false">Square Icons</string>
+    <string name="icon_shape_square_overlay" translatable="false">Square</string>
 
 </resources>
diff --git a/packages/overlays/IconShapeSquircleOverlay/res/values/strings.xml b/packages/overlays/IconShapeSquircleOverlay/res/values/strings.xml
index b7c001c..028eccb 100644
--- a/packages/overlays/IconShapeSquircleOverlay/res/values/strings.xml
+++ b/packages/overlays/IconShapeSquircleOverlay/res/values/strings.xml
@@ -18,6 +18,6 @@
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Squircle icon shape overlay -->
-    <string name="icon_shape_squircle_overlay" translatable="false">Square Icons</string>
+    <string name="icon_shape_squircle_overlay" translatable="false">Squircle</string>
 
 </resources>
diff --git a/packages/overlays/IconShapeTeardropOverlay/res/values/strings.xml b/packages/overlays/IconShapeTeardropOverlay/res/values/strings.xml
index d946ee8..db9fa98 100644
--- a/packages/overlays/IconShapeTeardropOverlay/res/values/strings.xml
+++ b/packages/overlays/IconShapeTeardropOverlay/res/values/strings.xml
@@ -18,6 +18,6 @@
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Teardrop icon overlay -->
-    <string name="icon_shape_teardrop_overlay" translatable="false">Teardrop Icons</string>
+    <string name="icon_shape_teardrop_overlay" translatable="false">Teardrop</string>
 
 </resources>
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 49a3186..fbb9e29 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.proto.ProtoOutputStream;
 import android.view.InsetsState;
@@ -28,6 +27,7 @@
 import android.view.InsetsSourceControl;
 
 import com.android.internal.util.function.TriConsumer;
+import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 
 import java.io.PrintWriter;
@@ -106,8 +106,8 @@
             mTmpRect.inset(mWin.mGivenContentInsets);
         }
         mSource.setFrame(mTmpRect);
-        setServerVisible(mWin.isVisible() && !mWin.mGivenInsetsPending);
-
+        setServerVisible(mWin.wouldBeVisibleIfPolicyIgnored() && mWin.mPolicyVisibility
+                && !mWin.mGivenInsetsPending);
     }
 
     void updateControlForTarget(@Nullable WindowState target) {
@@ -115,15 +115,15 @@
             return;
         }
         if (target == null) {
-            revokeControl();
+            // Cancelling the animation will invoke onAnimationCancelled, resetting all the fields.
+            mWin.cancelAnimation();
             return;
         }
         mAdapter = new ControlAdapter();
         mWin.startAnimation(mDisplayContent.getPendingTransaction(), mAdapter,
-                false /* TODO hidden */);
+                !mClientVisible /* hidden */);
         mControllingWin = target;
         mControl = new InsetsSourceControl(mSource.getType(), mAdapter.mCapturedLeash);
-        setClientVisible(InsetsState.getDefaultVisibly(mSource.getType()));
     }
 
     boolean onInsetsModified(WindowState caller, InsetsSource modifiedSource) {
@@ -135,7 +135,12 @@
     }
 
     private void setClientVisible(boolean clientVisible) {
+        if (mClientVisible == clientVisible) {
+            return;
+        }
         mClientVisible = clientVisible;
+        mDisplayContent.mWmService.mH.sendMessage(PooledLambda.obtainMessage(
+                DisplayContent::layoutAndAssignWindowLayersIfNeeded, mDisplayContent));
         updateVisibility();
     }
 
@@ -152,13 +157,8 @@
         return mControl;
     }
 
-    void revokeControl() {
-        if (mControllingWin != null) {
-
-            // Cancelling the animation will invoke onAnimationCancelled, resetting all the fields.
-            mWin.cancelAnimation();
-        }
-        setClientVisible(InsetsState.getDefaultVisibly(mSource.getType()));
+    boolean isClientVisible() {
+        return mClientVisible;
     }
 
     private class ControlAdapter implements AnimationAdapter {
@@ -186,6 +186,7 @@
         public void onAnimationCancelled(SurfaceControl animationLeash) {
             if (mAdapter == this) {
                 mStateController.notifyControlRevoked(mControllingWin, InsetsSourceProvider.this);
+                setClientVisible(InsetsState.getDefaultVisibly(mSource.getType()));
                 mControl = null;
                 mControllingWin = null;
                 mAdapter = null;
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 8e119bb..cc57b932 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -218,5 +218,11 @@
     void dump(String prefix, PrintWriter pw) {
         pw.println(prefix + "WindowInsetsStateController");
         mState.dump(prefix + "  ", pw);
+        pw.println(prefix + "  " + "Control map:");
+        for (int i = mTypeWinControlMap.size() - 1; i >= 0; i--) {
+            pw.print(prefix + "  ");
+            pw.println(InsetsState.typeToString(mTypeWinControlMap.keyAt(i)) + " -> "
+                    + mTypeWinControlMap.valueAt(i));
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index da63dc9..6acd864 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -219,10 +219,16 @@
                 }
             }
 
-            if (launchMode == WINDOWING_MODE_FREEFORM && !currentParams.mBounds.isEmpty()) {
+            if (!currentParams.mBounds.isEmpty()) {
+                // Carry over bounds from callers regardless of launch mode because bounds is still
+                // used to restore last non-fullscreen bounds when launch mode is not freeform.
+                // Therefore it's not a resolution step for non-freeform launch mode and only
+                // consider it fully resolved only when launch mode is freeform.
                 outParams.mBounds.set(currentParams.mBounds);
-                fullyResolvedCurrentParam = true;
-                if (DEBUG) appendLog("inherit-bounds=" + outParams.mBounds);
+                if (launchMode == WINDOWING_MODE_FREEFORM) {
+                    fullyResolvedCurrentParam = true;
+                    if (DEBUG) appendLog("inherit-bounds=" + outParams.mBounds);
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index e78c12c..cd29b3c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1373,12 +1373,15 @@
 
     @Override
     boolean isVisible() {
-        return wouldBeVisibleIfPolicyIgnored() && mPolicyVisibility;
+        return wouldBeVisibleIfPolicyIgnored() && mPolicyVisibility
+                // If we don't have a provider, this window isn't used as a window generating
+                // insets, so nobody can hide it over the inset APIs.
+                && (mInsetProvider == null || mInsetProvider.isClientVisible());
     }
 
     /**
-     * @return True if the window would be visible if we'd ignore policy visibility, false
-     *         otherwise.
+     * @return {@code true} if the window would be visible if we'd ignore policy visibility,
+     *         {@code false} otherwise.
      */
     boolean wouldBeVisibleIfPolicyIgnored() {
         return mHasSurface && !isParentWindowHidden()
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index 41cdcc0..53e99fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -787,6 +787,21 @@
     }
 
     @Test
+    public void testReturnBoundsForFullscreenWindowingMode() {
+        final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+                WINDOWING_MODE_FREEFORM);
+
+        mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+        mCurrent.mWindowingMode = WINDOWING_MODE_FULLSCREEN;
+        mCurrent.mBounds.set(0, 0, 200, 100);
+
+        assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+                mActivity, /* source */ null, /* options */ null, mCurrent, mResult));
+
+        assertEquals(new Rect(0, 0, 200, 100), mResult.mBounds);
+    }
+
+    @Test
     public void testUsesDisplayOrientationForNoSensorOrientation() {
         final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
                 WINDOWING_MODE_FREEFORM);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 7f78034..c09cd46 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -19,6 +19,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.hardware.camera2.params.OutputConfiguration.ROTATION_90;
+import static android.view.InsetsState.TYPE_TOP_BAR;
 import static android.view.Surface.ROTATION_0;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -31,6 +32,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
@@ -56,6 +58,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.Size;
 import android.view.DisplayCutout;
+import android.view.InsetsSource;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 
@@ -326,6 +329,20 @@
     }
 
     @Test
+    public void testVisibleWithInsetsProvider() throws Exception {
+        final WindowState topBar = createWindow(null, TYPE_STATUS_BAR, "topBar");
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        topBar.mHasSurface = true;
+        assertTrue(topBar.isVisible());
+        mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_TOP_BAR)
+                .setWindow(topBar, null);
+        mDisplayContent.getInsetsStateController().onBarControllingWindowChanged(app);
+        mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_TOP_BAR)
+                .onInsetsModified(app, new InsetsSource(TYPE_TOP_BAR));
+        assertFalse(topBar.isVisible());
+    }
+
+    @Test
     public void testIsSelfOrAncestorWindowAnimating() {
         final WindowState root = createWindow(null, TYPE_APPLICATION, "root");
         final WindowState child1 = createWindow(root, FIRST_SUB_WINDOW, "child1");
diff --git a/tools/aapt/ConfigDescription.h b/tools/aapt/ConfigDescription.h
index 09430f2..b4ea624 100644
--- a/tools/aapt/ConfigDescription.h
+++ b/tools/aapt/ConfigDescription.h
@@ -29,7 +29,7 @@
         size = sizeof(android::ResTable_config);
     }
 
-    ConfigDescription(const android::ResTable_config&o) {  // NOLINT(implicit)
+    ConfigDescription(const android::ResTable_config&o) {  // NOLINT(google-explicit-constructor)
         *static_cast<android::ResTable_config*>(this) = o;
         size = sizeof(android::ResTable_config);
     }
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 9460c9e..5f664f5 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -445,7 +445,7 @@
  public:
   using xml::ConstVisitor::Visit;
 
-  XmlPrinter(Printer* printer) : printer_(printer) {
+  explicit XmlPrinter(Printer* printer) : printer_(printer) {
   }
 
   void Visit(const xml::Element* el) override {
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index dd5c751..67ba895 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -113,7 +113,7 @@
   ResourceNameRef() = default;
   ResourceNameRef(const ResourceNameRef&) = default;
   ResourceNameRef(ResourceNameRef&&) = default;
-  ResourceNameRef(const ResourceName& rhs);  // NOLINT(implicit)
+  ResourceNameRef(const ResourceName& rhs);  // NOLINT(google-explicit-constructor)
   ResourceNameRef(const android::StringPiece& p, ResourceType t, const android::StringPiece& e);
   ResourceNameRef& operator=(const ResourceNameRef& rhs) = default;
   ResourceNameRef& operator=(ResourceNameRef&& rhs) = default;
@@ -144,7 +144,7 @@
 
   ResourceId();
   ResourceId(const ResourceId& rhs);
-  ResourceId(uint32_t res_id);  // NOLINT(implicit)
+  ResourceId(uint32_t res_id);  // NOLINT(google-explicit-constructor)
   ResourceId(uint8_t p, uint8_t t, uint16_t e);
 
   bool is_valid() const;
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index f63a074..52375a3 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -584,7 +584,7 @@
 
 class CompileContext : public IAaptContext {
  public:
-  CompileContext(IDiagnostics* diagnostics) : diagnostics_(diagnostics) {
+  explicit CompileContext(IDiagnostics* diagnostics) : diagnostics_(diagnostics) {
   }
 
   PackageType GetPackageType() override {
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index f5b7acf..729447e 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -78,7 +78,7 @@
 
 class LinkContext : public IAaptContext {
  public:
-  LinkContext(IDiagnostics* diagnostics)
+  explicit LinkContext(IDiagnostics* diagnostics)
       : diagnostics_(diagnostics), name_mangler_({}), symbols_(&name_mangler_) {
   }
 
@@ -163,7 +163,7 @@
 // See b/37498913.
 class FeatureSplitSymbolTableDelegate : public DefaultSymbolTableDelegate {
  public:
-  FeatureSplitSymbolTableDelegate(IAaptContext* context) : context_(context) {
+  explicit FeatureSplitSymbolTableDelegate(IAaptContext* context) : context_(context) {
   }
 
   virtual ~FeatureSplitSymbolTableDelegate() = default;
diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h
index 38b4860..f9656d1 100644
--- a/tools/aapt2/java/ProguardRules.h
+++ b/tools/aapt2/java/ProguardRules.h
@@ -49,7 +49,7 @@
  public:
   KeepSet() = default;
 
-  KeepSet(bool conditional_keep_rules) : conditional_keep_rules_(conditional_keep_rules) {
+  explicit KeepSet(bool conditional_keep_rules) : conditional_keep_rules_(conditional_keep_rules) {
   }
 
   inline void AddManifestClass(const UsageLocation& file, const std::string& class_name) {
diff --git a/tools/aapt2/link/XmlCompatVersioner.h b/tools/aapt2/link/XmlCompatVersioner.h
index 099e23c..9980618 100644
--- a/tools/aapt2/link/XmlCompatVersioner.h
+++ b/tools/aapt2/link/XmlCompatVersioner.h
@@ -55,7 +55,7 @@
  public:
   using Rules = std::unordered_map<ResourceId, std::unique_ptr<IDegradeRule>>;
 
-  XmlCompatVersioner(const Rules* rules);
+  explicit XmlCompatVersioner(const Rules* rules);
 
   std::vector<std::unique_ptr<xml::XmlResource>> Process(IAaptContext* context,
                                                          xml::XmlResource* doc,
@@ -83,7 +83,7 @@
 
 class DegradeToManyRule : public IDegradeRule {
  public:
-  DegradeToManyRule(std::vector<ReplacementAttr> attrs);
+  explicit DegradeToManyRule(std::vector<ReplacementAttr> attrs);
   virtual ~DegradeToManyRule() = default;
 
   std::vector<DegradeResult> Degrade(const xml::Element& src_el, const xml::Attribute& src_attr,
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
index 51a2e37..2d8bd02 100644
--- a/tools/aapt2/process/SymbolTable.h
+++ b/tools/aapt2/process/SymbolTable.h
@@ -71,7 +71,7 @@
     bool is_dynamic = false;
   };
 
-  SymbolTable(NameMangler* mangler);
+  explicit SymbolTable(NameMangler* mangler);
 
   // Overrides the default ISymbolTableDelegate, which allows a custom defined strategy for
   // looking up resources from a set of sources.
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index 50b41f1..777ca5c 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -173,10 +173,12 @@
 template <typename TValue>
 class ValueEqMatcher {
  public:
+  // NOLINTNEXTLINE(google-explicit-constructor)
   ValueEqMatcher(TValue expected) : expected_(std::move(expected)) {
   }
 
   template <typename T>
+  // NOLINTNEXTLINE(google-explicit-constructor)
   operator ::testing::Matcher<T>() const {
     return ::testing::Matcher<T>(new ValueEqImpl<T>(&expected_));
   }
@@ -188,10 +190,12 @@
 template <typename TValue>
 class ValueEqPointerMatcher {
  public:
+  // NOLINTNEXTLINE(google-explicit-constructor)
   ValueEqPointerMatcher(const TValue* expected) : expected_(expected) {
   }
 
   template <typename T>
+  // NOLINTNEXTLINE(google-explicit-constructor)
   operator ::testing::Matcher<T>() const {
     return ::testing::Matcher<T>(new ValueEqImpl<T>(expected_));
   }
diff --git a/tools/aapt2/util/Maybe.h b/tools/aapt2/util/Maybe.h
index 031276c..047e1a5 100644
--- a/tools/aapt2/util/Maybe.h
+++ b/tools/aapt2/util/Maybe.h
@@ -44,12 +44,12 @@
   Maybe(const Maybe& rhs);
 
   template <typename U>
-  Maybe(const Maybe<U>& rhs);  // NOLINT(implicit)
+  Maybe(const Maybe<U>& rhs);  // NOLINT(google-explicit-constructor)
 
   Maybe(Maybe&& rhs) noexcept;
 
   template <typename U>
-  Maybe(Maybe<U>&& rhs);  // NOLINT(implicit)
+  Maybe(Maybe<U>&& rhs);  // NOLINT(google-explicit-constructor)
 
   Maybe& operator=(const Maybe& rhs);
 
@@ -64,12 +64,12 @@
   /**
    * Construct a Maybe holding a value.
    */
-  Maybe(const T& value);  // NOLINT(implicit)
+  Maybe(const T& value);  // NOLINT(google-explicit-constructor)
 
   /**
    * Construct a Maybe holding a value.
    */
-  Maybe(T&& value);  // NOLINT(implicit)
+  Maybe(T&& value);  // NOLINT(google-explicit-constructor)
 
   /**
    * True if this holds a value, false if
diff --git a/wifi/java/android/net/wifi/aware/PeerHandle.java b/wifi/java/android/net/wifi/aware/PeerHandle.java
index 8ae4b5a..1603d00 100644
--- a/wifi/java/android/net/wifi/aware/PeerHandle.java
+++ b/wifi/java/android/net/wifi/aware/PeerHandle.java
@@ -16,6 +16,9 @@
 
 package android.net.wifi.aware;
 
+import android.os.Parcel;
+import android.os.Parcelable;
+
 /**
  * Opaque object used to represent a Wi-Fi Aware peer. Obtained from discovery sessions in
  * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], java.util.List)} or
@@ -33,7 +36,7 @@
  * {@link PublishConfig.Builder#setServiceSpecificInfo(byte[])}, or match filter,
  * {@link PublishConfig.Builder#setMatchFilter(java.util.List)}.
  */
-public class PeerHandle {
+public final class PeerHandle implements Parcelable {
     /** @hide */
     public PeerHandle(int peerId) {
         this.peerId = peerId;
@@ -59,4 +62,29 @@
     public int hashCode() {
         return peerId;
     }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(peerId);
+    }
+
+    public static final Creator<PeerHandle> CREATOR = new Creator<PeerHandle>() {
+        @Override
+        public PeerHandle[] newArray(int size) {
+            return new PeerHandle[size];
+        }
+
+        @Override
+        public PeerHandle createFromParcel(Parcel in) {
+            int peerHandle = in.readInt();
+
+            return new PeerHandle(peerHandle);
+        }
+    };
+
 }
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
index e882b6b..ed38c76 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
@@ -1382,4 +1382,24 @@
         assertEquals(cap.getPeerIpv6Addr().toString(), "/fe80::1322:33ff:fe44:5566%5");
         assertEquals(cap.hashCode(), rereadCap.hashCode());
     }
+
+    // PeerHandle tests
+
+    @Test
+    public void testPeerHandleParcel() {
+        final PeerHandle peerHandle = new PeerHandle(5);
+
+        Parcel parcelW = Parcel.obtain();
+        peerHandle.writeToParcel(parcelW, 0);
+        byte[] bytes = parcelW.marshall();
+        parcelW.recycle();
+
+        Parcel parcelR = Parcel.obtain();
+        parcelR.unmarshall(bytes, 0, bytes.length);
+        parcelR.setDataPosition(0);
+        PeerHandle rereadPeerHandle = PeerHandle.CREATOR.createFromParcel(parcelR);
+
+        assertEquals(peerHandle, rereadPeerHandle);
+        assertEquals(peerHandle.hashCode(), rereadPeerHandle.hashCode());
+    }
 }