Add standby ports APIs to LowPowerStandbyManager

During Low Power Standby, most ports on the device will be blocked to
avoid unnecessary wakeups of the application processor.
Privileged apps can request ports to remain to continue to offer
essential functionality during Low Power Standby.

Bug: 234002812
Test: atest LowPowerStandbyControllerTest LowPowerStandbyTest
Change-Id: I67d57c9d020ed5e76eda0566dca57e0b4d4fecbd
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 39c7145..fbe2309 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -325,6 +325,7 @@
     field public static final String SET_CLIP_SOURCE = "android.permission.SET_CLIP_SOURCE";
     field public static final String SET_DEFAULT_ACCOUNT_FOR_CONTACTS = "android.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS";
     field public static final String SET_HARMFUL_APP_WARNINGS = "android.permission.SET_HARMFUL_APP_WARNINGS";
+    field public static final String SET_LOW_POWER_STANDBY_PORTS = "android.permission.SET_LOW_POWER_STANDBY_PORTS";
     field public static final String SET_MEDIA_KEY_LISTENER = "android.permission.SET_MEDIA_KEY_LISTENER";
     field public static final String SET_ORIENTATION = "android.permission.SET_ORIENTATION";
     field public static final String SET_POINTER_SPEED = "android.permission.SET_POINTER_SPEED";
@@ -10642,6 +10643,7 @@
   public final class PowerManager {
     method @RequiresPermission(allOf={android.Manifest.permission.READ_DREAM_STATE, android.Manifest.permission.WRITE_DREAM_STATE}) public void dream(long);
     method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public boolean forceSuspend();
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public java.util.List<android.os.PowerManager.LowPowerStandbyPortDescription> getActiveLowPowerStandbyPorts();
     method @NonNull public android.os.BatterySaverPolicyConfig getFullPowerSavePolicy();
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public android.os.PowerManager.LowPowerStandbyPolicy getLowPowerStandbyPolicy();
     method public int getPowerSaveModeTrigger();
@@ -10649,6 +10651,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplaySuppressed();
     method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplaySuppressedForToken(@NonNull String);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public boolean isLowPowerStandbySupported();
+    method @NonNull @RequiresPermission(android.Manifest.permission.SET_LOW_POWER_STANDBY_PORTS) public android.os.PowerManager.LowPowerStandbyPortsLock newLowPowerStandbyPortsLock(@NonNull java.util.List<android.os.PowerManager.LowPowerStandbyPortDescription>);
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSaveEnabled(boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSavePolicy(@NonNull android.os.BatterySaverPolicyConfig);
     method @RequiresPermission(anyOf={android.Manifest.permission.BATTERY_PREDICTION, android.Manifest.permission.DEVICE_POWER}) public void setBatteryDischargePrediction(@NonNull java.time.Duration, boolean);
@@ -10660,6 +10663,7 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setPowerSaveModeEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void suppressAmbientDisplay(@NonNull String, boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.USER_ACTIVITY}) public void userActivity(long, int, int);
+    field @RequiresPermission(android.Manifest.permission.MANAGE_LOW_POWER_STANDBY) public static final String ACTION_LOW_POWER_STANDBY_PORTS_CHANGED = "android.os.action.LOW_POWER_STANDBY_PORTS_CHANGED";
     field public static final int POWER_SAVE_MODE_TRIGGER_DYNAMIC = 1; // 0x1
     field public static final int POWER_SAVE_MODE_TRIGGER_PERCENTAGE = 0; // 0x0
     field public static final String REBOOT_USERSPACE = "userspace";
@@ -10682,6 +10686,25 @@
     method @NonNull public String getIdentifier();
   }
 
+  public static final class PowerManager.LowPowerStandbyPortDescription {
+    ctor public PowerManager.LowPowerStandbyPortDescription(int, int, int);
+    ctor public PowerManager.LowPowerStandbyPortDescription(int, int, int, @Nullable android.net.LinkAddress);
+    method @Nullable public android.net.LinkAddress getBindAddress();
+    method public int getPortMatcher();
+    method public int getPortNumber();
+    method public int getProtocol();
+    field public static final int MATCH_PORT_LOCAL = 1; // 0x1
+    field public static final int MATCH_PORT_REMOTE = 2; // 0x2
+    field public static final int PROTOCOL_TCP = 1; // 0x1
+    field public static final int PROTOCOL_UDP = 2; // 0x2
+  }
+
+  public final class PowerManager.LowPowerStandbyPortsLock {
+    method @RequiresPermission(android.Manifest.permission.SET_LOW_POWER_STANDBY_PORTS) public void acquire();
+    method protected void finalize();
+    method @RequiresPermission(android.Manifest.permission.SET_LOW_POWER_STANDBY_PORTS) public void release();
+  }
+
   @Deprecated public class PowerWhitelistManager {
     method @Deprecated @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull String);
     method @Deprecated @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull java.util.List<java.lang.String>);
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 3b62881..80ae8a8 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -86,6 +86,12 @@
     boolean isExemptFromLowPowerStandby();
     boolean isReasonAllowedInLowPowerStandby(int reason);
     boolean isFeatureAllowedInLowPowerStandby(String feature);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.SET_LOW_POWER_STANDBY_PORTS)")
