diff options
author | 2022-12-21 17:10:48 +0000 | |
---|---|---|
committer | 2023-01-13 18:37:13 +0000 | |
commit | 6b6e255a5549a1a0afc8ae3d2810e54183950f42 (patch) | |
tree | fe95d1e69f4703337f8d227a11ce9d9efbbd964f | |
parent | f9878357ea634f103f25c32ea5da9ed0cb09e035 (diff) |
Add system APIs to add and remove QoS policies.
Both APIs are currently unimplemented.
Bug: 263288200
Test: atest WifiManagerTest \
android.net.wifi.QosPolicyParamsTest
Change-Id: If0326b0ef8af7e8b4bd9618a49df4f9946236867
-rw-r--r-- | framework/aidl-export/android/net/wifi/QosPolicyParams.aidl | 19 | ||||
-rw-r--r-- | framework/api/system-current.txt | 43 | ||||
-rw-r--r-- | framework/java/android/net/wifi/BaseWifiService.java | 10 | ||||
-rw-r--r-- | framework/java/android/net/wifi/IWifiManager.aidl | 5 | ||||
-rw-r--r-- | framework/java/android/net/wifi/QosPolicyParams.java | 525 | ||||
-rw-r--r-- | framework/java/android/net/wifi/WifiManager.java | 54 | ||||
-rw-r--r-- | framework/tests/src/android/net/wifi/QosPolicyParamsTest.java | 169 | ||||
-rw-r--r-- | framework/tests/src/android/net/wifi/WifiManagerTest.java | 34 | ||||
-rw-r--r-- | service/java/com/android/server/wifi/WifiServiceImpl.java | 33 |
9 files changed, 892 insertions, 0 deletions
diff --git a/framework/aidl-export/android/net/wifi/QosPolicyParams.aidl b/framework/aidl-export/android/net/wifi/QosPolicyParams.aidl new file mode 100644 index 0000000000..090f86697c --- /dev/null +++ b/framework/aidl-export/android/net/wifi/QosPolicyParams.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2023, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi; + +parcelable QosPolicyParams; diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index c218b60199..8849fc892b 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -27,6 +27,47 @@ package android.net.wifi { field public static final int EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT = 0; // 0x0 } + public final class QosPolicyParams implements android.os.Parcelable { + method @Nullable public android.net.MacAddress getDestinationAddress(); + method @Nullable public int[] getDestinationPortRange(); + method public int getDirection(); + method public int getDscp(); + method public int getPolicyId(); + method public int getProtocol(); + method @Nullable public android.net.MacAddress getSourceAddress(); + method public int getSourcePort(); + method public int getUserPriority(); + field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.QosPolicyParams> CREATOR; + field public static final int DIRECTION_DOWNLINK = 1; // 0x1 + field public static final int DIRECTION_UPLINK = 0; // 0x0 + field public static final int DSCP_ANY = -1; // 0xffffffff + field public static final int PROTOCOL_ANY = -1; // 0xffffffff + field public static final int PROTOCOL_ESP = 50; // 0x32 + field public static final int PROTOCOL_TCP = 6; // 0x6 + field public static final int PROTOCOL_UDP = 17; // 0x11 + field public static final int USER_PRIORITY_ANY = -1; // 0xffffffff + field public static final int USER_PRIORITY_BACKGROUND_HIGH = 2; // 0x2 + field public static final int USER_PRIORITY_BACKGROUND_LOW = 1; // 0x1 + field public static final int USER_PRIORITY_BEST_EFFORT_HIGH = 3; // 0x3 + field public static final int USER_PRIORITY_BEST_EFFORT_LOW = 0; // 0x0 + field public static final int USER_PRIORITY_VIDEO_HIGH = 5; // 0x5 + field public static final int USER_PRIORITY_VIDEO_LOW = 4; // 0x4 + field public static final int USER_PRIORITY_VOICE_HIGH = 7; // 0x7 + field public static final int USER_PRIORITY_VOICE_LOW = 6; // 0x6 + } + + public static final class QosPolicyParams.Builder { + ctor public QosPolicyParams.Builder(int, int); + method @NonNull public android.net.wifi.QosPolicyParams build(); + method @NonNull public android.net.wifi.QosPolicyParams.Builder setDestinationAddress(@NonNull android.net.MacAddress); + method @NonNull public android.net.wifi.QosPolicyParams.Builder setDestinationPortRange(int, int); + method @NonNull public android.net.wifi.QosPolicyParams.Builder setDscp(int); + method @NonNull public android.net.wifi.QosPolicyParams.Builder setProtocol(int); + method @NonNull public android.net.wifi.QosPolicyParams.Builder setSourceAddress(@NonNull android.net.MacAddress); + method @NonNull public android.net.wifi.QosPolicyParams.Builder setSourcePort(int); + method @NonNull public android.net.wifi.QosPolicyParams.Builder setUserPriority(int); + } + @Deprecated public class RttManager { method @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void disableResponder(android.net.wifi.RttManager.ResponderCallback); method @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void enableResponder(android.net.wifi.RttManager.ResponderCallback); @@ -530,6 +571,7 @@ package android.net.wifi { public class WifiManager { method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public void addOnWifiUsabilityStatsListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiUsabilityStatsListener); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.MANAGE_WIFI_NETWORK_SELECTION}) public void addQosPolicy(@NonNull android.net.wifi.QosPolicyParams); method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void addWifiVerboseLoggingStatusChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.WifiVerboseLoggingStatusChangedListener); method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void allowAutojoin(int, boolean); method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void allowAutojoinPasspoint(@NonNull String, boolean); @@ -582,6 +624,7 @@ package android.net.wifi { method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void registerTrafficStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.TrafficStateCallback); method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void removeAppState(int, @NonNull String); method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public void removeOnWifiUsabilityStatsListener(@NonNull android.net.wifi.WifiManager.OnWifiUsabilityStatsListener); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.MANAGE_WIFI_NETWORK_SELECTION}) public void removeQosPolicy(int); method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void removeWifiVerboseLoggingStatusChangedListener(@NonNull android.net.wifi.WifiManager.WifiVerboseLoggingStatusChangedListener); method @RequiresPermission(android.Manifest.permission.RESTART_WIFI_SUBSYSTEM) public void restartWifiSubsystem(); method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void restoreBackupData(@NonNull byte[]); diff --git a/framework/java/android/net/wifi/BaseWifiService.java b/framework/java/android/net/wifi/BaseWifiService.java index 960dfc8a9e..d3c5ba58f3 100644 --- a/framework/java/android/net/wifi/BaseWifiService.java +++ b/framework/java/android/net/wifi/BaseWifiService.java @@ -954,4 +954,14 @@ public class BaseWifiService extends IWifiManager.Stub { public int getMaxNumberOfChannelsPerRequest() { throw new UnsupportedOperationException(); } + + @Override + public void addQosPolicy(@NonNull QosPolicyParams policyParams, @NonNull IBinder binder) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeQosPolicy(int policyId) { + throw new UnsupportedOperationException(); + } } diff --git a/framework/java/android/net/wifi/IWifiManager.aidl b/framework/java/android/net/wifi/IWifiManager.aidl index 58ddbd8633..5b7ada7d56 100644 --- a/framework/java/android/net/wifi/IWifiManager.aidl +++ b/framework/java/android/net/wifi/IWifiManager.aidl @@ -47,6 +47,7 @@ import android.net.wifi.ITrafficStateCallback; import android.net.wifi.IWifiConnectedNetworkScorer; import android.net.wifi.IWifiNetworkSelectionConfigListener; import android.net.wifi.IWifiVerboseLoggingStatusChangedListener; +import android.net.wifi.QosPolicyParams; import android.net.wifi.ScanResult; import android.net.wifi.SoftApConfiguration; import android.net.wifi.WifiAvailableChannel; @@ -418,4 +419,8 @@ interface IWifiManager void reportCreateInterfaceImpact(String packageName, int interfaceType, boolean requireNewInterface, in IInterfaceCreationInfoCallback callback); int getMaxNumberOfChannelsPerRequest(); + + void addQosPolicy(in QosPolicyParams policyParams, in IBinder binder); + + void removeQosPolicy(int policyId); } diff --git a/framework/java/android/net/wifi/QosPolicyParams.java b/framework/java/android/net/wifi/QosPolicyParams.java new file mode 100644 index 0000000000..e3ddf1022d --- /dev/null +++ b/framework/java/android/net/wifi/QosPolicyParams.java @@ -0,0 +1,525 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.net.DscpPolicy; +import android.net.MacAddress; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.Objects; + +/** + * Parameters for QoS policies requested by system applications. + * @hide + */ +@SystemApi +public final class QosPolicyParams implements Parcelable { + private static final String TAG = "QosPolicyParams"; + + /** + * Indicates that the policy does not specify a DSCP value. + */ + public static final int DSCP_ANY = -1; + + /** + * Indicates that the policy does not specify a protocol. + */ + public static final int PROTOCOL_ANY = DscpPolicy.PROTOCOL_ANY; + + /** + * Policy should match packets using the TCP protocol. + */ + public static final int PROTOCOL_TCP = 6; + + /** + * Policy should match packets using the UDP protocol. + */ + public static final int PROTOCOL_UDP = 17; + + /** + * Policy should match packets using the ESP protocol. + */ + public static final int PROTOCOL_ESP = 50; + + /** @hide */ + @IntDef(prefix = { "PROTOCOL_" }, value = { + PROTOCOL_ANY, + PROTOCOL_TCP, + PROTOCOL_UDP, + PROTOCOL_ESP + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Protocol {} + + /** + * Policy should match packets in the uplink direction. + */ + public static final int DIRECTION_UPLINK = 0; + + /** + * Policy should match packets in the downlink direction. + */ + public static final int DIRECTION_DOWNLINK = 1; + + + /** @hide */ + @IntDef(prefix = { "DIRECTION_" }, value = { + DIRECTION_UPLINK, + DIRECTION_DOWNLINK, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Direction {} + + /** + * Indicates that the policy does not specify a User Priority. + */ + public static final int USER_PRIORITY_ANY = -1; + + /** + * Policy should be assigned a low background priority. + */ + public static final int USER_PRIORITY_BACKGROUND_LOW = 1; + + /** + * Policy should be assigned a high background priority. + */ + public static final int USER_PRIORITY_BACKGROUND_HIGH = 2; + + /** + * Policy should be assigned a low best-effort priority. + */ + public static final int USER_PRIORITY_BEST_EFFORT_LOW = 0; + + /** + * Policy should be assigned a high best-effort priority. + */ + public static final int USER_PRIORITY_BEST_EFFORT_HIGH = 3; + + /** + * Policy should be assigned a low video priority. + */ + public static final int USER_PRIORITY_VIDEO_LOW = 4; + + /** + * Policy should be assigned a high video priority. + */ + public static final int USER_PRIORITY_VIDEO_HIGH = 5; + + /** + * Policy should be assigned a low voice priority. + */ + public static final int USER_PRIORITY_VOICE_LOW = 6; + + /** + * Policy should be assigned a high voice priority. + */ + public static final int USER_PRIORITY_VOICE_HIGH = 7; + + /** @hide */ + @IntDef(prefix = { "USER_PRIORITY_" }, value = { + USER_PRIORITY_ANY, + USER_PRIORITY_BACKGROUND_LOW, + USER_PRIORITY_BACKGROUND_HIGH, + USER_PRIORITY_BEST_EFFORT_LOW, + USER_PRIORITY_BEST_EFFORT_HIGH, + USER_PRIORITY_VIDEO_LOW, + USER_PRIORITY_VIDEO_HIGH, + USER_PRIORITY_VOICE_LOW, + USER_PRIORITY_VOICE_HIGH, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface UserPriority {} + + /** + * Unique policy ID. See {@link Builder#Builder(int, int)} for more information. + */ + private final int mPolicyId; + + // QoS DSCP marking. See {@link Builder#setDscp(int)} for more information. + private final int mDscp; + + // User priority to apply to packets matching the policy. Only applicable to downlink requests. + private final int mUserPriority; + + // Source address. + private final @Nullable MacAddress mSrcAddr; + + // Destination address. + private final @Nullable MacAddress mDstAddr; + + // Source port. + private final int mSrcPort; + + // IP protocol that the policy requires. + private final @Protocol int mProtocol; + + // Destination port range. Inclusive range. + private final @Nullable int[] mDstPortRange; + + // Direction of traffic stream. + private final @Direction int mDirection; + + private QosPolicyParams(int policyId, int dscp, @UserPriority int userPriority, + @Nullable MacAddress srcAddr, @Nullable MacAddress dstAddr, int srcPort, + @Protocol int protocol, @Nullable int[] dstPortRange, @Direction int direction) { + this.mPolicyId = policyId; + this.mDscp = dscp; + this.mUserPriority = userPriority; + this.mSrcAddr = srcAddr; + this.mDstAddr = dstAddr; + this.mSrcPort = srcPort; + this.mProtocol = protocol; + this.mDstPortRange = dstPortRange; + this.mDirection = direction; + } + + /** + * Validate the parameters in this instance. + * + * @return true if all parameters are valid, false otherwise + * @hide + */ + public boolean validate() { + if (mPolicyId < 1 || mPolicyId > 255) { + Log.e(TAG, "Policy ID not in valid range: " + mPolicyId); + return false; + } + if (mDscp < DSCP_ANY || mDscp > 63) { + Log.e(TAG, "DSCP value not in valid range: " + mDscp); + return false; + } + if (mUserPriority < USER_PRIORITY_ANY || mUserPriority > USER_PRIORITY_VOICE_HIGH) { + Log.e(TAG, "User priority not in valid range: " + mUserPriority); + return false; + } + if (mSrcPort < DscpPolicy.SOURCE_PORT_ANY || mSrcPort > 65535) { + Log.e(TAG, "Source port not in valid range: " + mSrcPort); + return false; + } + if (mDstPortRange != null && (mDstPortRange[0] < 0 || mDstPortRange[0] > 65535 + || mDstPortRange[1] < 0 || mDstPortRange[1] > 65535)) { + Log.e(TAG, "Dst port range value not valid. start=" + + mDstPortRange[0] + ", end=" + mDstPortRange[1]); + return false; + } + if (!(mDirection == DIRECTION_UPLINK || mDirection == DIRECTION_DOWNLINK)) { + Log.e(TAG, "Invalid direction enum: " + mDirection); + return false; + } + + // Check DSCP and User Priority based on direction + if (mDirection == DIRECTION_UPLINK && mDscp == DSCP_ANY) { + Log.e(TAG, "DSCP must be provided for uplink requests"); + return false; + } + if (mDirection == DIRECTION_DOWNLINK && mUserPriority == USER_PRIORITY_ANY) { + Log.e(TAG, "User priority must be provided for downlink requests"); + return false; + } + return true; + } + + /** + * Get the ID for this policy. + * + * See {@link Builder#Builder(int, int)} for more information. + */ + public int getPolicyId() { + return mPolicyId; + } + + + /** + * Get the DSCP value for this policy. + * + * See {@link Builder#setDscp(int)} for more information. + * + * @return DSCP value, or {@link #DSCP_ANY} if not assigned. + */ + public int getDscp() { + return mDscp; + } + + /** + * Get the User Priority (UP) for this policy. + * + * See {@link Builder#setUserPriority(int)} for more information. + * + * @return User Priority value, or {@link #USER_PRIORITY_ANY} if not assigned. + */ + public @UserPriority int getUserPriority() { + return mUserPriority; + } + + /** + * Get the source address for this policy. + * + * See {@link Builder#setSourceAddress(MacAddress)} for more information. + * + * @return source address, or null if not assigned. + */ + public @Nullable MacAddress getSourceAddress() { + return mSrcAddr; + } + + /** + * Get the destination address for this policy. + * + * See {@link Builder#setDestinationAddress(MacAddress)} for more information. + * + * @return destination address, or null if not assigned. + */ + public @Nullable MacAddress getDestinationAddress() { + return mDstAddr; + } + + /** + * Get the source port for this policy. + * + * See {@link Builder#setSourcePort(int)} for more information. + * + * @return source port, or {@link DscpPolicy#SOURCE_PORT_ANY} if not assigned. + */ + public int getSourcePort() { + return mSrcPort; + } + + /** + * Get the protocol for this policy. + * + * See {@link Builder#setProtocol(int)} for more information. + * + * @return protocol, or {@link #PROTOCOL_ANY} if not assigned. + */ + public @Protocol int getProtocol() { + return mProtocol; + } + + /** + * Get the destination port range for this policy. + * + * See {@link Builder#setDestinationPortRange(int, int)} for more information. + * + * @return destination port range, or null if not assigned. + */ + public @Nullable int[] getDestinationPortRange() { + return mDstPortRange; + } + + /** + * Get the direction for this policy. + * + * See {@link Builder#Builder(int, int)} for more information. + */ + public @Direction int getDirection() { + return mDirection; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + QosPolicyParams that = (QosPolicyParams) o; + return mPolicyId == that.mPolicyId + && mDscp == that.mDscp + && mUserPriority == that.mUserPriority + && mSrcAddr.equals(that.mSrcAddr) + && mDstAddr.equals(that.mDstAddr) + && mSrcPort == that.mSrcPort + && mProtocol == that.mProtocol + && Arrays.equals(mDstPortRange, that.mDstPortRange) + && mDirection == that.mDirection; + } + + @Override + public int hashCode() { + return Objects.hash(mPolicyId, mDscp, mUserPriority, mSrcAddr, mDstAddr, mSrcPort, + mProtocol, Arrays.hashCode(mDstPortRange), mDirection); + } + + @Override + public String toString() { + return "{policyId=" + mPolicyId + ", " + + "dscp=" + mDscp + ", " + + "userPriority=" + mUserPriority + ", " + + "srcAddr=" + mSrcAddr + ", " + + "dstAddr=" + mDstAddr + ", " + + "srcPort=" + mSrcPort + ", " + + "protocol=" + mProtocol + ", " + + "dstPortRange=" + Arrays.toString(mDstPortRange) + ", " + + "direction=" + mDirection + "}"; + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mPolicyId); + dest.writeInt(mDscp); + dest.writeInt(mUserPriority); + dest.writeParcelable(mSrcAddr, 0); + dest.writeParcelable(mDstAddr, 0); + dest.writeInt(mSrcPort); + dest.writeInt(mProtocol); + dest.writeIntArray(mDstPortRange); + dest.writeInt(mDirection); + } + + /** @hide */ + QosPolicyParams(@NonNull Parcel in) { + this.mPolicyId = in.readInt(); + this.mDscp = in.readInt(); + this.mUserPriority = in.readInt(); + this.mSrcAddr = in.readParcelable(MacAddress.class.getClassLoader()); + this.mDstAddr = in.readParcelable(MacAddress.class.getClassLoader()); + this.mSrcPort = in.readInt(); + this.mProtocol = in.readInt(); + this.mDstPortRange = in.createIntArray(); + this.mDirection = in.readInt(); + } + + public static final @NonNull Parcelable.Creator<QosPolicyParams> CREATOR = + new Parcelable.Creator<QosPolicyParams>() { + @Override + public QosPolicyParams createFromParcel(Parcel in) { + return new QosPolicyParams(in); + } + + @Override + public QosPolicyParams[] newArray(int size) { + return new QosPolicyParams[size]; + } + }; + + /** + * Builder for {@link QosPolicyParams}. + */ + public static final class Builder { + private final int mPolicyId; + private final @Direction int mDirection; + private @Nullable MacAddress mSrcAddr; + private @Nullable MacAddress mDstAddr; + private int mDscp = DSCP_ANY; + private @UserPriority int mUserPriority = USER_PRIORITY_ANY; + private int mSrcPort = DscpPolicy.SOURCE_PORT_ANY; + private int mProtocol = PROTOCOL_ANY; + private @Nullable int[] mDstPortRange; + + /** + * Constructor for {@link Builder}. + * + * @param policyId Unique ID to identify this policy. Each requesting application is + * responsible for maintaining policy IDs unique for that app. IDs must be + * in the range 1 <= policyId <= 255. + * + * In the case where a policy with an existing ID is created, the new policy + * will be rejected. To update an existing policy, remove the existing one + * before sending the new one. + * @param direction Whether this policy applies to the uplink or downlink direction. + */ + public Builder(int policyId, @Direction int direction) { + mPolicyId = policyId; + mDirection = direction; + } + + /** + * Specifies that this policy matches packets with the provided source address. + */ + public @NonNull Builder setSourceAddress(@NonNull MacAddress value) { + Objects.requireNonNull(value, "Source address cannot be null"); + mSrcAddr = value; + return this; + } + + /** + * Specifies that this policy matches packets with the provided destination address. + */ + public @NonNull Builder setDestinationAddress(@NonNull MacAddress value) { + Objects.requireNonNull(value, "Destination address cannot be null"); + mDstAddr = value; + return this; + } + + /** + * Specifies the DSCP value. For uplink requests, this value will be applied to packets + * that match the classifier. For downlink requests, this will be part of the classifier. + */ + public @NonNull Builder setDscp(int value) { + mDscp = value; + return this; + } + + /** + * Specifies that the provided User Priority should be applied to packets that + * match this classifier. Only applicable to downlink requests. + */ + public @NonNull Builder setUserPriority(@UserPriority int value) { + mUserPriority = value; + return this; + } + + /** + * Specifies that this policy matches packets with the provided source port. + */ + public @NonNull Builder setSourcePort(int value) { + mSrcPort = value; + return this; + } + + /** + * Specifies that this policy matches packets with the provided protocol. + */ + public @NonNull Builder setProtocol(@Protocol int value) { + mProtocol = value; + return this; + } + + /** + * Specifies that this policy matches packets with the provided destination port range. + */ + public @NonNull Builder setDestinationPortRange(int start, int end) { + mDstPortRange = new int[]{start, end}; + return this; + } + + /** + * Construct a QosPolicyParams object with the specified parameters. + */ + public @NonNull QosPolicyParams build() { + QosPolicyParams params = new QosPolicyParams(mPolicyId, mDscp, mUserPriority, mSrcAddr, + mDstAddr, mSrcPort, mProtocol, mDstPortRange, mDirection); + if (!params.validate()) { + throw new IllegalArgumentException("Provided parameters are invalid"); + } + return params; + } + } +} diff --git a/framework/java/android/net/wifi/WifiManager.java b/framework/java/android/net/wifi/WifiManager.java index 2aabf2b256..bae22629bb 100644 --- a/framework/java/android/net/wifi/WifiManager.java +++ b/framework/java/android/net/wifi/WifiManager.java @@ -10537,4 +10537,58 @@ public class WifiManager { throw e.rethrowFromSystemServer(); } } + + /** + * Add a new application-initiated QoS policy. + * + * Note: Policies are managed using a policy ID, which can be retrieved using + * {@link QosPolicyParams#getPolicyId()}. This ID can be used when removing a policy via + * {@link #removeQosPolicy(int)}. The caller is in charge of assigning and managing the + * policy IDs for any requested policies. + * + * Note: Policies with duplicate IDs are not allowed. To update an existing policy, first + * remove it using {@link #removeQosPolicy(int)}, and then re-add it using this API. + * + * @param policyParams {@link QosPolicyParams} object describing the requested policy. + * @throws {@link SecurityException} if caller does not have the required permissions. + * @hide + */ + @SystemApi + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + MANAGE_WIFI_NETWORK_SELECTION + }) + public void addQosPolicy(@NonNull QosPolicyParams policyParams) { + try { + mService.addQosPolicy(policyParams, new Binder()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Remove an existing application-initiated QoS policy, previously added via + * {@link #addQosPolicy(QosPolicyParams)}. + * + * Note: The policy is identified by its policy ID, which is assigned by the caller. The ID + * for a given policy can be retrieved using {@link QosPolicyParams#getPolicyId()}. + * + * @param policyId ID of the policy to remove. + * @throws {@link SecurityException} if caller does not have the required permissions. + * @hide + */ + @SystemApi + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + MANAGE_WIFI_NETWORK_SELECTION + }) + public void removeQosPolicy(int policyId) { + try { + mService.removeQosPolicy(policyId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/framework/tests/src/android/net/wifi/QosPolicyParamsTest.java b/framework/tests/src/android/net/wifi/QosPolicyParamsTest.java new file mode 100644 index 0000000000..15e61ae7e3 --- /dev/null +++ b/framework/tests/src/android/net/wifi/QosPolicyParamsTest.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi; + + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +import android.net.DscpPolicy; +import android.net.MacAddress; +import android.os.Parcel; + +import com.android.modules.utils.build.SdkLevel; + +import org.junit.Before; +import org.junit.Test; + +/** + * Unit tests for {@link QosPolicyParams}. + */ +public class QosPolicyParamsTest { + private static final int TEST_POLICY_ID = 127; + private static final int TEST_DIRECTION = QosPolicyParams.DIRECTION_DOWNLINK; + private static final int TEST_DSCP = 7; + private static final int TEST_USER_PRIORITY = QosPolicyParams.USER_PRIORITY_VIDEO_LOW; + private static final int TEST_SOURCE_PORT = 15; + private static final int TEST_PROTOCOL = QosPolicyParams.PROTOCOL_TCP; + private static final int[] TEST_DESTINATION_PORT_RANGE = new int[]{17, 20}; + private static final MacAddress TEST_SOURCE_ADDRESS = + MacAddress.fromString("aa:bb:cc:dd:ee:ff"); + private static final MacAddress TEST_DESTINATION_ADDRESS = + MacAddress.fromString("00:11:22:33:44:55"); + + @Before + public void setUp() { + assumeTrue(SdkLevel.isAtLeastU()); + } + + /** + * Creates a QosPolicyParams object will all fields assigned to a default test value. + */ + private QosPolicyParams createTestQosPolicyParams() { + return new QosPolicyParams.Builder(TEST_POLICY_ID, TEST_DIRECTION) + .setUserPriority(TEST_USER_PRIORITY) + .setDscp(TEST_DSCP) + .setSourcePort(TEST_SOURCE_PORT) + .setProtocol(TEST_PROTOCOL) + .setDestinationPortRange( + TEST_DESTINATION_PORT_RANGE[0], TEST_DESTINATION_PORT_RANGE[1]) + .setSourceAddress(TEST_SOURCE_ADDRESS) + .setDestinationAddress(TEST_DESTINATION_ADDRESS) + .build(); + } + + /** + * Check that all fields in the provided QosPolicyParams object match the default test values. + */ + private void verifyTestQosPolicyParams(QosPolicyParams params) { + assertEquals(TEST_POLICY_ID, params.getPolicyId()); + assertEquals(TEST_DIRECTION, params.getDirection()); + assertEquals(TEST_USER_PRIORITY, params.getUserPriority()); + assertEquals(TEST_DSCP, params.getDscp()); + assertEquals(TEST_SOURCE_PORT, params.getSourcePort()); + assertEquals(TEST_PROTOCOL, params.getProtocol()); + assertArrayEquals(TEST_DESTINATION_PORT_RANGE, params.getDestinationPortRange()); + assertTrue(TEST_SOURCE_ADDRESS.equals(params.getSourceAddress())); + assertTrue(TEST_DESTINATION_ADDRESS.equals(params.getDestinationAddress())); + } + + /** + * Tests that the default parameters are set if they are not assigned by the user. + */ + @Test + public void testDefaultParamsSet() { + QosPolicyParams params = + new QosPolicyParams.Builder(TEST_POLICY_ID, QosPolicyParams.DIRECTION_DOWNLINK) + .setUserPriority(TEST_USER_PRIORITY) + .build(); + assertEquals(QosPolicyParams.DSCP_ANY, params.getDscp()); + assertEquals(QosPolicyParams.PROTOCOL_ANY, params.getProtocol()); + assertEquals(DscpPolicy.SOURCE_PORT_ANY, params.getSourcePort()); + } + + /** + * Test that if we set all the parameters in the Builder, the resulting QosPolicyParams + * object contains the expected values. + */ + @Test + public void testSetAllParams() { + QosPolicyParams params = createTestQosPolicyParams(); + verifyTestQosPolicyParams(params); + } + + /** + * Tests that the Builder throws an exception if an invalid parameter is set. + */ + @Test + public void testBuilderWithInvalidParam() { + assertThrows(IllegalArgumentException.class, () -> + new QosPolicyParams.Builder(TEST_POLICY_ID, QosPolicyParams.DIRECTION_DOWNLINK) + .setUserPriority(TEST_USER_PRIORITY) + .setDscp(120) // DSCP should be <= 63 + .build()); + } + + /** + * Tests that the Builder throws an exception if a null Mac Address is set. + */ + @Test + public void testBuilderWithNullMacAddress() { + assertThrows(NullPointerException.class, () -> + new QosPolicyParams.Builder(TEST_POLICY_ID, QosPolicyParams.DIRECTION_DOWNLINK) + .setUserPriority(TEST_USER_PRIORITY) + .setSourceAddress(null) + .build()); + } + + /** + * Tests that the Builder throws an exception if a direction-specific error is found. + */ + @Test + public void testBuilderWithDirectionSpecificError() { + assertThrows(IllegalArgumentException.class, () -> + // Policies for downlink are required to have a User Priority. + new QosPolicyParams.Builder(TEST_POLICY_ID, QosPolicyParams.DIRECTION_DOWNLINK) + .build()); + } + + /** + * Tests that the parceling logic can properly read and write from a Parcel. + */ + @Test + public void testParcelReadWrite() { + QosPolicyParams params = createTestQosPolicyParams(); + Parcel parcel = Parcel.obtain(); + params.writeToParcel(parcel, 0); + parcel.setDataPosition(0); // Rewind data position back to the beginning for read. + QosPolicyParams unparceledParams = QosPolicyParams.CREATOR.createFromParcel(parcel); + verifyTestQosPolicyParams(unparceledParams); + } + + /** + * Tests that the overridden equality and hashCode operators properly compare two objects. + */ + @Test + public void testObjectComparison() { + QosPolicyParams params1 = createTestQosPolicyParams(); + QosPolicyParams params2 = createTestQosPolicyParams(); + assertTrue(params1.equals(params2)); + assertEquals(params1.hashCode(), params2.hashCode()); + } +} diff --git a/framework/tests/src/android/net/wifi/WifiManagerTest.java b/framework/tests/src/android/net/wifi/WifiManagerTest.java index 1a010c1caa..285d68cfe1 100644 --- a/framework/tests/src/android/net/wifi/WifiManagerTest.java +++ b/framework/tests/src/android/net/wifi/WifiManagerTest.java @@ -3943,4 +3943,38 @@ public class WifiManagerTest { verify(mWifiService).getChannelData(any(IListListener.Stub.class), eq(TEST_PACKAGE_NAME), any(Bundle.class)); } + + /** + * Verify call to {@link WifiManager#addQosPolicy(QosPolicyParams)}. + */ + @Test + public void testAddQosPolicy() throws Exception { + assumeTrue(SdkLevel.isAtLeastU()); + + final int policyId = 2; + final int direction = QosPolicyParams.DIRECTION_DOWNLINK; + final int userPriority = QosPolicyParams.USER_PRIORITY_VIDEO_LOW; + QosPolicyParams policyParams = new QosPolicyParams.Builder(policyId, direction) + .setUserPriority(userPriority) + .build(); + ArgumentCaptor<QosPolicyParams> paramsCaptor = + ArgumentCaptor.forClass(QosPolicyParams.class); + + mWifiManager.addQosPolicy(policyParams); + verify(mWifiService).addQosPolicy(paramsCaptor.capture(), any()); + assertEquals(policyId, paramsCaptor.getValue().getPolicyId()); + assertEquals(direction, paramsCaptor.getValue().getDirection()); + assertEquals(userPriority, paramsCaptor.getValue().getUserPriority()); + } + + /** + * Verify call to {@link WifiManager#removeQosPolicy(int)} + */ + @Test + public void testRemoveQosPolicy() throws Exception { + assumeTrue(SdkLevel.isAtLeastU()); + final int policyId = 127; + mWifiManager.removeQosPolicy(policyId); + verify(mWifiService).removeQosPolicy(eq(policyId)); + } } diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java index 0c15dc9e62..46559834aa 100644 --- a/service/java/com/android/server/wifi/WifiServiceImpl.java +++ b/service/java/com/android/server/wifi/WifiServiceImpl.java @@ -113,6 +113,7 @@ import android.net.wifi.ITrafficStateCallback; import android.net.wifi.IWifiConnectedNetworkScorer; import android.net.wifi.IWifiNetworkSelectionConfigListener; import android.net.wifi.IWifiVerboseLoggingStatusChangedListener; +import android.net.wifi.QosPolicyParams; import android.net.wifi.ScanResult; import android.net.wifi.SoftApCapability; import android.net.wifi.SoftApConfiguration; @@ -7147,4 +7148,36 @@ public class WifiServiceImpl extends BaseWifiService { return mContext.getResources() .getInteger(R.integer.config_wifiNetworkSpecifierMaxPreferredChannels); } + + /** + * See {@link WifiManager#addQosPolicy(QosPolicyParams)}. + */ + @Override + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void addQosPolicy(@NonNull QosPolicyParams policyParams, @NonNull IBinder binder) { + if (!SdkLevel.isAtLeastU()) { + throw new UnsupportedOperationException("SDK level too old"); + } + int uid = Binder.getCallingUid(); + if (!mWifiPermissionsUtil.checkNetworkSettingsPermission(uid) + && !mWifiPermissionsUtil.checkManageWifiNetworkSelectionPermission(uid)) { + throw new SecurityException("Uid=" + uid + " is not allowed to add QoS policies"); + } + } + + /** + * See {@link WifiManager#removeQosPolicy(int)}. + */ + @Override + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void removeQosPolicy(int policyId) { + if (!SdkLevel.isAtLeastU()) { + throw new UnsupportedOperationException("SDK level too old"); + } + int uid = Binder.getCallingUid(); + if (!mWifiPermissionsUtil.checkNetworkSettingsPermission(uid) + && !mWifiPermissionsUtil.checkManageWifiNetworkSelectionPermission(uid)) { + throw new SecurityException("Uid=" + uid + " is not allowed to remove QoS policies"); + } + } } |