+    void acquireLowPowerStandbyPorts(in IBinder token, in List<LowPowerStandbyPortDescription> ports);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.SET_LOW_POWER_STANDBY_PORTS)")
+    void releaseLowPowerStandbyPorts(in IBinder token);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = { android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER })")
+    List<LowPowerStandbyPortDescription> getActiveLowPowerStandbyPorts();
 
     parcelable LowPowerStandbyPolicy {
         String identifier;
@@ -94,6 +100,13 @@
         List<String> allowedFeatures;
     }
 
+    parcelable LowPowerStandbyPortDescription {
+        int protocol;
+        int portMatcher;
+        int portNumber;
+        @nullable String bindAddress;
+    }
+
     @UnsupportedAppUsage
     void reboot(boolean confirm, String reason, boolean wait);
     void rebootSafeMode(boolean confirm, boolean wait);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index c021cad..ea3001b 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -32,6 +32,7 @@
 import android.app.PropertyInvalidatedCache;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.net.LinkAddress;
 import android.service.dreams.Sandman;
 import android.sysprop.InitProperties;
 import android.util.ArrayMap;
@@ -47,6 +48,7 @@
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
@@ -2472,6 +2474,49 @@
     }
 
     /**
+     * Creates a new Low Power Standby ports lock.
+     *
+     * <p>A Low Power Standby ports lock requests that the given ports remain open during
+     * Low Power Standby.
+     * Call {@link LowPowerStandbyPortsLock#acquire} to acquire the lock.
+     * This request is only respected if the calling package is exempt
+     * (see {@link #isExemptFromLowPowerStandby()}), and until the returned
+     * {@code LowPowerStandbyPorts} object is destroyed or has
+     * {@link LowPowerStandbyPortsLock#release} called on it.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.SET_LOW_POWER_STANDBY_PORTS)
+    @NonNull
+    public LowPowerStandbyPortsLock newLowPowerStandbyPortsLock(
+            @NonNull List<LowPowerStandbyPortDescription> ports) {
+        LowPowerStandbyPortsLock standbyPorts = new LowPowerStandbyPortsLock(ports);
+        return standbyPorts;
+    }
+
+    /**
+     * Gets all ports that should remain open in standby.
+     * Only includes ports requested by exempt packages (see {@link #getLowPowerStandbyPolicy()}).
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+            android.Manifest.permission.DEVICE_POWER
+    })
+    @NonNull
+    public List<LowPowerStandbyPortDescription> getActiveLowPowerStandbyPorts() {
+        try {
+            return LowPowerStandbyPortDescription.fromParcelable(
+                    mService.getActiveLowPowerStandbyPorts());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Return whether the given application package name is on the device's power allowlist.
      * Apps can be placed on the allowlist through the settings UI invoked by
      * {@link android.provider.Settings#ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS}.
@@ -2980,6 +3025,19 @@
             "android.os.action.LOW_POWER_STANDBY_POLICY_CHANGED";
 
     /**
+     * Intent that is broadcast when Low Power Standby exempt ports change.
+     * This broadcast is only sent to registered receivers.
+     *
+     * @see #getActiveLowPowerStandbyPorts
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_LOW_POWER_STANDBY)
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_LOW_POWER_STANDBY_PORTS_CHANGED =
+            "android.os.action.LOW_POWER_STANDBY_PORTS_CHANGED";
+
+    /**
      * Signals that wake-on-lan/wake-on-wlan is allowed in Low Power Standby.
      *
      * @see #isAllowedInLowPowerStandby(String)
@@ -3148,6 +3206,322 @@
     }
 
     /**
+     * Describes ports that may be requested to remain open during Low Power Standby.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class LowPowerStandbyPortDescription {
+        /** @hide */
+        @IntDef(prefix = { "PROTOCOL_" }, value = {
+                PROTOCOL_TCP,
+                PROTOCOL_UDP,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface Protocol {
+        }
+
+        /**
+         * Constant to indicate the {@link LowPowerStandbyPortDescription} refers to a TCP port.
+         */
+        public static final int PROTOCOL_TCP = 1;
+        /**
+         * Constant to indicate the {@link LowPowerStandbyPortDescription} refers to a UDP port.
+         */
+        public static final int PROTOCOL_UDP = 2;
+
+        /** @hide */
+        @IntDef(prefix = { "MATCH_PORT_" }, value = {
+                MATCH_PORT_LOCAL,
+                MATCH_PORT_REMOTE,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface PortMatcher {
+        }
+        /**
+         * Constant to indicate the {@link LowPowerStandbyPortDescription}'s port number is to be
+         * matched against the socket's local port number (the destination port number of an
+         * incoming packet).
+         */
+        public static final int MATCH_PORT_LOCAL = 1;
+        /**
+         * Constant to indicate the {@link LowPowerStandbyPortDescription}'s port number is to be
+         * matched against the socket's remote port number (the source port number of an
+         * incoming packet).
+         */
+        public static final int MATCH_PORT_REMOTE = 2;
+
+        @Protocol
+        private final int mProtocol;
+        @PortMatcher
+        private final int mPortMatcher;
+        private final int mPortNumber;
+        @Nullable
+        private final LinkAddress mBindAddress;
+
+        /**
+         * Describes a port.
+         *
+         * @param protocol The protocol of the port to match, {@link #PROTOCOL_TCP} or
+         *                 {@link #PROTOCOL_UDP}.
+         * @param portMatcher Whether to match the source port number of an incoming packet
+         *                    ({@link #MATCH_PORT_REMOTE}), or the destination port
+         *                    ({@link #MATCH_PORT_LOCAL}).
+         * @param portNumber The port number to match.
+         *
+         * @see #newLowPowerStandbyPortsLock(List)
+         */
+        public LowPowerStandbyPortDescription(@Protocol int protocol, @PortMatcher int portMatcher,
+                int portNumber) {
+            this.mProtocol = protocol;
+            this.mPortMatcher = portMatcher;
+            this.mPortNumber = portNumber;
+            this.mBindAddress = null;
+        }
+
+        /**
+         * Describes a port.
+         *
+         * @param protocol The protocol of the port to match, {@link #PROTOCOL_TCP} or
+         *                 {@link #PROTOCOL_UDP}.
+         * @param portMatcher Whether to match the source port number of an incoming packet
+         *                    ({@link #MATCH_PORT_REMOTE}), or the destination port
+         *                    ({@link #MATCH_PORT_LOCAL}).
+         * @param portNumber The port number to match.
+         * @param bindAddress The bind address to match.
+         *
+         * @see #newLowPowerStandbyPortsLock(List)
+         */
+        public LowPowerStandbyPortDescription(@Protocol int protocol, @PortMatcher int portMatcher,
+                int portNumber, @Nullable LinkAddress bindAddress) {
+            this.mProtocol = protocol;
+            this.mPortMatcher = portMatcher;
+            this.mPortNumber = portNumber;
+            this.mBindAddress = bindAddress;
+        }
+
+        private String protocolToString(int protocol) {
+            switch (protocol) {
+                case PROTOCOL_TCP: return "TCP";
+                case PROTOCOL_UDP: return "UDP";
+            }
+            return String.valueOf(protocol);
+        }
+
+        private String portMatcherToString(int portMatcher) {
+            switch (portMatcher) {
+                case MATCH_PORT_LOCAL: return "MATCH_PORT_LOCAL";
+                case MATCH_PORT_REMOTE: return "MATCH_PORT_REMOTE";
+            }
+            return String.valueOf(portMatcher);
+        }
+
+        /**
+         * Returns the described port's protocol,
+         * either {@link #PROTOCOL_TCP} or {@link #PROTOCOL_UDP}.
+         *
+         * @see #PROTOCOL_TCP
+         * @see #PROTOCOL_UDP
+         * @see #getPortNumber()
+         * @see #getPortMatcher()
+         */
+        @Protocol
+        public int getProtocol() {
+            return mProtocol;
+        }
+
+        /**
+         * Returns how the port number ({@link #getPortNumber()}) should be matched against
+         * incoming packets.
+         * Either {@link #PROTOCOL_TCP} or {@link #PROTOCOL_UDP}.
+         *
+         * @see #PROTOCOL_TCP
+         * @see #PROTOCOL_UDP
+         * @see #getPortNumber()
+         * @see #getProtocol()
+         */
+        @PortMatcher
+        public int getPortMatcher() {
+            return mPortMatcher;
+        }
+
+        /**
+         * Returns how the port number that incoming packets should be matched against.
+         *
+         * @see #getPortMatcher()
+         * @see #getProtocol()
+         */
+        public int getPortNumber() {
+            return mPortNumber;
+        }
+
+        /**
+         * Returns the bind address to match against, or {@code null} if matching against any
+         * bind address.
+         *
+         * @see #getPortMatcher()
+         * @see #getProtocol()
+         */
+        @Nullable
+        public LinkAddress getBindAddress() {
+            return mBindAddress;
+        }
+
+        @Override
+        public String toString() {
+            return "PortDescription{"
+                    + "mProtocol=" + protocolToString(mProtocol)
+                    + ", mPortMatcher=" + portMatcherToString(mPortMatcher)
+                    + ", mPortNumber=" + mPortNumber
+                    + ", mBindAddress=" + mBindAddress
+                    + '}';
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof LowPowerStandbyPortDescription)) return false;
+            LowPowerStandbyPortDescription that = (LowPowerStandbyPortDescription) o;
+            return mProtocol == that.mProtocol && mPortMatcher == that.mPortMatcher
+                    && mPortNumber == that.mPortNumber && Objects.equals(mBindAddress,
+                    that.mBindAddress);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mProtocol, mPortMatcher, mPortNumber, mBindAddress);
+        }
+
+        /** @hide */
+        public static IPowerManager.LowPowerStandbyPortDescription toParcelable(
+                LowPowerStandbyPortDescription portDescription) {
+            if (portDescription == null) {
+                return null;
+            }
+
+            IPowerManager.LowPowerStandbyPortDescription parcelablePortDescription =
+                    new IPowerManager.LowPowerStandbyPortDescription();
+            parcelablePortDescription.protocol = portDescription.mProtocol;
+            parcelablePortDescription.portMatcher = portDescription.mPortMatcher;
+            parcelablePortDescription.portNumber = portDescription.mPortNumber;
+            if (portDescription.mBindAddress != null) {
+                parcelablePortDescription.bindAddress = portDescription.mBindAddress.toString();
+            }
+            return parcelablePortDescription;
+        }
+
+        /** @hide */
+        public static List<IPowerManager.LowPowerStandbyPortDescription> toParcelable(
+                List<LowPowerStandbyPortDescription> portDescriptions) {
+            if (portDescriptions == null) {
+                return null;
+            }
+
+            ArrayList<IPowerManager.LowPowerStandbyPortDescription> result = new ArrayList<>();
+            for (LowPowerStandbyPortDescription port : portDescriptions) {
+                result.add(toParcelable(port));
+            }
+            return result;
+        }
+
+        /** @hide */
+        public static LowPowerStandbyPortDescription fromParcelable(
+                IPowerManager.LowPowerStandbyPortDescription parcelablePortDescription) {
+            if (parcelablePortDescription == null) {
+                return null;
+            }
+
+            LinkAddress bindAddress = null;
+            if (parcelablePortDescription.bindAddress != null) {
+                bindAddress = new LinkAddress(parcelablePortDescription.bindAddress);
+            }
+            return new LowPowerStandbyPortDescription(
+                    parcelablePortDescription.protocol,
+                    parcelablePortDescription.portMatcher,
+                    parcelablePortDescription.portNumber,
+                    bindAddress);
+        }
+
+        /** @hide */
+        public static List<LowPowerStandbyPortDescription> fromParcelable(
+                List<IPowerManager.LowPowerStandbyPortDescription> portDescriptions) {
+            if (portDescriptions == null) {
+                return null;
+            }
+
+            ArrayList<LowPowerStandbyPortDescription> result = new ArrayList<>();
+            for (IPowerManager.LowPowerStandbyPortDescription port : portDescriptions) {
+                result.add(fromParcelable(port));
+            }
+            return result;
+        }
+    }
+
+    /**
+     * An object that can be used to request network ports to remain open during Low Power Standby.
+     *
+     * <p>Use {@link #newLowPowerStandbyPortsLock} to create a ports lock, and {@link #acquire()}
+     * to request the ports to remain open. The request is only respected if the app requesting the
+     * lock is exempt from Low Power Standby ({@link #isExemptFromLowPowerStandby()}).
+     *
+     * @hide
+     */
+    @SystemApi
+    @SuppressLint("NotCloseable")
+    public final class LowPowerStandbyPortsLock {
+        private final IBinder mToken;
+        private final List<LowPowerStandbyPortDescription> mPorts;
+        private boolean mHeld;
+
+        LowPowerStandbyPortsLock(List<LowPowerStandbyPortDescription> ports) {
+            mPorts = ports;
+            mToken = new Binder();
+        }
+
+        /** Request the ports to remain open during standby. */
+        @RequiresPermission(android.Manifest.permission.SET_LOW_POWER_STANDBY_PORTS)
+        public void acquire() {
+            synchronized (mToken) {
+                try {
+                    mService.acquireLowPowerStandbyPorts(mToken,
+                            LowPowerStandbyPortDescription.toParcelable(mPorts));
+                    mHeld = true;
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+
+        /**
+         * Release the request, allowing these ports to be blocked during standby.
+         *
+         * <p>Note: This lock is not reference counted, so calling this method will release the lock
+         * regardless of how many times {@link #acquire()} has been called before.
+         */
+        @RequiresPermission(android.Manifest.permission.SET_LOW_POWER_STANDBY_PORTS)
+        public void release() {
+            synchronized (mToken) {
+                try {
+                    mService.releaseLowPowerStandbyPorts(mToken);
+                    mHeld = false;
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+
+        @Override
+        protected void finalize() {
+            synchronized (mToken) {
+                if (mHeld) {
+                    Log.wtf(TAG, "LowPowerStandbyPorts finalized while still held");
+                    release();
+                }
+            }
+        }
+    }
+
+    /**
      * Constant for PreIdleTimeout normal mode (default mode, not short nor extend timeout) .
      * @hide
      */
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a4818c7..58fd50b 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -109,6 +109,7 @@
     <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED_INTERNAL" />
     <protected-broadcast android:name="android.os.action.LOW_POWER_STANDBY_ENABLED_CHANGED" />
     <protected-broadcast android:name="android.os.action.LOW_POWER_STANDBY_POLICY_CHANGED" />
+    <protected-broadcast android:name="android.os.action.LOW_POWER_STANDBY_PORTS_CHANGED" />
     <protected-broadcast android:name="android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED" />
 
     <!-- @deprecated This is rarely used and will be phased out soon. -->
@@ -5940,6 +5941,12 @@
     <permission android:name="android.permission.MANAGE_LOW_POWER_STANDBY"
                 android:protectionLevel="signature|privileged" />
 
+    <!-- @hide @SystemApi Allows an application to request ports to remain open during
+         Low Power Standby.
+         <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.SET_LOW_POWER_STANDBY_PORTS"
+        android:protectionLevel="signature|privileged" />
+
    <!-- @hide Allows low-level access to tun tap driver -->
     <permission android:name="android.permission.NET_TUNNELING"
         android:protectionLevel="signature|role" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index a37cb80..9ebcc75 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -281,6 +281,7 @@
         <permission name="android.permission.MANAGE_GAME_MODE"/>
         <permission name="android.permission.MANAGE_GAME_ACTIVITY" />
         <permission name="android.permission.MANAGE_LOW_POWER_STANDBY" />
+        <permission name="android.permission.SET_LOW_POWER_STANDBY_PORTS" />
         <permission name="android.permission.MANAGE_ROLLBACKS"/>
         <permission name="android.permission.MANAGE_USB"/>
         <!-- Needed for tests only -->
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 0664061..464672c 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -265,6 +265,7 @@
     <uses-permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS" />
     <uses-permission android:name="android.permission.MANAGE_APP_PREDICTIONS" />
     <uses-permission android:name="android.permission.MANAGE_LOW_POWER_STANDBY" />
+    <uses-permission android:name="android.permission.SET_LOW_POWER_STANDBY_PORTS" />
     <uses-permission android:name="android.permission.MANAGE_SEARCH_UI" />
     <uses-permission android:name="android.permission.MANAGE_SMARTSPACE" />
     <uses-permission android:name="android.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION" />
diff --git a/services/core/java/com/android/server/power/LowPowerStandbyController.java b/services/core/java/com/android/server/power/LowPowerStandbyController.java
index cf59a7c..d3dafa4 100644
--- a/services/core/java/com/android/server/power/LowPowerStandbyController.java
+++ b/services/core/java/com/android/server/power/LowPowerStandbyController.java
@@ -19,6 +19,7 @@
 import static android.os.PowerManager.LOW_POWER_STANDBY_ALLOWED_REASON_TEMP_POWER_SAVE_ALLOWLIST;
 import static android.os.PowerManager.lowPowerStandbyAllowedReasonsToString;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AlarmManager;
@@ -33,12 +34,15 @@
 import android.net.Uri;
 import android.os.Environment;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.PowerManager.LowPowerStandbyAllowedReason;
 import android.os.PowerManager.LowPowerStandbyPolicy;
+import android.os.PowerManager.LowPowerStandbyPortDescription;
 import android.os.PowerManagerInternal;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -111,6 +115,7 @@
     private static final int MSG_NOTIFY_ACTIVE_CHANGED = 1;
     private static final int MSG_NOTIFY_ALLOWLIST_CHANGED = 2;
     private static final int MSG_NOTIFY_POLICY_CHANGED = 3;
+    private static final int MSG_NOTIFY_STANDBY_PORTS_CHANGED = 4;
 
     private static final String TAG_ROOT = "low-power-standby-policy";
     private static final String TAG_IDENTIFIER = "identifier";
@@ -131,9 +136,11 @@
             this::onStandbyTimeoutExpired;
     private final LowPowerStandbyControllerInternal mLocalService = new LocalService();
     private final SparseIntArray mUidAllowedReasons = new SparseIntArray();
+    private final List<StandbyPortsLock> mStandbyPortLocks = new ArrayList<>();
 
     @GuardedBy("mLock")
     private boolean mEnableCustomPolicy;
+    private boolean mEnableStandbyPorts;
 
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
@@ -189,6 +196,49 @@
         }
     };
 
+    private final class StandbyPortsLock implements IBinder.DeathRecipient {
+        private final IBinder mToken;
+        private final int mUid;
+        private final List<LowPowerStandbyPortDescription> mPorts;
+
+        StandbyPortsLock(IBinder token, int uid, List<LowPowerStandbyPortDescription> ports) {
+            mToken = token;
+            mUid = uid;
+            mPorts = ports;
+        }
+
+        public boolean linkToDeath() {
+            try {
+                mToken.linkToDeath(this, 0);
+                return true;
+            } catch (RemoteException e) {
+                Slog.i(TAG, "StandbyPorts token already died");
+                return false;
+            }
+        }
+
+        public void unlinkToDeath() {
+            mToken.unlinkToDeath(this, 0);
+        }
+
+        public IBinder getToken() {
+            return mToken;
+        }
+
+        public int getUid() {
+            return mUid;
+        }
+
+        public List<LowPowerStandbyPortDescription> getPorts() {
+            return mPorts;
+        }
+
+        @Override
+        public void binderDied() {
+            releaseStandbyPorts(mToken);
+        }
+    }
+
     @GuardedBy("mLock")
     private AlarmManager mAlarmManager;
     @GuardedBy("mLock")
@@ -311,6 +361,7 @@
             mDeviceConfig.registerPropertyUpdateListener(mContext.getMainExecutor(),
                     properties -> onDeviceConfigFlagsChanged());
             mEnableCustomPolicy = mDeviceConfig.enableCustomPolicy();
+            mEnableStandbyPorts = mDeviceConfig.enableStandbyPorts();
 
             if (mEnableCustomPolicy) {
                 mPolicy = loadPolicy();
@@ -336,6 +387,8 @@
                 enqueueNotifyAllowlistChangedLocked();
                 mEnableCustomPolicy = enableCustomPolicy;
             }
+
+            mEnableStandbyPorts = mDeviceConfig.enableStandbyPorts();
         }
     }
 
@@ -861,6 +914,78 @@
         }
     }
 
+    private int findIndexOfStandbyPorts(@NonNull IBinder token) {
+        for (int i = 0; i < mStandbyPortLocks.size(); i++) {
+            if (mStandbyPortLocks.get(i).getToken() == token) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    void acquireStandbyPorts(@NonNull IBinder token, int uid,
+            @NonNull List<LowPowerStandbyPortDescription> ports) {
+        validatePorts(ports);
+
+        StandbyPortsLock standbyPortsLock = new StandbyPortsLock(token, uid, ports);
+        synchronized (mLock) {
+            if (findIndexOfStandbyPorts(token) != -1) {
+                return;
+            }
+
+            if (standbyPortsLock.linkToDeath()) {
+                mStandbyPortLocks.add(standbyPortsLock);
+                if (mEnableStandbyPorts && isEnabled() && isPackageExempt(uid)) {
+                    enqueueNotifyStandbyPortsChangedLocked();
+                }
+            }
+        }
+    }
+
+    void validatePorts(@NonNull List<LowPowerStandbyPortDescription> ports) {
+        for (LowPowerStandbyPortDescription portDescription : ports) {
+            int port = portDescription.getPortNumber();
+            if (port < 0 || port > 0xFFFF) {
+                throw new IllegalArgumentException("port out of range:" + port);
+            }
+        }
+    }
+
+    void releaseStandbyPorts(@NonNull IBinder token) {
+        synchronized (mLock) {
+            int index = findIndexOfStandbyPorts(token);
+            if (index == -1) {
+                return;
+            }
+
+            StandbyPortsLock standbyPortsLock = mStandbyPortLocks.remove(index);
+            standbyPortsLock.unlinkToDeath();
+            if (mEnableStandbyPorts && isEnabled() && isPackageExempt(standbyPortsLock.getUid())) {
+                enqueueNotifyStandbyPortsChangedLocked();
+            }
+        }
+    }
+
+    @NonNull
+    List<LowPowerStandbyPortDescription> getActiveStandbyPorts() {
+        List<LowPowerStandbyPortDescription> activeStandbyPorts = new ArrayList<>();
+        synchronized (mLock) {
+            if (!isEnabled() || !mEnableStandbyPorts) {
+                return activeStandbyPorts;
+            }
+
+            List<Integer> exemptPackageAppIds = getExemptPackageAppIdsLocked();
+            for (StandbyPortsLock standbyPortsLock : mStandbyPortLocks) {
+                int standbyPortsAppid = UserHandle.getAppId(standbyPortsLock.getUid());
+                if (exemptPackageAppIds.contains(standbyPortsAppid)) {
+                    activeStandbyPorts.addAll(standbyPortsLock.getPorts());
+                }
+            }
+
+            return activeStandbyPorts;
+        }
+    }
+
     private boolean policyChangeAffectsAllowlistLocked(
             @Nullable LowPowerStandbyPolicy oldPolicy, @Nullable LowPowerStandbyPolicy newPolicy) {
         final LowPowerStandbyPolicy policyA = policyOrDefault(oldPolicy);
@@ -939,6 +1064,17 @@
                 }
             }
             ipw.decreaseIndent();
+
+            final List<LowPowerStandbyPortDescription> activeStandbyPorts = getActiveStandbyPorts();
+            if (!activeStandbyPorts.isEmpty()) {
+                ipw.println();
+                ipw.println("Active standby ports locks:");
+                ipw.increaseIndent();
+                for (LowPowerStandbyPortDescription portDescription : activeStandbyPorts) {
+                    ipw.print(portDescription.toString());
+                }
+                ipw.decreaseIndent();
+            }
         }
         ipw.decreaseIndent();
     }
@@ -1002,6 +1138,9 @@
                 case MSG_NOTIFY_POLICY_CHANGED:
                     notifyPolicyChanged((LowPowerStandbyPolicy) msg.obj);
                     break;
+                case MSG_NOTIFY_STANDBY_PORTS_CHANGED:
+                    notifyStandbyPortsChanged();
+                    break;
             }
         }
     }
@@ -1156,6 +1295,29 @@
         npmi.setLowPowerStandbyAllowlist(allowlistUids);
     }
 
+    @GuardedBy("mLock")
+    private void enqueueNotifyStandbyPortsChangedLocked() {
+        final long now = mClock.elapsedRealtime();
+
+        if (DEBUG) {
+            Slog.d(TAG, "enqueueNotifyStandbyPortsChangedLocked");
+        }
+
+        final Message msg = mHandler.obtainMessage(MSG_NOTIFY_STANDBY_PORTS_CHANGED);
+        mHandler.sendMessageAtTime(msg, now);
+    }
+
+    private void notifyStandbyPortsChanged() {
+        if (DEBUG) {
+            Slog.d(TAG, "notifyStandbyPortsChanged");
+        }
+
+        final Intent intent = new Intent(PowerManager.ACTION_LOW_POWER_STANDBY_PORTS_CHANGED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+        mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                Manifest.permission.MANAGE_LOW_POWER_STANDBY);
+    }
+
     /**
      * Class that is used to read device config for low power standby configuration.
      */
@@ -1163,6 +1325,7 @@
     public static class DeviceConfigWrapper {
         public static final String NAMESPACE = "low_power_standby";
         public static final String FEATURE_FLAG_ENABLE_POLICY = "enable_policy";
+        public static final String FEATURE_FLAG_ENABLE_STANDBY_PORTS = "enable_standby_ports";
 
         /**
          * Returns true if custom policies are enabled.
@@ -1173,6 +1336,14 @@
         }
 
         /**
+         * Returns true if standby ports are enabled.
+         * Otherwise, returns false, and {@link #getActiveStandbyPorts()} will always be empty.
+         */
+        public boolean enableStandbyPorts() {
+            return DeviceConfig.getBoolean(NAMESPACE, FEATURE_FLAG_ENABLE_STANDBY_PORTS, false);
+        }
+
+        /**
          * Registers a DeviceConfig update listener.
          */
         public void registerPropertyUpdateListener(
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index ddf70f3..b83d509 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -6226,6 +6226,61 @@
             }
         }
 
+        @Override // Binder call
+        @RequiresPermission(android.Manifest.permission.SET_LOW_POWER_STANDBY_PORTS)
+        public void acquireLowPowerStandbyPorts(IBinder token,
+                List<LowPowerStandbyPortDescription> ports) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.SET_LOW_POWER_STANDBY_PORTS,
+                    "acquireLowPowerStandbyPorts");
+
+            final int callingUid = Binder.getCallingUid();
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                mLowPowerStandbyController.acquireStandbyPorts(token, callingUid,
+                        PowerManager.LowPowerStandbyPortDescription.fromParcelable(ports));
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override // Binder call
+        @RequiresPermission(android.Manifest.permission.SET_LOW_POWER_STANDBY_PORTS)
+        public void releaseLowPowerStandbyPorts(IBinder token) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.SET_LOW_POWER_STANDBY_PORTS,
+                    "releaseLowPowerStandbyPorts");
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                mLowPowerStandbyController.releaseStandbyPorts(token);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override // Binder call
+        @RequiresPermission(anyOf = {
+                android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+                android.Manifest.permission.DEVICE_POWER
+        })
+        public List<LowPowerStandbyPortDescription> getActiveLowPowerStandbyPorts() {
+            if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
+                    != PackageManager.PERMISSION_GRANTED) {
+                mContext.enforceCallingOrSelfPermission(
+                        android.Manifest.permission.MANAGE_LOW_POWER_STANDBY,
+                        "getActiveLowPowerStandbyPorts");
+            }
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return PowerManager.LowPowerStandbyPortDescription.toParcelable(
+                        mLowPowerStandbyController.getActiveStandbyPorts());
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
         /**
          * Gets the reason for the last time the phone had to reboot.
          *
diff --git a/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java b/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java
index 454d3f3..9ce80c4 100644
--- a/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java
@@ -19,6 +19,9 @@
 import static android.os.PowerManager.LOW_POWER_STANDBY_ALLOWED_REASON_TEMP_POWER_SAVE_ALLOWLIST;
 import static android.os.PowerManager.LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION;
 import static android.os.PowerManager.LOW_POWER_STANDBY_FEATURE_WAKE_ON_LAN;
+import static android.os.PowerManager.LowPowerStandbyPortDescription.MATCH_PORT_LOCAL;
+import static android.os.PowerManager.LowPowerStandbyPortDescription.PROTOCOL_TCP;
+import static android.os.PowerManager.LowPowerStandbyPortDescription.PROTOCOL_UDP;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -47,9 +50,11 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.IPowerManager;
 import android.os.PowerManager;
 import android.os.PowerManager.LowPowerStandbyPolicy;
+import android.os.PowerManager.LowPowerStandbyPortDescription;
 import android.os.PowerManagerInternal;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -98,6 +103,10 @@
     private static final int USER_ID_2 = 10;
     private static final LowPowerStandbyPolicy EMPTY_POLICY = new LowPowerStandbyPolicy(
             "Test policy", Collections.emptySet(), 0, Collections.emptySet());
+    private static final LowPowerStandbyPortDescription PORT_DESC_1 =
+            new LowPowerStandbyPortDescription(PROTOCOL_UDP, MATCH_PORT_LOCAL, 5353);
+    private static final LowPowerStandbyPortDescription PORT_DESC_2 =
+            new LowPowerStandbyPortDescription(PROTOCOL_TCP, MATCH_PORT_LOCAL, 8008);
 
     private LowPowerStandbyController mController;
     private BroadcastInterceptingContext mContextSpy;
@@ -140,6 +149,7 @@
         when(mIPowerManagerMock.isInteractive()).thenReturn(true);
 
         when(mDeviceConfigWrapperMock.enableCustomPolicy()).thenReturn(true);
+        when(mDeviceConfigWrapperMock.enableStandbyPorts()).thenReturn(true);
         mResourcesSpy = spy(mContextSpy.getResources());
         when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
         when(mResourcesSpy.getBoolean(
@@ -703,6 +713,81 @@
         verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[0]);
     }
 
+    @Test
+    public void testStandbyPorts_broadcastChangedIfPackageIsExempt() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(true);
+        mController.setPolicy(policyWithExemptPackages(TEST_PKG1));
+
+        Binder token = new Binder();
+        BroadcastInterceptingContext.FutureIntent futureIntent = mContextSpy.nextBroadcastIntent(
+                PowerManager.ACTION_LOW_POWER_STANDBY_PORTS_CHANGED);
+        mController.acquireStandbyPorts(token, TEST_PKG1_APP_ID, List.of(PORT_DESC_1));
+        mTestLooper.dispatchAll();
+        assertThat(futureIntent.get(1, TimeUnit.SECONDS)).isNotNull();
+
+        futureIntent = mContextSpy.nextBroadcastIntent(
+                PowerManager.ACTION_LOW_POWER_STANDBY_PORTS_CHANGED);
+        mController.releaseStandbyPorts(token);
+        mTestLooper.dispatchAll();
+        assertThat(futureIntent.get(1, TimeUnit.SECONDS)).isNotNull();
+    }
+
+    @Test
+    public void testStandbyPorts_noBroadcastChangedIfPackageIsNotExempt() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(true);
+        mController.setPolicy(policyWithExemptPackages(TEST_PKG1));
+
+        BroadcastInterceptingContext.FutureIntent futureIntent = mContextSpy.nextBroadcastIntent(
+                PowerManager.ACTION_LOW_POWER_STANDBY_PORTS_CHANGED);
+        mController.acquireStandbyPorts(new Binder(), TEST_PKG2_APP_ID, List.of(PORT_DESC_1));
+        mTestLooper.dispatchAll();
+        futureIntent.assertNotReceived();
+    }
+
+    @Test
+    public void testActiveStandbyPorts_emptyIfDisabled() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(false);
+        mController.setPolicy(policyWithExemptPackages(TEST_PKG1));
+
+        mController.acquireStandbyPorts(new Binder(), TEST_PKG1_APP_ID, List.of(PORT_DESC_1));
+        assertThat(mController.getActiveStandbyPorts()).isEmpty();
+    }
+
+    @Test
+    public void testActiveStandbyPorts_emptyIfPackageNotExempt() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(true);
+        mController.setPolicy(policyWithExemptPackages(TEST_PKG2));
+
+        mController.acquireStandbyPorts(new Binder(), TEST_PKG1_APP_ID, List.of(PORT_DESC_1));
+        assertThat(mController.getActiveStandbyPorts()).isEmpty();
+    }
+
+    @Test
+    public void testActiveStandbyPorts_activeIfPackageExempt() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(true);
+        mController.setPolicy(policyWithExemptPackages(TEST_PKG1));
+
+        mController.acquireStandbyPorts(new Binder(), TEST_PKG1_APP_ID, List.of(PORT_DESC_1));
+        mController.acquireStandbyPorts(new Binder(), TEST_PKG2_APP_ID, List.of(PORT_DESC_2));
+        assertThat(mController.getActiveStandbyPorts()).containsExactly(PORT_DESC_1);
+    }
+
+    @Test
+    public void testActiveStandbyPorts_removedAfterRelease() throws Exception {
+        mController.systemReady();
+        mController.setEnabled(true);
+        mController.setPolicy(policyWithExemptPackages(TEST_PKG1));
+        Binder token = new Binder();
+        mController.acquireStandbyPorts(token, TEST_PKG1_APP_ID, List.of(PORT_DESC_1));
+        mController.releaseStandbyPorts(token);
+        assertThat(mController.getActiveStandbyPorts()).isEmpty();
+    }
+
     private void setInteractive() throws Exception {
         when(mIPowerManagerMock.isInteractive()).thenReturn(true);
         mContextSpy.sendBroadcast(new Intent(Intent.ACTION_SCREEN_ON));