Add SDK 29 sources.
Test: N/A
Change-Id: Iedb7a31029e003928eb16f7e69ed147e72bb6235
diff --git a/android/net/CaptivePortal.java b/android/net/CaptivePortal.java
new file mode 100644
index 0000000..a66fcae
--- /dev/null
+++ b/android/net/CaptivePortal.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed urnder 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;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+/**
+ * A class allowing apps handling the {@link ConnectivityManager#ACTION_CAPTIVE_PORTAL_SIGN_IN}
+ * activity to indicate to the system different outcomes of captive portal sign in. This class is
+ * passed as an extra named {@link ConnectivityManager#EXTRA_CAPTIVE_PORTAL} with the
+ * {@code ACTION_CAPTIVE_PORTAL_SIGN_IN} activity.
+ */
+public class CaptivePortal implements Parcelable {
+ /**
+ * Response code from the captive portal application, indicating that the portal was dismissed
+ * and the network should be re-validated.
+ * @see ICaptivePortal#appResponse(int)
+ * @see android.net.INetworkMonitor#notifyCaptivePortalAppFinished(int)
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final int APP_RETURN_DISMISSED = 0;
+ /**
+ * Response code from the captive portal application, indicating that the user did not login and
+ * does not want to use the captive portal network.
+ * @see ICaptivePortal#appResponse(int)
+ * @see android.net.INetworkMonitor#notifyCaptivePortalAppFinished(int)
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final int APP_RETURN_UNWANTED = 1;
+ /**
+ * Response code from the captive portal application, indicating that the user does not wish to
+ * login but wants to use the captive portal network as-is.
+ * @see ICaptivePortal#appResponse(int)
+ * @see android.net.INetworkMonitor#notifyCaptivePortalAppFinished(int)
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final int APP_RETURN_WANTED_AS_IS = 2;
+
+ private final IBinder mBinder;
+
+ /** @hide */
+ public CaptivePortal(@NonNull IBinder binder) {
+ mBinder = binder;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeStrongBinder(mBinder);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<CaptivePortal> CREATOR
+ = new Parcelable.Creator<CaptivePortal>() {
+ @Override
+ public CaptivePortal createFromParcel(Parcel in) {
+ return new CaptivePortal(in.readStrongBinder());
+ }
+
+ @Override
+ public CaptivePortal[] newArray(int size) {
+ return new CaptivePortal[size];
+ }
+ };
+
+ /**
+ * Indicate to the system that the captive portal has been
+ * dismissed. In response the framework will re-evaluate the network's
+ * connectivity and might take further action thereafter.
+ */
+ public void reportCaptivePortalDismissed() {
+ try {
+ ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_DISMISSED);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Indicate to the system that the user does not want to pursue signing in to the
+ * captive portal and the system should continue to prefer other networks
+ * without captive portals for use as the default active data network. The
+ * system will not retest the network for a captive portal so as to avoid
+ * disturbing the user with further sign in to network notifications.
+ */
+ public void ignoreNetwork() {
+ try {
+ ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_UNWANTED);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Indicate to the system the user wants to use this network as is, even though
+ * the captive portal is still in place. The system will treat the network
+ * as if it did not have a captive portal when selecting the network to use
+ * as the default active data network. This may result in this network
+ * becoming the default active data network, which could disrupt network
+ * connectivity for apps because the captive portal is still in place.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public void useNetwork() {
+ try {
+ ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_WANTED_AS_IS);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Log a captive portal login event.
+ * @param eventId one of the CAPTIVE_PORTAL_LOGIN_* constants in metrics_constants.proto.
+ * @param packageName captive portal application package name.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public void logEvent(int eventId, @NonNull String packageName) {
+ try {
+ ICaptivePortal.Stub.asInterface(mBinder).logEvent(eventId, packageName);
+ } catch (RemoteException e) {
+ }
+ }
+}
diff --git a/android/net/ConnectionInfo.java b/android/net/ConnectionInfo.java
new file mode 100644
index 0000000..4514a84
--- /dev/null
+++ b/android/net/ConnectionInfo.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Describe a network connection including local and remote address/port of a connection and the
+ * transport protocol.
+ *
+ * @hide
+ */
+public final class ConnectionInfo implements Parcelable {
+ public final int protocol;
+ public final InetSocketAddress local;
+ public final InetSocketAddress remote;
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public ConnectionInfo(int protocol, InetSocketAddress local, InetSocketAddress remote) {
+ this.protocol = protocol;
+ this.local = local;
+ this.remote = remote;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(protocol);
+ out.writeByteArray(local.getAddress().getAddress());
+ out.writeInt(local.getPort());
+ out.writeByteArray(remote.getAddress().getAddress());
+ out.writeInt(remote.getPort());
+ }
+
+ public static final @android.annotation.NonNull Creator<ConnectionInfo> CREATOR = new Creator<ConnectionInfo>() {
+ public ConnectionInfo createFromParcel(Parcel in) {
+ int protocol = in.readInt();
+ InetAddress localAddress;
+ try {
+ localAddress = InetAddress.getByAddress(in.createByteArray());
+ } catch (UnknownHostException e) {
+ throw new IllegalArgumentException("Invalid InetAddress");
+ }
+ int localPort = in.readInt();
+ InetAddress remoteAddress;
+ try {
+ remoteAddress = InetAddress.getByAddress(in.createByteArray());
+ } catch (UnknownHostException e) {
+ throw new IllegalArgumentException("Invalid InetAddress");
+ }
+ int remotePort = in.readInt();
+ InetSocketAddress local = new InetSocketAddress(localAddress, localPort);
+ InetSocketAddress remote = new InetSocketAddress(remoteAddress, remotePort);
+ return new ConnectionInfo(protocol, local, remote);
+ }
+
+ public ConnectionInfo[] newArray(int size) {
+ return new ConnectionInfo[size];
+ }
+ };
+}
diff --git a/android/net/ConnectivityManager.java b/android/net/ConnectivityManager.java
new file mode 100644
index 0000000..111a8c4
--- /dev/null
+++ b/android/net/ConnectivityManager.java
@@ -0,0 +1,4519 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import static android.net.IpSecManager.INVALID_RESOURCE_ID;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.SocketKeepalive.Callback;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.INetworkActivityListener;
+import android.os.INetworkManagementService;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
+import android.provider.Settings;
+import android.telephony.SubscriptionManager;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.Protocol;
+
+import libcore.net.event.NetworkEventDispatcher;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
+
+/**
+ * Class that answers queries about the state of network connectivity. It also
+ * notifies applications when network connectivity changes.
+ * <p>
+ * The primary responsibilities of this class are to:
+ * <ol>
+ * <li>Monitor network connections (Wi-Fi, GPRS, UMTS, etc.)</li>
+ * <li>Send broadcast intents when network connectivity changes</li>
+ * <li>Attempt to "fail over" to another network when connectivity to a network
+ * is lost</li>
+ * <li>Provide an API that allows applications to query the coarse-grained or fine-grained
+ * state of the available networks</li>
+ * <li>Provide an API that allows applications to request and select networks for their data
+ * traffic</li>
+ * </ol>
+ */
+@SystemService(Context.CONNECTIVITY_SERVICE)
+public class ConnectivityManager {
+ private static final String TAG = "ConnectivityManager";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ /**
+ * A change in network connectivity has occurred. A default connection has either
+ * been established or lost. The NetworkInfo for the affected network is
+ * sent as an extra; it should be consulted to see what kind of
+ * connectivity event occurred.
+ * <p/>
+ * Apps targeting Android 7.0 (API level 24) and higher do not receive this
+ * broadcast if they declare the broadcast receiver in their manifest. Apps
+ * will still receive broadcasts if they register their
+ * {@link android.content.BroadcastReceiver} with
+ * {@link android.content.Context#registerReceiver Context.registerReceiver()}
+ * and that context is still valid.
+ * <p/>
+ * If this is a connection that was the result of failing over from a
+ * disconnected network, then the FAILOVER_CONNECTION boolean extra is
+ * set to true.
+ * <p/>
+ * For a loss of connectivity, if the connectivity manager is attempting
+ * to connect (or has already connected) to another network, the
+ * NetworkInfo for the new network is also passed as an extra. This lets
+ * any receivers of the broadcast know that they should not necessarily
+ * tell the user that no data traffic will be possible. Instead, the
+ * receiver should expect another broadcast soon, indicating either that
+ * the failover attempt succeeded (and so there is still overall data
+ * connectivity), or that the failover attempt failed, meaning that all
+ * connectivity has been lost.
+ * <p/>
+ * For a disconnect event, the boolean extra EXTRA_NO_CONNECTIVITY
+ * is set to {@code true} if there are no connected networks at all.
+ *
+ * @deprecated apps should use the more versatile {@link #requestNetwork},
+ * {@link #registerNetworkCallback} or {@link #registerDefaultNetworkCallback}
+ * functions instead for faster and more detailed updates about the network
+ * changes they care about.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @Deprecated
+ public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
+
+ /**
+ * A temporary hack until SUPL system can get off the legacy APIS.
+ * They do too many network requests and the long list of apps listening
+ * and waking due to the CONNECTIVITY_ACTION broadcast makes it expensive.
+ * Use this broadcast intent instead for SUPL requests.
+ * @hide
+ */
+ public static final String CONNECTIVITY_ACTION_SUPL =
+ "android.net.conn.CONNECTIVITY_CHANGE_SUPL";
+
+ /**
+ * The device has connected to a network that has presented a captive
+ * portal, which is blocking Internet connectivity. The user was presented
+ * with a notification that network sign in is required,
+ * and the user invoked the notification's action indicating they
+ * desire to sign in to the network. Apps handling this activity should
+ * facilitate signing in to the network. This action includes a
+ * {@link Network} typed extra called {@link #EXTRA_NETWORK} that represents
+ * the network presenting the captive portal; all communication with the
+ * captive portal must be done using this {@code Network} object.
+ * <p/>
+ * This activity includes a {@link CaptivePortal} extra named
+ * {@link #EXTRA_CAPTIVE_PORTAL} that can be used to indicate different
+ * outcomes of the captive portal sign in to the system:
+ * <ul>
+ * <li> When the app handling this action believes the user has signed in to
+ * the network and the captive portal has been dismissed, the app should
+ * call {@link CaptivePortal#reportCaptivePortalDismissed} so the system can
+ * reevaluate the network. If reevaluation finds the network no longer
+ * subject to a captive portal, the network may become the default active
+ * data network.</li>
+ * <li> When the app handling this action believes the user explicitly wants
+ * to ignore the captive portal and the network, the app should call
+ * {@link CaptivePortal#ignoreNetwork}. </li>
+ * </ul>
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL";
+
+ /**
+ * The lookup key for a {@link NetworkInfo} object. Retrieve with
+ * {@link android.content.Intent#getParcelableExtra(String)}.
+ *
+ * @deprecated The {@link NetworkInfo} object is deprecated, as many of its properties
+ * can't accurately represent modern network characteristics.
+ * Please obtain information about networks from the {@link NetworkCapabilities}
+ * or {@link LinkProperties} objects instead.
+ */
+ @Deprecated
+ public static final String EXTRA_NETWORK_INFO = "networkInfo";
+
+ /**
+ * Network type which triggered a {@link #CONNECTIVITY_ACTION} broadcast.
+ *
+ * @see android.content.Intent#getIntExtra(String, int)
+ * @deprecated The network type is not rich enough to represent the characteristics
+ * of modern networks. Please use {@link NetworkCapabilities} instead,
+ * in particular the transports.
+ */
+ @Deprecated
+ public static final String EXTRA_NETWORK_TYPE = "networkType";
+
+ /**
+ * The lookup key for a boolean that indicates whether a connect event
+ * is for a network to which the connectivity manager was failing over
+ * following a disconnect on another network.
+ * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}.
+ *
+ * @deprecated See {@link NetworkInfo}.
+ */
+ @Deprecated
+ public static final String EXTRA_IS_FAILOVER = "isFailover";
+ /**
+ * The lookup key for a {@link NetworkInfo} object. This is supplied when
+ * there is another network that it may be possible to connect to. Retrieve with
+ * {@link android.content.Intent#getParcelableExtra(String)}.
+ *
+ * @deprecated See {@link NetworkInfo}.
+ */
+ @Deprecated
+ public static final String EXTRA_OTHER_NETWORK_INFO = "otherNetwork";
+ /**
+ * The lookup key for a boolean that indicates whether there is a
+ * complete lack of connectivity, i.e., no network is available.
+ * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}.
+ */
+ public static final String EXTRA_NO_CONNECTIVITY = "noConnectivity";
+ /**
+ * The lookup key for a string that indicates why an attempt to connect
+ * to a network failed. The string has no particular structure. It is
+ * intended to be used in notifications presented to users. Retrieve
+ * it with {@link android.content.Intent#getStringExtra(String)}.
+ */
+ public static final String EXTRA_REASON = "reason";
+ /**
+ * The lookup key for a string that provides optionally supplied
+ * extra information about the network state. The information
+ * may be passed up from the lower networking layers, and its
+ * meaning may be specific to a particular network type. Retrieve
+ * it with {@link android.content.Intent#getStringExtra(String)}.
+ *
+ * @deprecated See {@link NetworkInfo#getExtraInfo()}.
+ */
+ @Deprecated
+ public static final String EXTRA_EXTRA_INFO = "extraInfo";
+ /**
+ * The lookup key for an int that provides information about
+ * our connection to the internet at large. 0 indicates no connection,
+ * 100 indicates a great connection. Retrieve it with
+ * {@link android.content.Intent#getIntExtra(String, int)}.
+ * {@hide}
+ */
+ public static final String EXTRA_INET_CONDITION = "inetCondition";
+ /**
+ * The lookup key for a {@link CaptivePortal} object included with the
+ * {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} intent. The {@code CaptivePortal}
+ * object can be used to either indicate to the system that the captive
+ * portal has been dismissed or that the user does not want to pursue
+ * signing in to captive portal. Retrieve it with
+ * {@link android.content.Intent#getParcelableExtra(String)}.
+ */
+ public static final String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL";
+
+ /**
+ * Key for passing a URL to the captive portal login activity.
+ */
+ public static final String EXTRA_CAPTIVE_PORTAL_URL = "android.net.extra.CAPTIVE_PORTAL_URL";
+
+ /**
+ * Key for passing a {@link android.net.captiveportal.CaptivePortalProbeSpec} to the captive
+ * portal login activity.
+ * {@hide}
+ */
+ @SystemApi
+ @TestApi
+ public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC =
+ "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC";
+
+ /**
+ * Key for passing a user agent string to the captive portal login activity.
+ * {@hide}
+ */
+ @SystemApi
+ @TestApi
+ public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT =
+ "android.net.extra.CAPTIVE_PORTAL_USER_AGENT";
+
+ /**
+ * Broadcast action to indicate the change of data activity status
+ * (idle or active) on a network in a recent period.
+ * The network becomes active when data transmission is started, or
+ * idle if there is no data transmission for a period of time.
+ * {@hide}
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DATA_ACTIVITY_CHANGE =
+ "android.net.conn.DATA_ACTIVITY_CHANGE";
+ /**
+ * The lookup key for an enum that indicates the network device type on which this data activity
+ * change happens.
+ * {@hide}
+ */
+ public static final String EXTRA_DEVICE_TYPE = "deviceType";
+ /**
+ * The lookup key for a boolean that indicates the device is active or not. {@code true} means
+ * it is actively sending or receiving data and {@code false} means it is idle.
+ * {@hide}
+ */
+ public static final String EXTRA_IS_ACTIVE = "isActive";
+ /**
+ * The lookup key for a long that contains the timestamp (nanos) of the radio state change.
+ * {@hide}
+ */
+ public static final String EXTRA_REALTIME_NS = "tsNanos";
+
+ /**
+ * Broadcast Action: The setting for background data usage has changed
+ * values. Use {@link #getBackgroundDataSetting()} to get the current value.
+ * <p>
+ * If an application uses the network in the background, it should listen
+ * for this broadcast and stop using the background data if the value is
+ * {@code false}.
+ * <p>
+ *
+ * @deprecated As of {@link VERSION_CODES#ICE_CREAM_SANDWICH}, availability
+ * of background data depends on several combined factors, and
+ * this broadcast is no longer sent. Instead, when background
+ * data is unavailable, {@link #getActiveNetworkInfo()} will now
+ * appear disconnected. During first boot after a platform
+ * upgrade, this broadcast will be sent once if
+ * {@link #getBackgroundDataSetting()} was {@code false} before
+ * the upgrade.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @Deprecated
+ public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED =
+ "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
+
+ /**
+ * Broadcast Action: The network connection may not be good
+ * uses {@code ConnectivityManager.EXTRA_INET_CONDITION} and
+ * {@code ConnectivityManager.EXTRA_NETWORK_INFO} to specify
+ * the network and it's condition.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @UnsupportedAppUsage
+ public static final String INET_CONDITION_ACTION =
+ "android.net.conn.INET_CONDITION_ACTION";
+
+ /**
+ * Broadcast Action: A tetherable connection has come or gone.
+ * Uses {@code ConnectivityManager.EXTRA_AVAILABLE_TETHER},
+ * {@code ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY},
+ * {@code ConnectivityManager.EXTRA_ACTIVE_TETHER}, and
+ * {@code ConnectivityManager.EXTRA_ERRORED_TETHER} to indicate
+ * the current state of tethering. Each include a list of
+ * interface names in that state (may be empty).
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @UnsupportedAppUsage
+ public static final String ACTION_TETHER_STATE_CHANGED =
+ "android.net.conn.TETHER_STATE_CHANGED";
+
+ /**
+ * @hide
+ * gives a String[] listing all the interfaces configured for
+ * tethering and currently available for tethering.
+ */
+ @UnsupportedAppUsage
+ public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
+
+ /**
+ * @hide
+ * gives a String[] listing all the interfaces currently in local-only
+ * mode (ie, has DHCPv4+IPv6-ULA support and no packet forwarding)
+ */
+ public static final String EXTRA_ACTIVE_LOCAL_ONLY = "localOnlyArray";
+
+ /**
+ * @hide
+ * gives a String[] listing all the interfaces currently tethered
+ * (ie, has DHCPv4 support and packets potentially forwarded/NATed)
+ */
+ @UnsupportedAppUsage
+ public static final String EXTRA_ACTIVE_TETHER = "tetherArray";
+
+ /**
+ * @hide
+ * gives a String[] listing all the interfaces we tried to tether and
+ * failed. Use {@link #getLastTetherError} to find the error code
+ * for any interfaces listed here.
+ */
+ @UnsupportedAppUsage
+ public static final String EXTRA_ERRORED_TETHER = "erroredArray";
+
+ /**
+ * Broadcast Action: The captive portal tracker has finished its test.
+ * Sent only while running Setup Wizard, in lieu of showing a user
+ * notification.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_CAPTIVE_PORTAL_TEST_COMPLETED =
+ "android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED";
+ /**
+ * The lookup key for a boolean that indicates whether a captive portal was detected.
+ * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}.
+ * @hide
+ */
+ public static final String EXTRA_IS_CAPTIVE_PORTAL = "captivePortal";
+
+ /**
+ * Action used to display a dialog that asks the user whether to connect to a network that is
+ * not validated. This intent is used to start the dialog in settings via startActivity.
+ *
+ * @hide
+ */
+ public static final String ACTION_PROMPT_UNVALIDATED = "android.net.conn.PROMPT_UNVALIDATED";
+
+ /**
+ * Action used to display a dialog that asks the user whether to avoid a network that is no
+ * longer validated. This intent is used to start the dialog in settings via startActivity.
+ *
+ * @hide
+ */
+ public static final String ACTION_PROMPT_LOST_VALIDATION =
+ "android.net.conn.PROMPT_LOST_VALIDATION";
+
+ /**
+ * Action used to display a dialog that asks the user whether to stay connected to a network
+ * that has not validated. This intent is used to start the dialog in settings via
+ * startActivity.
+ *
+ * @hide
+ */
+ public static final String ACTION_PROMPT_PARTIAL_CONNECTIVITY =
+ "android.net.conn.PROMPT_PARTIAL_CONNECTIVITY";
+
+ /**
+ * Invalid tethering type.
+ * @see #startTethering(int, boolean, OnStartTetheringCallback)
+ * @hide
+ */
+ public static final int TETHERING_INVALID = -1;
+
+ /**
+ * Wifi tethering type.
+ * @see #startTethering(int, boolean, OnStartTetheringCallback)
+ * @hide
+ */
+ @SystemApi
+ public static final int TETHERING_WIFI = 0;
+
+ /**
+ * USB tethering type.
+ * @see #startTethering(int, boolean, OnStartTetheringCallback)
+ * @hide
+ */
+ @SystemApi
+ public static final int TETHERING_USB = 1;
+
+ /**
+ * Bluetooth tethering type.
+ * @see #startTethering(int, boolean, OnStartTetheringCallback)
+ * @hide
+ */
+ @SystemApi
+ public static final int TETHERING_BLUETOOTH = 2;
+
+ /**
+ * Extra used for communicating with the TetherService. Includes the type of tethering to
+ * enable if any.
+ * @hide
+ */
+ public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
+
+ /**
+ * Extra used for communicating with the TetherService. Includes the type of tethering for
+ * which to cancel provisioning.
+ * @hide
+ */
+ public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
+
+ /**
+ * Extra used for communicating with the TetherService. True to schedule a recheck of tether
+ * provisioning.
+ * @hide
+ */
+ public static final String EXTRA_SET_ALARM = "extraSetAlarm";
+
+ /**
+ * Tells the TetherService to run a provision check now.
+ * @hide
+ */
+ public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
+
+ /**
+ * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver}
+ * which will receive provisioning results. Can be left empty.
+ * @hide
+ */
+ public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback";
+
+ /**
+ * The absence of a connection type.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
+ public static final int TYPE_NONE = -1;
+
+ /**
+ * A Mobile data connection. Devices may support more than one.
+ *
+ * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an
+ * appropriate network. {@see NetworkCapabilities} for supported transports.
+ */
+ @Deprecated
+ public static final int TYPE_MOBILE = 0;
+
+ /**
+ * A WIFI data connection. Devices may support more than one.
+ *
+ * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an
+ * appropriate network. {@see NetworkCapabilities} for supported transports.
+ */
+ @Deprecated
+ public static final int TYPE_WIFI = 1;
+
+ /**
+ * An MMS-specific Mobile data connection. This network type may use the
+ * same network interface as {@link #TYPE_MOBILE} or it may use a different
+ * one. This is used by applications needing to talk to the carrier's
+ * Multimedia Messaging Service servers.
+ *
+ * @deprecated Applications should instead use {@link NetworkCapabilities#hasCapability} or
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that
+ * provides the {@link NetworkCapabilities#NET_CAPABILITY_MMS} capability.
+ */
+ @Deprecated
+ public static final int TYPE_MOBILE_MMS = 2;
+
+ /**
+ * A SUPL-specific Mobile data connection. This network type may use the
+ * same network interface as {@link #TYPE_MOBILE} or it may use a different
+ * one. This is used by applications needing to talk to the carrier's
+ * Secure User Plane Location servers for help locating the device.
+ *
+ * @deprecated Applications should instead use {@link NetworkCapabilities#hasCapability} or
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that
+ * provides the {@link NetworkCapabilities#NET_CAPABILITY_SUPL} capability.
+ */
+ @Deprecated
+ public static final int TYPE_MOBILE_SUPL = 3;
+
+ /**
+ * A DUN-specific Mobile data connection. This network type may use the
+ * same network interface as {@link #TYPE_MOBILE} or it may use a different
+ * one. This is sometimes by the system when setting up an upstream connection
+ * for tethering so that the carrier is aware of DUN traffic.
+ *
+ * @deprecated Applications should instead use {@link NetworkCapabilities#hasCapability} or
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that
+ * provides the {@link NetworkCapabilities#NET_CAPABILITY_DUN} capability.
+ */
+ @Deprecated
+ public static final int TYPE_MOBILE_DUN = 4;
+
+ /**
+ * A High Priority Mobile data connection. This network type uses the
+ * same network interface as {@link #TYPE_MOBILE} but the routing setup
+ * is different.
+ *
+ * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an
+ * appropriate network. {@see NetworkCapabilities} for supported transports.
+ */
+ @Deprecated
+ public static final int TYPE_MOBILE_HIPRI = 5;
+
+ /**
+ * A WiMAX data connection.
+ *
+ * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an
+ * appropriate network. {@see NetworkCapabilities} for supported transports.
+ */
+ @Deprecated
+ public static final int TYPE_WIMAX = 6;
+
+ /**
+ * A Bluetooth data connection.
+ *
+ * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an
+ * appropriate network. {@see NetworkCapabilities} for supported transports.
+ */
+ @Deprecated
+ public static final int TYPE_BLUETOOTH = 7;
+
+ /**
+ * Dummy data connection. This should not be used on shipping devices.
+ * @deprecated This is not used any more.
+ */
+ @Deprecated
+ public static final int TYPE_DUMMY = 8;
+
+ /**
+ * An Ethernet data connection.
+ *
+ * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an
+ * appropriate network. {@see NetworkCapabilities} for supported transports.
+ */
+ @Deprecated
+ public static final int TYPE_ETHERNET = 9;
+
+ /**
+ * Over the air Administration.
+ * @deprecated Use {@link NetworkCapabilities} instead.
+ * {@hide}
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
+ public static final int TYPE_MOBILE_FOTA = 10;
+
+ /**
+ * IP Multimedia Subsystem.
+ * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_IMS} instead.
+ * {@hide}
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public static final int TYPE_MOBILE_IMS = 11;
+
+ /**
+ * Carrier Branded Services.
+ * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_CBS} instead.
+ * {@hide}
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
+ public static final int TYPE_MOBILE_CBS = 12;
+
+ /**
+ * A Wi-Fi p2p connection. Only requesting processes will have access to
+ * the peers connected.
+ * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_WIFI_P2P} instead.
+ * {@hide}
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public static final int TYPE_WIFI_P2P = 13;
+
+ /**
+ * The network to use for initially attaching to the network
+ * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_IA} instead.
+ * {@hide}
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public static final int TYPE_MOBILE_IA = 14;
+
+ /**
+ * Emergency PDN connection for emergency services. This
+ * may include IMS and MMS in emergency situations.
+ * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_EIMS} instead.
+ * {@hide}
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
+ public static final int TYPE_MOBILE_EMERGENCY = 15;
+
+ /**
+ * The network that uses proxy to achieve connectivity.
+ * @deprecated Use {@link NetworkCapabilities} instead.
+ * {@hide}
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public static final int TYPE_PROXY = 16;
+
+ /**
+ * A virtual network using one or more native bearers.
+ * It may or may not be providing security services.
+ * @deprecated Applications should use {@link NetworkCapabilities#TRANSPORT_VPN} instead.
+ */
+ @Deprecated
+ public static final int TYPE_VPN = 17;
+
+ /**
+ * A network that is exclusively meant to be used for testing
+ *
+ * @deprecated Use {@link NetworkCapabilities} instead.
+ * @hide
+ */
+ @Deprecated
+ public static final int TYPE_TEST = 18; // TODO: Remove this once NetworkTypes are unused.
+
+ /** {@hide} */
+ public static final int MAX_RADIO_TYPE = TYPE_TEST;
+
+ /** {@hide} */
+ public static final int MAX_NETWORK_TYPE = TYPE_TEST;
+
+ private static final int MIN_NETWORK_TYPE = TYPE_MOBILE;
+
+ /**
+ * If you want to set the default network preference,you can directly
+ * change the networkAttributes array in framework's config.xml.
+ *
+ * @deprecated Since we support so many more networks now, the single
+ * network default network preference can't really express
+ * the hierarchy. Instead, the default is defined by the
+ * networkAttributes in config.xml. You can determine
+ * the current value by calling {@link #getNetworkPreference()}
+ * from an App.
+ */
+ @Deprecated
+ public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI;
+
+ /**
+ * @hide
+ */
+ public static final int REQUEST_ID_UNSET = 0;
+
+ /**
+ * Static unique request used as a tombstone for NetworkCallbacks that have been unregistered.
+ * This allows to distinguish when unregistering NetworkCallbacks those that were never
+ * registered from those that were already unregistered.
+ * @hide
+ */
+ private static final NetworkRequest ALREADY_UNREGISTERED =
+ new NetworkRequest.Builder().clearCapabilities().build();
+
+ /**
+ * A NetID indicating no Network is selected.
+ * Keep in sync with bionic/libc/dns/include/resolv_netid.h
+ * @hide
+ */
+ public static final int NETID_UNSET = 0;
+
+ /**
+ * Private DNS Mode values.
+ *
+ * The "private_dns_mode" global setting stores a String value which is
+ * expected to be one of the following.
+ */
+
+ /**
+ * @hide
+ */
+ public static final String PRIVATE_DNS_MODE_OFF = "off";
+ /**
+ * @hide
+ */
+ public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic";
+ /**
+ * @hide
+ */
+ public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname";
+ /**
+ * The default Private DNS mode.
+ *
+ * This may change from release to release or may become dependent upon
+ * the capabilities of the underlying platform.
+ *
+ * @hide
+ */
+ public static final String PRIVATE_DNS_DEFAULT_MODE_FALLBACK = PRIVATE_DNS_MODE_OPPORTUNISTIC;
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
+ private final IConnectivityManager mService;
+ /**
+ * A kludge to facilitate static access where a Context pointer isn't available, like in the
+ * case of the static set/getProcessDefaultNetwork methods and from the Network class.
+ * TODO: Remove this after deprecating the static methods in favor of non-static methods or
+ * methods that take a Context argument.
+ */
+ private static ConnectivityManager sInstance;
+
+ private final Context mContext;
+
+ private INetworkManagementService mNMService;
+ private INetworkPolicyManager mNPManager;
+
+ /**
+ * Tests if a given integer represents a valid network type.
+ * @param networkType the type to be tested
+ * @return a boolean. {@code true} if the type is valid, else {@code false}
+ * @deprecated All APIs accepting a network type are deprecated. There should be no need to
+ * validate a network type.
+ */
+ @Deprecated
+ public static boolean isNetworkTypeValid(int networkType) {
+ return MIN_NETWORK_TYPE <= networkType && networkType <= MAX_NETWORK_TYPE;
+ }
+
+ /**
+ * Returns a non-localized string representing a given network type.
+ * ONLY used for debugging output.
+ * @param type the type needing naming
+ * @return a String for the given type, or a string version of the type ("87")
+ * if no name is known.
+ * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead.
+ * {@hide}
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public static String getNetworkTypeName(int type) {
+ switch (type) {
+ case TYPE_NONE:
+ return "NONE";
+ case TYPE_MOBILE:
+ return "MOBILE";
+ case TYPE_WIFI:
+ return "WIFI";
+ case TYPE_MOBILE_MMS:
+ return "MOBILE_MMS";
+ case TYPE_MOBILE_SUPL:
+ return "MOBILE_SUPL";
+ case TYPE_MOBILE_DUN:
+ return "MOBILE_DUN";
+ case TYPE_MOBILE_HIPRI:
+ return "MOBILE_HIPRI";
+ case TYPE_WIMAX:
+ return "WIMAX";
+ case TYPE_BLUETOOTH:
+ return "BLUETOOTH";
+ case TYPE_DUMMY:
+ return "DUMMY";
+ case TYPE_ETHERNET:
+ return "ETHERNET";
+ case TYPE_MOBILE_FOTA:
+ return "MOBILE_FOTA";
+ case TYPE_MOBILE_IMS:
+ return "MOBILE_IMS";
+ case TYPE_MOBILE_CBS:
+ return "MOBILE_CBS";
+ case TYPE_WIFI_P2P:
+ return "WIFI_P2P";
+ case TYPE_MOBILE_IA:
+ return "MOBILE_IA";
+ case TYPE_MOBILE_EMERGENCY:
+ return "MOBILE_EMERGENCY";
+ case TYPE_PROXY:
+ return "PROXY";
+ case TYPE_VPN:
+ return "VPN";
+ default:
+ return Integer.toString(type);
+ }
+ }
+
+ /**
+ * Checks if a given type uses the cellular data connection.
+ * This should be replaced in the future by a network property.
+ * @param networkType the type to check
+ * @return a boolean - {@code true} if uses cellular network, else {@code false}
+ * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead.
+ * {@hide}
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
+ public static boolean isNetworkTypeMobile(int networkType) {
+ switch (networkType) {
+ case TYPE_MOBILE:
+ case TYPE_MOBILE_MMS:
+ case TYPE_MOBILE_SUPL:
+ case TYPE_MOBILE_DUN:
+ case TYPE_MOBILE_HIPRI:
+ case TYPE_MOBILE_FOTA:
+ case TYPE_MOBILE_IMS:
+ case TYPE_MOBILE_CBS:
+ case TYPE_MOBILE_IA:
+ case TYPE_MOBILE_EMERGENCY:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Checks if the given network type is backed by a Wi-Fi radio.
+ *
+ * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead.
+ * @hide
+ */
+ @Deprecated
+ public static boolean isNetworkTypeWifi(int networkType) {
+ switch (networkType) {
+ case TYPE_WIFI:
+ case TYPE_WIFI_P2P:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Specifies the preferred network type. When the device has more
+ * than one type available the preferred network type will be used.
+ *
+ * @param preference the network type to prefer over all others. It is
+ * unspecified what happens to the old preferred network in the
+ * overall ordering.
+ * @deprecated Functionality has been removed as it no longer makes sense,
+ * with many more than two networks - we'd need an array to express
+ * preference. Instead we use dynamic network properties of
+ * the networks to describe their precedence.
+ */
+ @Deprecated
+ public void setNetworkPreference(int preference) {
+ }
+
+ /**
+ * Retrieves the current preferred network type.
+ *
+ * @return an integer representing the preferred network type
+ *
+ * @deprecated Functionality has been removed as it no longer makes sense,
+ * with many more than two networks - we'd need an array to express
+ * preference. Instead we use dynamic network properties of
+ * the networks to describe their precedence.
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ public int getNetworkPreference() {
+ return TYPE_NONE;
+ }
+
+ /**
+ * Returns details about the currently active default data network. When
+ * connected, this network is the default route for outgoing connections.
+ * You should always check {@link NetworkInfo#isConnected()} before initiating
+ * network traffic. This may return {@code null} when there is no default
+ * network.
+ * Note that if the default network is a VPN, this method will return the
+ * NetworkInfo for one of its underlying networks instead, or null if the
+ * VPN agent did not specify any. Apps interested in learning about VPNs
+ * should use {@link #getNetworkInfo(android.net.Network)} instead.
+ *
+ * @return a {@link NetworkInfo} object for the current default network
+ * or {@code null} if no default network is currently active
+ * @deprecated See {@link NetworkInfo}.
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @Nullable
+ public NetworkInfo getActiveNetworkInfo() {
+ try {
+ return mService.getActiveNetworkInfo();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns a {@link Network} object corresponding to the currently active
+ * default data network. In the event that the current active default data
+ * network disconnects, the returned {@code Network} object will no longer
+ * be usable. This will return {@code null} when there is no default
+ * network.
+ *
+ * @return a {@link Network} object for the current default network or
+ * {@code null} if no default network is currently active
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @Nullable
+ public Network getActiveNetwork() {
+ try {
+ return mService.getActiveNetwork();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns a {@link Network} object corresponding to the currently active
+ * default data network for a specific UID. In the event that the default data
+ * network disconnects, the returned {@code Network} object will no longer
+ * be usable. This will return {@code null} when there is no default
+ * network for the UID.
+ *
+ * @return a {@link Network} object for the current default network for the
+ * given UID or {@code null} if no default network is currently active
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL)
+ @Nullable
+ public Network getActiveNetworkForUid(int uid) {
+ return getActiveNetworkForUid(uid, false);
+ }
+
+ /** {@hide} */
+ public Network getActiveNetworkForUid(int uid, boolean ignoreBlocked) {
+ try {
+ return mService.getActiveNetworkForUid(uid, ignoreBlocked);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Checks if a VPN app supports always-on mode.
+ *
+ * In order to support the always-on feature, an app has to
+ * <ul>
+ * <li>target {@link VERSION_CODES#N API 24} or above, and
+ * <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
+ * meta-data field.
+ * </ul>
+ *
+ * @param userId The identifier of the user for whom the VPN app is installed.
+ * @param vpnPackage The canonical package name of the VPN app.
+ * @return {@code true} if and only if the VPN app exists and supports always-on mode.
+ * @hide
+ */
+ public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) {
+ try {
+ return mService.isAlwaysOnVpnPackageSupported(userId, vpnPackage);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Configures an always-on VPN connection through a specific application.
+ * This connection is automatically granted and persisted after a reboot.
+ *
+ * <p>The designated package should declare a {@link VpnService} in its
+ * manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE},
+ * otherwise the call will fail.
+ *
+ * @param userId The identifier of the user to set an always-on VPN for.
+ * @param vpnPackage The package name for an installed VPN app on the device, or {@code null}
+ * to remove an existing always-on VPN configuration.
+ * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or
+ * {@code false} otherwise.
+ * @param lockdownWhitelist The list of packages that are allowed to access network directly
+ * when VPN is in lockdown mode but is not running. Non-existent packages are ignored so
+ * this method must be called when a package that should be whitelisted is installed or
+ * uninstalled.
+ * @return {@code true} if the package is set as always-on VPN controller;
+ * {@code false} otherwise.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+ public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage,
+ boolean lockdownEnabled, @Nullable List<String> lockdownWhitelist) {
+ try {
+ return mService.setAlwaysOnVpnPackage(
+ userId, vpnPackage, lockdownEnabled, lockdownWhitelist);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the package name of the currently set always-on VPN application.
+ * If there is no always-on VPN set, or the VPN is provided by the system instead
+ * of by an app, {@code null} will be returned.
+ *
+ * @return Package name of VPN controller responsible for always-on VPN,
+ * or {@code null} if none is set.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+ public String getAlwaysOnVpnPackageForUser(int userId) {
+ try {
+ return mService.getAlwaysOnVpnPackage(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @return whether always-on VPN is in lockdown mode.
+ *
+ * @hide
+ **/
+ @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+ public boolean isVpnLockdownEnabled(int userId) {
+ try {
+ return mService.isVpnLockdownEnabled(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ }
+
+ /**
+ * @return the list of packages that are allowed to access network when always-on VPN is in
+ * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active.
+ *
+ * @hide
+ **/
+ @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+ public List<String> getVpnLockdownWhitelist(int userId) {
+ try {
+ return mService.getVpnLockdownWhitelist(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns details about the currently active default data network
+ * for a given uid. This is for internal use only to avoid spying
+ * other apps.
+ *
+ * @return a {@link NetworkInfo} object for the current default network
+ * for the given uid or {@code null} if no default network is
+ * available for the specified uid.
+ *
+ * {@hide}
+ */
+ @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL)
+ @UnsupportedAppUsage
+ public NetworkInfo getActiveNetworkInfoForUid(int uid) {
+ return getActiveNetworkInfoForUid(uid, false);
+ }
+
+ /** {@hide} */
+ public NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked) {
+ try {
+ return mService.getActiveNetworkInfoForUid(uid, ignoreBlocked);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns connection status information about a particular
+ * network type.
+ *
+ * @param networkType integer specifying which networkType in
+ * which you're interested.
+ * @return a {@link NetworkInfo} object for the requested
+ * network type or {@code null} if the type is not
+ * supported by the device. If {@code networkType} is
+ * TYPE_VPN and a VPN is active for the calling app,
+ * then this method will try to return one of the
+ * underlying networks for the VPN or null if the
+ * VPN agent didn't specify any.
+ *
+ * @deprecated This method does not support multiple connected networks
+ * of the same type. Use {@link #getAllNetworks} and
+ * {@link #getNetworkInfo(android.net.Network)} instead.
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @Nullable
+ public NetworkInfo getNetworkInfo(int networkType) {
+ try {
+ return mService.getNetworkInfo(networkType);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns connection status information about a particular
+ * Network.
+ *
+ * @param network {@link Network} specifying which network
+ * in which you're interested.
+ * @return a {@link NetworkInfo} object for the requested
+ * network or {@code null} if the {@code Network}
+ * is not valid.
+ * @deprecated See {@link NetworkInfo}.
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @Nullable
+ public NetworkInfo getNetworkInfo(@Nullable Network network) {
+ return getNetworkInfoForUid(network, Process.myUid(), false);
+ }
+
+ /** {@hide} */
+ public NetworkInfo getNetworkInfoForUid(Network network, int uid, boolean ignoreBlocked) {
+ try {
+ return mService.getNetworkInfoForUid(network, uid, ignoreBlocked);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns connection status information about all network
+ * types supported by the device.
+ *
+ * @return an array of {@link NetworkInfo} objects. Check each
+ * {@link NetworkInfo#getType} for which type each applies.
+ *
+ * @deprecated This method does not support multiple connected networks
+ * of the same type. Use {@link #getAllNetworks} and
+ * {@link #getNetworkInfo(android.net.Network)} instead.
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @NonNull
+ public NetworkInfo[] getAllNetworkInfo() {
+ try {
+ return mService.getAllNetworkInfo();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the {@link Network} object currently serving a given type, or
+ * null if the given type is not connected.
+ *
+ * @hide
+ * @deprecated This method does not support multiple connected networks
+ * of the same type. Use {@link #getAllNetworks} and
+ * {@link #getNetworkInfo(android.net.Network)} instead.
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @UnsupportedAppUsage
+ public Network getNetworkForType(int networkType) {
+ try {
+ return mService.getNetworkForType(networkType);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns an array of all {@link Network} currently tracked by the
+ * framework.
+ *
+ * @return an array of {@link Network} objects.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @NonNull
+ public Network[] getAllNetworks() {
+ try {
+ return mService.getAllNetworks();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns an array of {@link android.net.NetworkCapabilities} objects, representing
+ * the Networks that applications run by the given user will use by default.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId) {
+ try {
+ return mService.getDefaultNetworkCapabilitiesForUser(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the IP information for the current default network.
+ *
+ * @return a {@link LinkProperties} object describing the IP info
+ * for the current default network, or {@code null} if there
+ * is no current default network.
+ *
+ * {@hide}
+ * @deprecated please use {@link #getLinkProperties(Network)} on the return
+ * value of {@link #getActiveNetwork()} instead. In particular,
+ * this method will return non-null LinkProperties even if the
+ * app is blocked by policy from using this network.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 109783091)
+ public LinkProperties getActiveLinkProperties() {
+ try {
+ return mService.getActiveLinkProperties();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the IP information for a given network type.
+ *
+ * @param networkType the network type of interest.
+ * @return a {@link LinkProperties} object describing the IP info
+ * for the given networkType, or {@code null} if there is
+ * no current default network.
+ *
+ * {@hide}
+ * @deprecated This method does not support multiple connected networks
+ * of the same type. Use {@link #getAllNetworks},
+ * {@link #getNetworkInfo(android.net.Network)}, and
+ * {@link #getLinkProperties(android.net.Network)} instead.
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
+ public LinkProperties getLinkProperties(int networkType) {
+ try {
+ return mService.getLinkPropertiesForType(networkType);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the {@link LinkProperties} for the given {@link Network}. This
+ * will return {@code null} if the network is unknown.
+ *
+ * @param network The {@link Network} object identifying the network in question.
+ * @return The {@link LinkProperties} for the network, or {@code null}.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @Nullable
+ public LinkProperties getLinkProperties(@Nullable Network network) {
+ try {
+ return mService.getLinkProperties(network);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the {@link android.net.NetworkCapabilities} for the given {@link Network}. This
+ * will return {@code null} if the network is unknown.
+ *
+ * @param network The {@link Network} object identifying the network in question.
+ * @return The {@link android.net.NetworkCapabilities} for the network, or {@code null}.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @Nullable
+ public NetworkCapabilities getNetworkCapabilities(@Nullable Network network) {
+ try {
+ return mService.getNetworkCapabilities(network);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets a URL that can be used for resolving whether a captive portal is present.
+ * 1. This URL should respond with a 204 response to a GET request to indicate no captive
+ * portal is present.
+ * 2. This URL must be HTTP as redirect responses are used to find captive portal
+ * sign-in pages. Captive portals cannot respond to HTTPS requests with redirects.
+ *
+ * The system network validation may be using different strategies to detect captive portals,
+ * so this method does not necessarily return a URL used by the system. It only returns a URL
+ * that may be relevant for other components trying to detect captive portals.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.LOCAL_MAC_ADDRESS)
+ public String getCaptivePortalServerUrl() {
+ try {
+ return mService.getCaptivePortalServerUrl();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Tells the underlying networking system that the caller wants to
+ * begin using the named feature. The interpretation of {@code feature}
+ * is completely up to each networking implementation.
+ *
+ * <p>This method requires the caller to hold either the
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+ * or the ability to modify system settings as determined by
+ * {@link android.provider.Settings.System#canWrite}.</p>
+ *
+ * @param networkType specifies which network the request pertains to
+ * @param feature the name of the feature to be used
+ * @return an integer value representing the outcome of the request.
+ * The interpretation of this value is specific to each networking
+ * implementation+feature combination, except that the value {@code -1}
+ * always indicates failure.
+ *
+ * @deprecated Deprecated in favor of the cleaner
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)} API.
+ * In {@link VERSION_CODES#M}, and above, this method is unsupported and will
+ * throw {@code UnsupportedOperationException} if called.
+ * @removed
+ */
+ @Deprecated
+ public int startUsingNetworkFeature(int networkType, String feature) {
+ checkLegacyRoutingApiAccess();
+ NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature);
+ if (netCap == null) {
+ Log.d(TAG, "Can't satisfy startUsingNetworkFeature for " + networkType + ", " +
+ feature);
+ return PhoneConstants.APN_REQUEST_FAILED;
+ }
+
+ NetworkRequest request = null;
+ synchronized (sLegacyRequests) {
+ LegacyRequest l = sLegacyRequests.get(netCap);
+ if (l != null) {
+ Log.d(TAG, "renewing startUsingNetworkFeature request " + l.networkRequest);
+ renewRequestLocked(l);
+ if (l.currentNetwork != null) {
+ return PhoneConstants.APN_ALREADY_ACTIVE;
+ } else {
+ return PhoneConstants.APN_REQUEST_STARTED;
+ }
+ }
+
+ request = requestNetworkForFeatureLocked(netCap);
+ }
+ if (request != null) {
+ Log.d(TAG, "starting startUsingNetworkFeature for request " + request);
+ return PhoneConstants.APN_REQUEST_STARTED;
+ } else {
+ Log.d(TAG, " request Failed");
+ return PhoneConstants.APN_REQUEST_FAILED;
+ }
+ }
+
+ /**
+ * Tells the underlying networking system that the caller is finished
+ * using the named feature. The interpretation of {@code feature}
+ * is completely up to each networking implementation.
+ *
+ * <p>This method requires the caller to hold either the
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+ * or the ability to modify system settings as determined by
+ * {@link android.provider.Settings.System#canWrite}.</p>
+ *
+ * @param networkType specifies which network the request pertains to
+ * @param feature the name of the feature that is no longer needed
+ * @return an integer value representing the outcome of the request.
+ * The interpretation of this value is specific to each networking
+ * implementation+feature combination, except that the value {@code -1}
+ * always indicates failure.
+ *
+ * @deprecated Deprecated in favor of the cleaner
+ * {@link #unregisterNetworkCallback(NetworkCallback)} API.
+ * In {@link VERSION_CODES#M}, and above, this method is unsupported and will
+ * throw {@code UnsupportedOperationException} if called.
+ * @removed
+ */
+ @Deprecated
+ public int stopUsingNetworkFeature(int networkType, String feature) {
+ checkLegacyRoutingApiAccess();
+ NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature);
+ if (netCap == null) {
+ Log.d(TAG, "Can't satisfy stopUsingNetworkFeature for " + networkType + ", " +
+ feature);
+ return -1;
+ }
+
+ if (removeRequestForFeature(netCap)) {
+ Log.d(TAG, "stopUsingNetworkFeature for " + networkType + ", " + feature);
+ }
+ return 1;
+ }
+
+ @UnsupportedAppUsage
+ private NetworkCapabilities networkCapabilitiesForFeature(int networkType, String feature) {
+ if (networkType == TYPE_MOBILE) {
+ switch (feature) {
+ case "enableCBS":
+ return networkCapabilitiesForType(TYPE_MOBILE_CBS);
+ case "enableDUN":
+ case "enableDUNAlways":
+ return networkCapabilitiesForType(TYPE_MOBILE_DUN);
+ case "enableFOTA":
+ return networkCapabilitiesForType(TYPE_MOBILE_FOTA);
+ case "enableHIPRI":
+ return networkCapabilitiesForType(TYPE_MOBILE_HIPRI);
+ case "enableIMS":
+ return networkCapabilitiesForType(TYPE_MOBILE_IMS);
+ case "enableMMS":
+ return networkCapabilitiesForType(TYPE_MOBILE_MMS);
+ case "enableSUPL":
+ return networkCapabilitiesForType(TYPE_MOBILE_SUPL);
+ default:
+ return null;
+ }
+ } else if (networkType == TYPE_WIFI && "p2p".equals(feature)) {
+ return networkCapabilitiesForType(TYPE_WIFI_P2P);
+ }
+ return null;
+ }
+
+ /**
+ * Guess what the network request was trying to say so that the resulting
+ * network is accessible via the legacy (deprecated) API such as
+ * requestRouteToHost.
+ *
+ * This means we should try to be fairly precise about transport and
+ * capability but ignore things such as networkSpecifier.
+ * If the request has more than one transport or capability it doesn't
+ * match the old legacy requests (they selected only single transport/capability)
+ * so this function cannot map the request to a single legacy type and
+ * the resulting network will not be available to the legacy APIs.
+ *
+ * This code is only called from the requestNetwork API (L and above).
+ *
+ * Setting a legacy type causes CONNECTIVITY_ACTION broadcasts, which are expensive
+ * because they wake up lots of apps - see http://b/23350688 . So we currently only
+ * do this for SUPL requests, which are the only ones that we know need it. If
+ * omitting these broadcasts causes unacceptable app breakage, then for backwards
+ * compatibility we can send them:
+ *
+ * if (targetSdkVersion < Build.VERSION_CODES.M) && // legacy API unsupported >= M
+ * targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP)) // requestNetwork not present < L
+ *
+ * TODO - This should be removed when the legacy APIs are removed.
+ */
+ private int inferLegacyTypeForNetworkCapabilities(NetworkCapabilities netCap) {
+ if (netCap == null) {
+ return TYPE_NONE;
+ }
+
+ if (!netCap.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+ return TYPE_NONE;
+ }
+
+ // Do this only for SUPL, until GnssLocationProvider is fixed. http://b/25876485 .
+ if (!netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
+ // NOTE: if this causes app breakage, we should not just comment out this early return;
+ // instead, we should make this early return conditional on the requesting app's target
+ // SDK version, as described in the comment above.
+ return TYPE_NONE;
+ }
+
+ String type = null;
+ int result = TYPE_NONE;
+
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
+ type = "enableCBS";
+ result = TYPE_MOBILE_CBS;
+ } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
+ type = "enableIMS";
+ result = TYPE_MOBILE_IMS;
+ } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) {
+ type = "enableFOTA";
+ result = TYPE_MOBILE_FOTA;
+ } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) {
+ type = "enableDUN";
+ result = TYPE_MOBILE_DUN;
+ } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
+ type = "enableSUPL";
+ result = TYPE_MOBILE_SUPL;
+ // back out this hack for mms as they no longer need this and it's causing
+ // device slowdowns - b/23350688 (note, supl still needs this)
+ //} else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
+ // type = "enableMMS";
+ // result = TYPE_MOBILE_MMS;
+ } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+ type = "enableHIPRI";
+ result = TYPE_MOBILE_HIPRI;
+ }
+ if (type != null) {
+ NetworkCapabilities testCap = networkCapabilitiesForFeature(TYPE_MOBILE, type);
+ if (testCap.equalsNetCapabilities(netCap) && testCap.equalsTransportTypes(netCap)) {
+ return result;
+ }
+ }
+ return TYPE_NONE;
+ }
+
+ private int legacyTypeForNetworkCapabilities(NetworkCapabilities netCap) {
+ if (netCap == null) return TYPE_NONE;
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
+ return TYPE_MOBILE_CBS;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
+ return TYPE_MOBILE_IMS;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) {
+ return TYPE_MOBILE_FOTA;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) {
+ return TYPE_MOBILE_DUN;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
+ return TYPE_MOBILE_SUPL;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
+ return TYPE_MOBILE_MMS;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+ return TYPE_MOBILE_HIPRI;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P)) {
+ return TYPE_WIFI_P2P;
+ }
+ return TYPE_NONE;
+ }
+
+ private static class LegacyRequest {
+ NetworkCapabilities networkCapabilities;
+ NetworkRequest networkRequest;
+ int expireSequenceNumber;
+ Network currentNetwork;
+ int delay = -1;
+
+ private void clearDnsBinding() {
+ if (currentNetwork != null) {
+ currentNetwork = null;
+ setProcessDefaultNetworkForHostResolution(null);
+ }
+ }
+
+ NetworkCallback networkCallback = new NetworkCallback() {
+ @Override
+ public void onAvailable(Network network) {
+ currentNetwork = network;
+ Log.d(TAG, "startUsingNetworkFeature got Network:" + network);
+ setProcessDefaultNetworkForHostResolution(network);
+ }
+ @Override
+ public void onLost(Network network) {
+ if (network.equals(currentNetwork)) clearDnsBinding();
+ Log.d(TAG, "startUsingNetworkFeature lost Network:" + network);
+ }
+ };
+ }
+
+ @UnsupportedAppUsage
+ private static final HashMap<NetworkCapabilities, LegacyRequest> sLegacyRequests =
+ new HashMap<>();
+
+ private NetworkRequest findRequestForFeature(NetworkCapabilities netCap) {
+ synchronized (sLegacyRequests) {
+ LegacyRequest l = sLegacyRequests.get(netCap);
+ if (l != null) return l.networkRequest;
+ }
+ return null;
+ }
+
+ private void renewRequestLocked(LegacyRequest l) {
+ l.expireSequenceNumber++;
+ Log.d(TAG, "renewing request to seqNum " + l.expireSequenceNumber);
+ sendExpireMsgForFeature(l.networkCapabilities, l.expireSequenceNumber, l.delay);
+ }
+
+ private void expireRequest(NetworkCapabilities netCap, int sequenceNum) {
+ int ourSeqNum = -1;
+ synchronized (sLegacyRequests) {
+ LegacyRequest l = sLegacyRequests.get(netCap);
+ if (l == null) return;
+ ourSeqNum = l.expireSequenceNumber;
+ if (l.expireSequenceNumber == sequenceNum) removeRequestForFeature(netCap);
+ }
+ Log.d(TAG, "expireRequest with " + ourSeqNum + ", " + sequenceNum);
+ }
+
+ @UnsupportedAppUsage
+ private NetworkRequest requestNetworkForFeatureLocked(NetworkCapabilities netCap) {
+ int delay = -1;
+ int type = legacyTypeForNetworkCapabilities(netCap);
+ try {
+ delay = mService.getRestoreDefaultNetworkDelay(type);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ LegacyRequest l = new LegacyRequest();
+ l.networkCapabilities = netCap;
+ l.delay = delay;
+ l.expireSequenceNumber = 0;
+ l.networkRequest = sendRequestForNetwork(
+ netCap, l.networkCallback, 0, REQUEST, type, getDefaultHandler());
+ if (l.networkRequest == null) return null;
+ sLegacyRequests.put(netCap, l);
+ sendExpireMsgForFeature(netCap, l.expireSequenceNumber, delay);
+ return l.networkRequest;
+ }
+
+ private void sendExpireMsgForFeature(NetworkCapabilities netCap, int seqNum, int delay) {
+ if (delay >= 0) {
+ Log.d(TAG, "sending expire msg with seqNum " + seqNum + " and delay " + delay);
+ CallbackHandler handler = getDefaultHandler();
+ Message msg = handler.obtainMessage(EXPIRE_LEGACY_REQUEST, seqNum, 0, netCap);
+ handler.sendMessageDelayed(msg, delay);
+ }
+ }
+
+ @UnsupportedAppUsage
+ private boolean removeRequestForFeature(NetworkCapabilities netCap) {
+ final LegacyRequest l;
+ synchronized (sLegacyRequests) {
+ l = sLegacyRequests.remove(netCap);
+ }
+ if (l == null) return false;
+ unregisterNetworkCallback(l.networkCallback);
+ l.clearDnsBinding();
+ return true;
+ }
+
+ private static final SparseIntArray sLegacyTypeToTransport = new SparseIntArray();
+ static {
+ sLegacyTypeToTransport.put(TYPE_MOBILE, NetworkCapabilities.TRANSPORT_CELLULAR);
+ sLegacyTypeToTransport.put(TYPE_MOBILE_CBS, NetworkCapabilities.TRANSPORT_CELLULAR);
+ sLegacyTypeToTransport.put(TYPE_MOBILE_DUN, NetworkCapabilities.TRANSPORT_CELLULAR);
+ sLegacyTypeToTransport.put(TYPE_MOBILE_FOTA, NetworkCapabilities.TRANSPORT_CELLULAR);
+ sLegacyTypeToTransport.put(TYPE_MOBILE_HIPRI, NetworkCapabilities.TRANSPORT_CELLULAR);
+ sLegacyTypeToTransport.put(TYPE_MOBILE_IMS, NetworkCapabilities.TRANSPORT_CELLULAR);
+ sLegacyTypeToTransport.put(TYPE_MOBILE_MMS, NetworkCapabilities.TRANSPORT_CELLULAR);
+ sLegacyTypeToTransport.put(TYPE_MOBILE_SUPL, NetworkCapabilities.TRANSPORT_CELLULAR);
+ sLegacyTypeToTransport.put(TYPE_WIFI, NetworkCapabilities.TRANSPORT_WIFI);
+ sLegacyTypeToTransport.put(TYPE_WIFI_P2P, NetworkCapabilities.TRANSPORT_WIFI);
+ sLegacyTypeToTransport.put(TYPE_BLUETOOTH, NetworkCapabilities.TRANSPORT_BLUETOOTH);
+ sLegacyTypeToTransport.put(TYPE_ETHERNET, NetworkCapabilities.TRANSPORT_ETHERNET);
+ }
+
+ private static final SparseIntArray sLegacyTypeToCapability = new SparseIntArray();
+ static {
+ sLegacyTypeToCapability.put(TYPE_MOBILE_CBS, NetworkCapabilities.NET_CAPABILITY_CBS);
+ sLegacyTypeToCapability.put(TYPE_MOBILE_DUN, NetworkCapabilities.NET_CAPABILITY_DUN);
+ sLegacyTypeToCapability.put(TYPE_MOBILE_FOTA, NetworkCapabilities.NET_CAPABILITY_FOTA);
+ sLegacyTypeToCapability.put(TYPE_MOBILE_IMS, NetworkCapabilities.NET_CAPABILITY_IMS);
+ sLegacyTypeToCapability.put(TYPE_MOBILE_MMS, NetworkCapabilities.NET_CAPABILITY_MMS);
+ sLegacyTypeToCapability.put(TYPE_MOBILE_SUPL, NetworkCapabilities.NET_CAPABILITY_SUPL);
+ sLegacyTypeToCapability.put(TYPE_WIFI_P2P, NetworkCapabilities.NET_CAPABILITY_WIFI_P2P);
+ }
+
+ /**
+ * Given a legacy type (TYPE_WIFI, ...) returns a NetworkCapabilities
+ * instance suitable for registering a request or callback. Throws an
+ * IllegalArgumentException if no mapping from the legacy type to
+ * NetworkCapabilities is known.
+ *
+ * @deprecated Types are deprecated. Use {@link NetworkCallback} or {@link NetworkRequest}
+ * to find the network instead.
+ * @hide
+ */
+ public static NetworkCapabilities networkCapabilitiesForType(int type) {
+ final NetworkCapabilities nc = new NetworkCapabilities();
+
+ // Map from type to transports.
+ final int NOT_FOUND = -1;
+ final int transport = sLegacyTypeToTransport.get(type, NOT_FOUND);
+ Preconditions.checkArgument(transport != NOT_FOUND, "unknown legacy type: " + type);
+ nc.addTransportType(transport);
+
+ // Map from type to capabilities.
+ nc.addCapability(sLegacyTypeToCapability.get(
+ type, NetworkCapabilities.NET_CAPABILITY_INTERNET));
+ nc.maybeMarkCapabilitiesRestricted();
+ return nc;
+ }
+
+ /** @hide */
+ public static class PacketKeepaliveCallback {
+ /** The requested keepalive was successfully started. */
+ @UnsupportedAppUsage
+ public void onStarted() {}
+ /** The keepalive was successfully stopped. */
+ @UnsupportedAppUsage
+ public void onStopped() {}
+ /** An error occurred. */
+ @UnsupportedAppUsage
+ public void onError(int error) {}
+ }
+
+ /**
+ * Allows applications to request that the system periodically send specific packets on their
+ * behalf, using hardware offload to save battery power.
+ *
+ * To request that the system send keepalives, call one of the methods that return a
+ * {@link ConnectivityManager.PacketKeepalive} object, such as {@link #startNattKeepalive},
+ * passing in a non-null callback. If the callback is successfully started, the callback's
+ * {@code onStarted} method will be called. If an error occurs, {@code onError} will be called,
+ * specifying one of the {@code ERROR_*} constants in this class.
+ *
+ * To stop an existing keepalive, call {@link PacketKeepalive#stop}. The system will call
+ * {@link PacketKeepaliveCallback#onStopped} if the operation was successful or
+ * {@link PacketKeepaliveCallback#onError} if an error occurred.
+ *
+ * @deprecated Use {@link SocketKeepalive} instead.
+ *
+ * @hide
+ */
+ public class PacketKeepalive {
+
+ private static final String TAG = "PacketKeepalive";
+
+ /** @hide */
+ public static final int SUCCESS = 0;
+
+ /** @hide */
+ public static final int NO_KEEPALIVE = -1;
+
+ /** @hide */
+ public static final int BINDER_DIED = -10;
+
+ /** The specified {@code Network} is not connected. */
+ public static final int ERROR_INVALID_NETWORK = -20;
+ /** The specified IP addresses are invalid. For example, the specified source IP address is
+ * not configured on the specified {@code Network}. */
+ public static final int ERROR_INVALID_IP_ADDRESS = -21;
+ /** The requested port is invalid. */
+ public static final int ERROR_INVALID_PORT = -22;
+ /** The packet length is invalid (e.g., too long). */
+ public static final int ERROR_INVALID_LENGTH = -23;
+ /** The packet transmission interval is invalid (e.g., too short). */
+ public static final int ERROR_INVALID_INTERVAL = -24;
+
+ /** The hardware does not support this request. */
+ public static final int ERROR_HARDWARE_UNSUPPORTED = -30;
+ /** The hardware returned an error. */
+ public static final int ERROR_HARDWARE_ERROR = -31;
+
+ /** The NAT-T destination port for IPsec */
+ public static final int NATT_PORT = 4500;
+
+ /** The minimum interval in seconds between keepalive packet transmissions */
+ public static final int MIN_INTERVAL = 10;
+
+ private final Network mNetwork;
+ private final ISocketKeepaliveCallback mCallback;
+ private final ExecutorService mExecutor;
+
+ private volatile Integer mSlot;
+
+ @UnsupportedAppUsage
+ public void stop() {
+ try {
+ mExecutor.execute(() -> {
+ try {
+ if (mSlot != null) {
+ mService.stopKeepalive(mNetwork, mSlot);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error stopping packet keepalive: ", e);
+ throw e.rethrowFromSystemServer();
+ }
+ });
+ } catch (RejectedExecutionException e) {
+ // The internal executor has already stopped due to previous event.
+ }
+ }
+
+ private PacketKeepalive(Network network, PacketKeepaliveCallback callback) {
+ Preconditions.checkNotNull(network, "network cannot be null");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ mNetwork = network;
+ mExecutor = Executors.newSingleThreadExecutor();
+ mCallback = new ISocketKeepaliveCallback.Stub() {
+ @Override
+ public void onStarted(int slot) {
+ Binder.withCleanCallingIdentity(() ->
+ mExecutor.execute(() -> {
+ mSlot = slot;
+ callback.onStarted();
+ }));
+ }
+
+ @Override
+ public void onStopped() {
+ Binder.withCleanCallingIdentity(() ->
+ mExecutor.execute(() -> {
+ mSlot = null;
+ callback.onStopped();
+ }));
+ mExecutor.shutdown();
+ }
+
+ @Override
+ public void onError(int error) {
+ Binder.withCleanCallingIdentity(() ->
+ mExecutor.execute(() -> {
+ mSlot = null;
+ callback.onError(error);
+ }));
+ mExecutor.shutdown();
+ }
+
+ @Override
+ public void onDataReceived() {
+ // PacketKeepalive is only used for Nat-T keepalive and as such does not invoke
+ // this callback when data is received.
+ }
+ };
+ }
+ }
+
+ /**
+ * Starts an IPsec NAT-T keepalive packet with the specified parameters.
+ *
+ * @deprecated Use {@link #createSocketKeepalive} instead.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public PacketKeepalive startNattKeepalive(
+ Network network, int intervalSeconds, PacketKeepaliveCallback callback,
+ InetAddress srcAddr, int srcPort, InetAddress dstAddr) {
+ final PacketKeepalive k = new PacketKeepalive(network, callback);
+ try {
+ mService.startNattKeepalive(network, intervalSeconds, k.mCallback,
+ srcAddr.getHostAddress(), srcPort, dstAddr.getHostAddress());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error starting packet keepalive: ", e);
+ throw e.rethrowFromSystemServer();
+ }
+ return k;
+ }
+
+ /**
+ * Request that keepalives be started on a IPsec NAT-T socket.
+ *
+ * @param network The {@link Network} the socket is on.
+ * @param socket The socket that needs to be kept alive.
+ * @param source The source address of the {@link UdpEncapsulationSocket}.
+ * @param destination The destination address of the {@link UdpEncapsulationSocket}.
+ * @param executor The executor on which callback will be invoked. The provided {@link Executor}
+ * must run callback sequentially, otherwise the order of callbacks cannot be
+ * guaranteed.
+ * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive
+ * changes. Must be extended by applications that use this API.
+ *
+ * @return A {@link SocketKeepalive} object that can be used to control the keepalive on the
+ * given socket.
+ **/
+ public @NonNull SocketKeepalive createSocketKeepalive(@NonNull Network network,
+ @NonNull UdpEncapsulationSocket socket,
+ @NonNull InetAddress source,
+ @NonNull InetAddress destination,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Callback callback) {
+ ParcelFileDescriptor dup;
+ try {
+ // Dup is needed here as the pfd inside the socket is owned by the IpSecService,
+ // which cannot be obtained by the app process.
+ dup = ParcelFileDescriptor.dup(socket.getFileDescriptor());
+ } catch (IOException ignored) {
+ // Construct an invalid fd, so that if the user later calls start(), it will fail with
+ // ERROR_INVALID_SOCKET.
+ dup = new ParcelFileDescriptor(new FileDescriptor());
+ }
+ return new NattSocketKeepalive(mService, network, dup, socket.getResourceId(), source,
+ destination, executor, callback);
+ }
+
+ /**
+ * Request that keepalives be started on a IPsec NAT-T socket file descriptor. Directly called
+ * by system apps which don't use IpSecService to create {@link UdpEncapsulationSocket}.
+ *
+ * @param network The {@link Network} the socket is on.
+ * @param pfd The {@link ParcelFileDescriptor} that needs to be kept alive. The provided
+ * {@link ParcelFileDescriptor} must be bound to a port and the keepalives will be sent
+ * from that port.
+ * @param source The source address of the {@link UdpEncapsulationSocket}.
+ * @param destination The destination address of the {@link UdpEncapsulationSocket}. The
+ * keepalive packets will always be sent to port 4500 of the given {@code destination}.
+ * @param executor The executor on which callback will be invoked. The provided {@link Executor}
+ * must run callback sequentially, otherwise the order of callbacks cannot be
+ * guaranteed.
+ * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive
+ * changes. Must be extended by applications that use this API.
+ *
+ * @return A {@link SocketKeepalive} object that can be used to control the keepalive on the
+ * given socket.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD)
+ public @NonNull SocketKeepalive createNattKeepalive(@NonNull Network network,
+ @NonNull ParcelFileDescriptor pfd,
+ @NonNull InetAddress source,
+ @NonNull InetAddress destination,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Callback callback) {
+ ParcelFileDescriptor dup;
+ try {
+ // TODO: Consider remove unnecessary dup.
+ dup = pfd.dup();
+ } catch (IOException ignored) {
+ // Construct an invalid fd, so that if the user later calls start(), it will fail with
+ // ERROR_INVALID_SOCKET.
+ dup = new ParcelFileDescriptor(new FileDescriptor());
+ }
+ return new NattSocketKeepalive(mService, network, dup,
+ INVALID_RESOURCE_ID /* Unused */, source, destination, executor, callback);
+ }
+
+ /**
+ * Request that keepalives be started on a TCP socket.
+ * The socket must be established.
+ *
+ * @param network The {@link Network} the socket is on.
+ * @param socket The socket that needs to be kept alive.
+ * @param executor The executor on which callback will be invoked. This implementation assumes
+ * the provided {@link Executor} runs the callbacks in sequence with no
+ * concurrency. Failing this, no guarantee of correctness can be made. It is
+ * the responsibility of the caller to ensure the executor provides this
+ * guarantee. A simple way of creating such an executor is with the standard
+ * tool {@code Executors.newSingleThreadExecutor}.
+ * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive
+ * changes. Must be extended by applications that use this API.
+ *
+ * @return A {@link SocketKeepalive} object that can be used to control the keepalive on the
+ * given socket.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD)
+ public @NonNull SocketKeepalive createSocketKeepalive(@NonNull Network network,
+ @NonNull Socket socket,
+ @NonNull Executor executor,
+ @NonNull Callback callback) {
+ ParcelFileDescriptor dup;
+ try {
+ dup = ParcelFileDescriptor.fromSocket(socket);
+ } catch (UncheckedIOException ignored) {
+ // Construct an invalid fd, so that if the user later calls start(), it will fail with
+ // ERROR_INVALID_SOCKET.
+ dup = new ParcelFileDescriptor(new FileDescriptor());
+ }
+ return new TcpSocketKeepalive(mService, network, dup, executor, callback);
+ }
+
+ /**
+ * Ensure that a network route exists to deliver traffic to the specified
+ * host via the specified network interface. An attempt to add a route that
+ * already exists is ignored, but treated as successful.
+ *
+ * <p>This method requires the caller to hold either the
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+ * or the ability to modify system settings as determined by
+ * {@link android.provider.Settings.System#canWrite}.</p>
+ *
+ * @param networkType the type of the network over which traffic to the specified
+ * host is to be routed
+ * @param hostAddress the IP address of the host to which the route is desired
+ * @return {@code true} on success, {@code false} on failure
+ *
+ * @deprecated Deprecated in favor of the
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)},
+ * {@link #bindProcessToNetwork} and {@link Network#getSocketFactory} API.
+ * In {@link VERSION_CODES#M}, and above, this method is unsupported and will
+ * throw {@code UnsupportedOperationException} if called.
+ * @removed
+ */
+ @Deprecated
+ public boolean requestRouteToHost(int networkType, int hostAddress) {
+ return requestRouteToHostAddress(networkType, NetworkUtils.intToInetAddress(hostAddress));
+ }
+
+ /**
+ * Ensure that a network route exists to deliver traffic to the specified
+ * host via the specified network interface. An attempt to add a route that
+ * already exists is ignored, but treated as successful.
+ *
+ * <p>This method requires the caller to hold either the
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+ * or the ability to modify system settings as determined by
+ * {@link android.provider.Settings.System#canWrite}.</p>
+ *
+ * @param networkType the type of the network over which traffic to the specified
+ * host is to be routed
+ * @param hostAddress the IP address of the host to which the route is desired
+ * @return {@code true} on success, {@code false} on failure
+ * @hide
+ * @deprecated Deprecated in favor of the {@link #requestNetwork} and
+ * {@link #bindProcessToNetwork} API.
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) {
+ checkLegacyRoutingApiAccess();
+ try {
+ return mService.requestRouteToHostAddress(networkType, hostAddress.getAddress());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the value of the setting for background data usage. If false,
+ * applications should not use the network if the application is not in the
+ * foreground. Developers should respect this setting, and check the value
+ * of this before performing any background data operations.
+ * <p>
+ * All applications that have background services that use the network
+ * should listen to {@link #ACTION_BACKGROUND_DATA_SETTING_CHANGED}.
+ * <p>
+ * @deprecated As of {@link VERSION_CODES#ICE_CREAM_SANDWICH}, availability of
+ * background data depends on several combined factors, and this method will
+ * always return {@code true}. Instead, when background data is unavailable,
+ * {@link #getActiveNetworkInfo()} will now appear disconnected.
+ *
+ * @return Whether background data usage is allowed.
+ */
+ @Deprecated
+ public boolean getBackgroundDataSetting() {
+ // assume that background data is allowed; final authority is
+ // NetworkInfo which may be blocked.
+ return true;
+ }
+
+ /**
+ * Sets the value of the setting for background data usage.
+ *
+ * @param allowBackgroundData Whether an application should use data while
+ * it is in the background.
+ *
+ * @attr ref android.Manifest.permission#CHANGE_BACKGROUND_DATA_SETTING
+ * @see #getBackgroundDataSetting()
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public void setBackgroundDataSetting(boolean allowBackgroundData) {
+ // ignored
+ }
+
+ /** {@hide} */
+ @Deprecated
+ @UnsupportedAppUsage
+ public NetworkQuotaInfo getActiveNetworkQuotaInfo() {
+ try {
+ return mService.getActiveNetworkQuotaInfo();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * @deprecated Talk to TelephonyManager directly
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public boolean getMobileDataEnabled() {
+ IBinder b = ServiceManager.getService(Context.TELEPHONY_SERVICE);
+ if (b != null) {
+ try {
+ ITelephony it = ITelephony.Stub.asInterface(b);
+ int subId = SubscriptionManager.getDefaultDataSubscriptionId();
+ Log.d("ConnectivityManager", "getMobileDataEnabled()+ subId=" + subId);
+ boolean retVal = it.isUserDataEnabled(subId);
+ Log.d("ConnectivityManager", "getMobileDataEnabled()- subId=" + subId
+ + " retVal=" + retVal);
+ return retVal;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ Log.d("ConnectivityManager", "getMobileDataEnabled()- remote exception retVal=false");
+ return false;
+ }
+
+ /**
+ * Callback for use with {@link ConnectivityManager#addDefaultNetworkActiveListener}
+ * to find out when the system default network has gone in to a high power state.
+ */
+ public interface OnNetworkActiveListener {
+ /**
+ * Called on the main thread of the process to report that the current data network
+ * has become active, and it is now a good time to perform any pending network
+ * operations. Note that this listener only tells you when the network becomes
+ * active; if at any other time you want to know whether it is active (and thus okay
+ * to initiate network traffic), you can retrieve its instantaneous state with
+ * {@link ConnectivityManager#isDefaultNetworkActive}.
+ */
+ void onNetworkActive();
+ }
+
+ private INetworkManagementService getNetworkManagementService() {
+ synchronized (this) {
+ if (mNMService != null) {
+ return mNMService;
+ }
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ mNMService = INetworkManagementService.Stub.asInterface(b);
+ return mNMService;
+ }
+ }
+
+ private final ArrayMap<OnNetworkActiveListener, INetworkActivityListener>
+ mNetworkActivityListeners = new ArrayMap<>();
+
+ /**
+ * Start listening to reports when the system's default data network is active, meaning it is
+ * a good time to perform network traffic. Use {@link #isDefaultNetworkActive()}
+ * to determine the current state of the system's default network after registering the
+ * listener.
+ * <p>
+ * If the process default network has been set with
+ * {@link ConnectivityManager#bindProcessToNetwork} this function will not
+ * reflect the process's default, but the system default.
+ *
+ * @param l The listener to be told when the network is active.
+ */
+ public void addDefaultNetworkActiveListener(final OnNetworkActiveListener l) {
+ INetworkActivityListener rl = new INetworkActivityListener.Stub() {
+ @Override
+ public void onNetworkActive() throws RemoteException {
+ l.onNetworkActive();
+ }
+ };
+
+ try {
+ getNetworkManagementService().registerNetworkActivityListener(rl);
+ mNetworkActivityListeners.put(l, rl);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove network active listener previously registered with
+ * {@link #addDefaultNetworkActiveListener}.
+ *
+ * @param l Previously registered listener.
+ */
+ public void removeDefaultNetworkActiveListener(@NonNull OnNetworkActiveListener l) {
+ INetworkActivityListener rl = mNetworkActivityListeners.get(l);
+ Preconditions.checkArgument(rl != null, "Listener was not registered.");
+ try {
+ getNetworkManagementService().unregisterNetworkActivityListener(rl);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return whether the data network is currently active. An active network means that
+ * it is currently in a high power state for performing data transmission. On some
+ * types of networks, it may be expensive to move and stay in such a state, so it is
+ * more power efficient to batch network traffic together when the radio is already in
+ * this state. This method tells you whether right now is currently a good time to
+ * initiate network traffic, as the network is already active.
+ */
+ public boolean isDefaultNetworkActive() {
+ try {
+ return getNetworkManagementService().isNetworkActive();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * {@hide}
+ */
+ public ConnectivityManager(Context context, IConnectivityManager service) {
+ mContext = Preconditions.checkNotNull(context, "missing context");
+ mService = Preconditions.checkNotNull(service, "missing IConnectivityManager");
+ sInstance = this;
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public static ConnectivityManager from(Context context) {
+ return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ }
+
+ /** @hide */
+ public NetworkRequest getDefaultRequest() {
+ try {
+ // This is not racy as the default request is final in ConnectivityService.
+ return mService.getDefaultRequest();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /* TODO: These permissions checks don't belong in client-side code. Move them to
+ * services.jar, possibly in com.android.server.net. */
+
+ /** {@hide} */
+ public static final void enforceChangePermission(Context context) {
+ int uid = Binder.getCallingUid();
+ Settings.checkAndNoteChangeNetworkStateOperation(context, uid, Settings
+ .getPackageNameForUid(context, uid), true /* throwException */);
+ }
+
+ /** {@hide} */
+ public static final void enforceTetherChangePermission(Context context, String callingPkg) {
+ Preconditions.checkNotNull(context, "Context cannot be null");
+ Preconditions.checkNotNull(callingPkg, "callingPkg cannot be null");
+
+ if (context.getResources().getStringArray(
+ com.android.internal.R.array.config_mobile_hotspot_provision_app).length == 2) {
+ // Have a provisioning app - must only let system apps (which check this app)
+ // turn on tethering
+ context.enforceCallingOrSelfPermission(
+ android.Manifest.permission.TETHER_PRIVILEGED, "ConnectivityService");
+ } else {
+ int uid = Binder.getCallingUid();
+ // If callingPkg's uid is not same as Binder.getCallingUid(),
+ // AppOpsService throws SecurityException.
+ Settings.checkAndNoteWriteSettingsOperation(context, uid, callingPkg,
+ true /* throwException */);
+ }
+ }
+
+ /**
+ * @deprecated - use getSystemService. This is a kludge to support static access in certain
+ * situations where a Context pointer is unavailable.
+ * @hide
+ */
+ @Deprecated
+ static ConnectivityManager getInstanceOrNull() {
+ return sInstance;
+ }
+
+ /**
+ * @deprecated - use getSystemService. This is a kludge to support static access in certain
+ * situations where a Context pointer is unavailable.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ private static ConnectivityManager getInstance() {
+ if (getInstanceOrNull() == null) {
+ throw new IllegalStateException("No ConnectivityManager yet constructed");
+ }
+ return getInstanceOrNull();
+ }
+
+ /**
+ * Get the set of tetherable, available interfaces. This list is limited by
+ * device configuration and current interface existence.
+ *
+ * @return an array of 0 or more Strings of tetherable interface names.
+ *
+ * {@hide}
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @UnsupportedAppUsage
+ public String[] getTetherableIfaces() {
+ try {
+ return mService.getTetherableIfaces();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the set of tethered interfaces.
+ *
+ * @return an array of 0 or more String of currently tethered interface names.
+ *
+ * {@hide}
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @UnsupportedAppUsage
+ public String[] getTetheredIfaces() {
+ try {
+ return mService.getTetheredIfaces();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the set of interface names which attempted to tether but
+ * failed. Re-attempting to tether may cause them to reset to the Tethered
+ * state. Alternatively, causing the interface to be destroyed and recreated
+ * may cause them to reset to the available state.
+ * {@link ConnectivityManager#getLastTetherError} can be used to get more
+ * information on the cause of the errors.
+ *
+ * @return an array of 0 or more String indicating the interface names
+ * which failed to tether.
+ *
+ * {@hide}
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @UnsupportedAppUsage
+ public String[] getTetheringErroredIfaces() {
+ try {
+ return mService.getTetheringErroredIfaces();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the set of tethered dhcp ranges.
+ *
+ * @return an array of 0 or more {@code String} of tethered dhcp ranges.
+ * {@hide}
+ */
+ public String[] getTetheredDhcpRanges() {
+ try {
+ return mService.getTetheredDhcpRanges();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Attempt to tether the named interface. This will setup a dhcp server
+ * on the interface, forward and NAT IP packets and forward DNS requests
+ * to the best active upstream network interface. Note that if no upstream
+ * IP network interface is available, dhcp will still run and traffic will be
+ * allowed between the tethered devices and this device, though upstream net
+ * access will of course fail until an upstream network interface becomes
+ * active.
+ *
+ * <p>This method requires the caller to hold either the
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+ * or the ability to modify system settings as determined by
+ * {@link android.provider.Settings.System#canWrite}.</p>
+ *
+ * <p>WARNING: New clients should not use this function. The only usages should be in PanService
+ * and WifiStateMachine which need direct access. All other clients should use
+ * {@link #startTethering} and {@link #stopTethering} which encapsulate proper provisioning
+ * logic.</p>
+ *
+ * @param iface the interface name to tether.
+ * @return error a {@code TETHER_ERROR} value indicating success or failure type
+ *
+ * {@hide}
+ */
+ @UnsupportedAppUsage
+ public int tether(String iface) {
+ try {
+ String pkgName = mContext.getOpPackageName();
+ Log.i(TAG, "tether caller:" + pkgName);
+ return mService.tether(iface, pkgName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Stop tethering the named interface.
+ *
+ * <p>This method requires the caller to hold either the
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+ * or the ability to modify system settings as determined by
+ * {@link android.provider.Settings.System#canWrite}.</p>
+ *
+ * <p>WARNING: New clients should not use this function. The only usages should be in PanService
+ * and WifiStateMachine which need direct access. All other clients should use
+ * {@link #startTethering} and {@link #stopTethering} which encapsulate proper provisioning
+ * logic.</p>
+ *
+ * @param iface the interface name to untether.
+ * @return error a {@code TETHER_ERROR} value indicating success or failure type
+ *
+ * {@hide}
+ */
+ @UnsupportedAppUsage
+ public int untether(String iface) {
+ try {
+ String pkgName = mContext.getOpPackageName();
+ Log.i(TAG, "untether caller:" + pkgName);
+ return mService.untether(iface, pkgName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Check if the device allows for tethering. It may be disabled via
+ * {@code ro.tether.denied} system property, Settings.TETHER_SUPPORTED or
+ * due to device configuration.
+ *
+ * <p>If this app does not have permission to use this API, it will always
+ * return false rather than throw an exception.</p>
+ *
+ * <p>If the device has a hotspot provisioning app, the caller is required to hold the
+ * {@link android.Manifest.permission.TETHER_PRIVILEGED} permission.</p>
+ *
+ * <p>Otherwise, this method requires the caller to hold the ability to modify system
+ * settings as determined by {@link android.provider.Settings.System#canWrite}.</p>
+ *
+ * @return a boolean - {@code true} indicating Tethering is supported.
+ *
+ * {@hide}
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {android.Manifest.permission.TETHER_PRIVILEGED,
+ android.Manifest.permission.WRITE_SETTINGS})
+ public boolean isTetheringSupported() {
+ String pkgName = mContext.getOpPackageName();
+ try {
+ return mService.isTetheringSupported(pkgName);
+ } catch (SecurityException e) {
+ // This API is not available to this caller, but for backward-compatibility
+ // this will just return false instead of throwing.
+ return false;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Callback for use with {@link #startTethering} to find out whether tethering succeeded.
+ * @hide
+ */
+ @SystemApi
+ public static abstract class OnStartTetheringCallback {
+ /**
+ * Called when tethering has been successfully started.
+ */
+ public void onTetheringStarted() {}
+
+ /**
+ * Called when starting tethering failed.
+ */
+ public void onTetheringFailed() {}
+ }
+
+ /**
+ * Convenient overload for
+ * {@link #startTethering(int, boolean, OnStartTetheringCallback, Handler)} which passes a null
+ * handler to run on the current thread's {@link Looper}.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
+ public void startTethering(int type, boolean showProvisioningUi,
+ final OnStartTetheringCallback callback) {
+ startTethering(type, showProvisioningUi, callback, null);
+ }
+
+ /**
+ * Runs tether provisioning for the given type if needed and then starts tethering if
+ * the check succeeds. If no carrier provisioning is required for tethering, tethering is
+ * enabled immediately. If provisioning fails, tethering will not be enabled. It also
+ * schedules tether provisioning re-checks if appropriate.
+ *
+ * @param type The type of tethering to start. Must be one of
+ * {@link ConnectivityManager.TETHERING_WIFI},
+ * {@link ConnectivityManager.TETHERING_USB}, or
+ * {@link ConnectivityManager.TETHERING_BLUETOOTH}.
+ * @param showProvisioningUi a boolean indicating to show the provisioning app UI if there
+ * is one. This should be true the first time this function is called and also any time
+ * the user can see this UI. It gives users information from their carrier about the
+ * check failing and how they can sign up for tethering if possible.
+ * @param callback an {@link OnStartTetheringCallback} which will be called to notify the caller
+ * of the result of trying to tether.
+ * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
+ public void startTethering(int type, boolean showProvisioningUi,
+ final OnStartTetheringCallback callback, Handler handler) {
+ Preconditions.checkNotNull(callback, "OnStartTetheringCallback cannot be null.");
+
+ ResultReceiver wrappedCallback = new ResultReceiver(handler) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == TETHER_ERROR_NO_ERROR) {
+ callback.onTetheringStarted();
+ } else {
+ callback.onTetheringFailed();
+ }
+ }
+ };
+
+ try {
+ String pkgName = mContext.getOpPackageName();
+ Log.i(TAG, "startTethering caller:" + pkgName);
+ mService.startTethering(type, wrappedCallback, showProvisioningUi, pkgName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception trying to start tethering.", e);
+ wrappedCallback.send(TETHER_ERROR_SERVICE_UNAVAIL, null);
+ }
+ }
+
+ /**
+ * Stops tethering for the given type. Also cancels any provisioning rechecks for that type if
+ * applicable.
+ *
+ * @param type The type of tethering to stop. Must be one of
+ * {@link ConnectivityManager.TETHERING_WIFI},
+ * {@link ConnectivityManager.TETHERING_USB}, or
+ * {@link ConnectivityManager.TETHERING_BLUETOOTH}.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
+ public void stopTethering(int type) {
+ try {
+ String pkgName = mContext.getOpPackageName();
+ Log.i(TAG, "stopTethering caller:" + pkgName);
+ mService.stopTethering(type, pkgName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Callback for use with {@link registerTetheringEventCallback} to find out tethering
+ * upstream status.
+ *
+ *@hide
+ */
+ @SystemApi
+ public abstract static class OnTetheringEventCallback {
+
+ /**
+ * Called when tethering upstream changed. This can be called multiple times and can be
+ * called any time.
+ *
+ * @param network the {@link Network} of tethering upstream. Null means tethering doesn't
+ * have any upstream.
+ */
+ public void onUpstreamChanged(@Nullable Network network) {}
+ }
+
+ @GuardedBy("mTetheringEventCallbacks")
+ private final ArrayMap<OnTetheringEventCallback, ITetheringEventCallback>
+ mTetheringEventCallbacks = new ArrayMap<>();
+
+ /**
+ * Start listening to tethering change events. Any new added callback will receive the last
+ * tethering status right away. If callback is registered when tethering has no upstream or
+ * disabled, {@link OnTetheringEventCallback#onUpstreamChanged} will immediately be called
+ * with a null argument. The same callback object cannot be registered twice.
+ *
+ * @param executor the executor on which callback will be invoked.
+ * @param callback the callback to be called when tethering has change events.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
+ public void registerTetheringEventCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull final OnTetheringEventCallback callback) {
+ Preconditions.checkNotNull(callback, "OnTetheringEventCallback cannot be null.");
+
+ synchronized (mTetheringEventCallbacks) {
+ Preconditions.checkArgument(!mTetheringEventCallbacks.containsKey(callback),
+ "callback was already registered.");
+ ITetheringEventCallback remoteCallback = new ITetheringEventCallback.Stub() {
+ @Override
+ public void onUpstreamChanged(Network network) throws RemoteException {
+ Binder.withCleanCallingIdentity(() ->
+ executor.execute(() -> {
+ callback.onUpstreamChanged(network);
+ }));
+ }
+ };
+ try {
+ String pkgName = mContext.getOpPackageName();
+ Log.i(TAG, "registerTetheringUpstreamCallback:" + pkgName);
+ mService.registerTetheringEventCallback(remoteCallback, pkgName);
+ mTetheringEventCallbacks.put(callback, remoteCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Remove tethering event callback previously registered with
+ * {@link #registerTetheringEventCallback}.
+ *
+ * @param callback previously registered callback.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
+ public void unregisterTetheringEventCallback(
+ @NonNull final OnTetheringEventCallback callback) {
+ synchronized (mTetheringEventCallbacks) {
+ ITetheringEventCallback remoteCallback = mTetheringEventCallbacks.remove(callback);
+ Preconditions.checkNotNull(remoteCallback, "callback was not registered.");
+ try {
+ String pkgName = mContext.getOpPackageName();
+ Log.i(TAG, "unregisterTetheringEventCallback:" + pkgName);
+ mService.unregisterTetheringEventCallback(remoteCallback, pkgName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+
+ /**
+ * Get the list of regular expressions that define any tetherable
+ * USB network interfaces. If USB tethering is not supported by the
+ * device, this list should be empty.
+ *
+ * @return an array of 0 or more regular expression Strings defining
+ * what interfaces are considered tetherable usb interfaces.
+ *
+ * {@hide}
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @UnsupportedAppUsage
+ public String[] getTetherableUsbRegexs() {
+ try {
+ return mService.getTetherableUsbRegexs();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the list of regular expressions that define any tetherable
+ * Wifi network interfaces. If Wifi tethering is not supported by the
+ * device, this list should be empty.
+ *
+ * @return an array of 0 or more regular expression Strings defining
+ * what interfaces are considered tetherable wifi interfaces.
+ *
+ * {@hide}
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @UnsupportedAppUsage
+ public String[] getTetherableWifiRegexs() {
+ try {
+ return mService.getTetherableWifiRegexs();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the list of regular expressions that define any tetherable
+ * Bluetooth network interfaces. If Bluetooth tethering is not supported by the
+ * device, this list should be empty.
+ *
+ * @return an array of 0 or more regular expression Strings defining
+ * what interfaces are considered tetherable bluetooth interfaces.
+ *
+ * {@hide}
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @UnsupportedAppUsage
+ public String[] getTetherableBluetoothRegexs() {
+ try {
+ return mService.getTetherableBluetoothRegexs();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Attempt to both alter the mode of USB and Tethering of USB. A
+ * utility method to deal with some of the complexity of USB - will
+ * attempt to switch to Rndis and subsequently tether the resulting
+ * interface on {@code true} or turn off tethering and switch off
+ * Rndis on {@code false}.
+ *
+ * <p>This method requires the caller to hold either the
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+ * or the ability to modify system settings as determined by
+ * {@link android.provider.Settings.System#canWrite}.</p>
+ *
+ * @param enable a boolean - {@code true} to enable tethering
+ * @return error a {@code TETHER_ERROR} value indicating success or failure type
+ *
+ * {@hide}
+ */
+ @UnsupportedAppUsage
+ public int setUsbTethering(boolean enable) {
+ try {
+ String pkgName = mContext.getOpPackageName();
+ Log.i(TAG, "setUsbTethering caller:" + pkgName);
+ return mService.setUsbTethering(enable, pkgName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
+ @SystemApi
+ public static final int TETHER_ERROR_NO_ERROR = 0;
+ /** {@hide} */
+ public static final int TETHER_ERROR_UNKNOWN_IFACE = 1;
+ /** {@hide} */
+ public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2;
+ /** {@hide} */
+ public static final int TETHER_ERROR_UNSUPPORTED = 3;
+ /** {@hide} */
+ public static final int TETHER_ERROR_UNAVAIL_IFACE = 4;
+ /** {@hide} */
+ public static final int TETHER_ERROR_MASTER_ERROR = 5;
+ /** {@hide} */
+ public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6;
+ /** {@hide} */
+ public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7;
+ /** {@hide} */
+ public static final int TETHER_ERROR_ENABLE_NAT_ERROR = 8;
+ /** {@hide} */
+ public static final int TETHER_ERROR_DISABLE_NAT_ERROR = 9;
+ /** {@hide} */
+ public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10;
+ /** {@hide} */
+ @SystemApi
+ public static final int TETHER_ERROR_PROVISION_FAILED = 11;
+ /** {@hide} */
+ public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12;
+ /** {@hide} */
+ @SystemApi
+ public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13;
+
+ /**
+ * Get a more detailed error code after a Tethering or Untethering
+ * request asynchronously failed.
+ *
+ * @param iface The name of the interface of interest
+ * @return error The error code of the last error tethering or untethering the named
+ * interface
+ *
+ * {@hide}
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @UnsupportedAppUsage
+ public int getLastTetherError(String iface) {
+ try {
+ return mService.getLastTetherError(iface);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ TETHER_ERROR_NO_ERROR,
+ TETHER_ERROR_PROVISION_FAILED,
+ TETHER_ERROR_ENTITLEMENT_UNKONWN,
+ })
+ public @interface EntitlementResultCode {
+ }
+
+ /**
+ * Callback for use with {@link #getLatestTetheringEntitlementResult} to find out whether
+ * entitlement succeeded.
+ * @hide
+ */
+ @SystemApi
+ public interface OnTetheringEntitlementResultListener {
+ /**
+ * Called to notify entitlement result.
+ *
+ * @param resultCode an int value of entitlement result. It may be one of
+ * {@link #TETHER_ERROR_NO_ERROR},
+ * {@link #TETHER_ERROR_PROVISION_FAILED}, or
+ * {@link #TETHER_ERROR_ENTITLEMENT_UNKONWN}.
+ */
+ void onTetheringEntitlementResult(@EntitlementResultCode int resultCode);
+ }
+
+ /**
+ * Get the last value of the entitlement check on this downstream. If the cached value is
+ * {@link #TETHER_ERROR_NO_ERROR} or showEntitlementUi argument is false, it just return the
+ * cached value. Otherwise, a UI-based entitlement check would be performed. It is not
+ * guaranteed that the UI-based entitlement check will complete in any specific time period
+ * and may in fact never complete. Any successful entitlement check the platform performs for
+ * any reason will update the cached value.
+ *
+ * @param type the downstream type of tethering. Must be one of
+ * {@link #TETHERING_WIFI},
+ * {@link #TETHERING_USB}, or
+ * {@link #TETHERING_BLUETOOTH}.
+ * @param showEntitlementUi a boolean indicating whether to run UI-based entitlement check.
+ * @param executor the executor on which callback will be invoked.
+ * @param listener an {@link OnTetheringEntitlementResultListener} which will be called to
+ * notify the caller of the result of entitlement check. The listener may be called zero
+ * or one time.
+ * {@hide}
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
+ public void getLatestTetheringEntitlementResult(int type, boolean showEntitlementUi,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull final OnTetheringEntitlementResultListener listener) {
+ Preconditions.checkNotNull(listener, "TetheringEntitlementResultListener cannot be null.");
+ ResultReceiver wrappedListener = new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ Binder.withCleanCallingIdentity(() ->
+ executor.execute(() -> {
+ listener.onTetheringEntitlementResult(resultCode);
+ }));
+ }
+ };
+
+ try {
+ String pkgName = mContext.getOpPackageName();
+ Log.i(TAG, "getLatestTetheringEntitlementResult:" + pkgName);
+ mService.getLatestTetheringEntitlementResult(type, wrappedListener,
+ showEntitlementUi, pkgName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Report network connectivity status. This is currently used only
+ * to alter status bar UI.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#STATUS_BAR}.
+ *
+ * @param networkType The type of network you want to report on
+ * @param percentage The quality of the connection 0 is bad, 100 is good
+ * @deprecated Types are deprecated. Use {@link #reportNetworkConnectivity} instead.
+ * {@hide}
+ */
+ public void reportInetCondition(int networkType, int percentage) {
+ printStackTrace();
+ try {
+ mService.reportInetCondition(networkType, percentage);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Report a problem network to the framework. This provides a hint to the system
+ * that there might be connectivity problems on this network and may cause
+ * the framework to re-evaluate network connectivity and/or switch to another
+ * network.
+ *
+ * @param network The {@link Network} the application was attempting to use
+ * or {@code null} to indicate the current default network.
+ * @deprecated Use {@link #reportNetworkConnectivity} which allows reporting both
+ * working and non-working connectivity.
+ */
+ @Deprecated
+ public void reportBadNetwork(@Nullable Network network) {
+ printStackTrace();
+ try {
+ // One of these will be ignored because it matches system's current state.
+ // The other will trigger the necessary reevaluation.
+ mService.reportNetworkConnectivity(network, true);
+ mService.reportNetworkConnectivity(network, false);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Report to the framework whether a network has working connectivity.
+ * This provides a hint to the system that a particular network is providing
+ * working connectivity or not. In response the framework may re-evaluate
+ * the network's connectivity and might take further action thereafter.
+ *
+ * @param network The {@link Network} the application was attempting to use
+ * or {@code null} to indicate the current default network.
+ * @param hasConnectivity {@code true} if the application was able to successfully access the
+ * Internet using {@code network} or {@code false} if not.
+ */
+ public void reportNetworkConnectivity(@Nullable Network network, boolean hasConnectivity) {
+ printStackTrace();
+ try {
+ mService.reportNetworkConnectivity(network, hasConnectivity);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set a network-independent global http proxy. This is not normally what you want
+ * for typical HTTP proxies - they are general network dependent. However if you're
+ * doing something unusual like general internal filtering this may be useful. On
+ * a private network where the proxy is not accessible, you may break HTTP using this.
+ *
+ * @param p A {@link ProxyInfo} object defining the new global
+ * HTTP proxy. A {@code null} value will clear the global HTTP proxy.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL)
+ public void setGlobalProxy(ProxyInfo p) {
+ try {
+ mService.setGlobalProxy(p);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Retrieve any network-independent global HTTP proxy.
+ *
+ * @return {@link ProxyInfo} for the current global HTTP proxy or {@code null}
+ * if no global HTTP proxy is set.
+ * @hide
+ */
+ public ProxyInfo getGlobalProxy() {
+ try {
+ return mService.getGlobalProxy();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Retrieve the global HTTP proxy, or if no global HTTP proxy is set, a
+ * network-specific HTTP proxy. If {@code network} is null, the
+ * network-specific proxy returned is the proxy of the default active
+ * network.
+ *
+ * @return {@link ProxyInfo} for the current global HTTP proxy, or if no
+ * global HTTP proxy is set, {@code ProxyInfo} for {@code network},
+ * or when {@code network} is {@code null},
+ * the {@code ProxyInfo} for the default active network. Returns
+ * {@code null} when no proxy applies or the caller doesn't have
+ * permission to use {@code network}.
+ * @hide
+ */
+ public ProxyInfo getProxyForNetwork(Network network) {
+ try {
+ return mService.getProxyForNetwork(network);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the current default HTTP proxy settings. If a global proxy is set it will be returned,
+ * otherwise if this process is bound to a {@link Network} using
+ * {@link #bindProcessToNetwork} then that {@code Network}'s proxy is returned, otherwise
+ * the default network's proxy is returned.
+ *
+ * @return the {@link ProxyInfo} for the current HTTP proxy, or {@code null} if no
+ * HTTP proxy is active.
+ */
+ @Nullable
+ public ProxyInfo getDefaultProxy() {
+ return getProxyForNetwork(getBoundNetworkForProcess());
+ }
+
+ /**
+ * Returns true if the hardware supports the given network type
+ * else it returns false. This doesn't indicate we have coverage
+ * or are authorized onto a network, just whether or not the
+ * hardware supports it. For example a GSM phone without a SIM
+ * should still return {@code true} for mobile data, but a wifi only
+ * tablet would return {@code false}.
+ *
+ * @param networkType The network type we'd like to check
+ * @return {@code true} if supported, else {@code false}
+ * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead.
+ * @hide
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
+ public boolean isNetworkSupported(int networkType) {
+ try {
+ return mService.isNetworkSupported(networkType);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns if the currently active data network is metered. A network is
+ * classified as metered when the user is sensitive to heavy data usage on
+ * that connection due to monetary costs, data limitations or
+ * battery/performance issues. You should check this before doing large
+ * data transfers, and warn the user or delay the operation until another
+ * network is available.
+ *
+ * @return {@code true} if large transfers should be avoided, otherwise
+ * {@code false}.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ public boolean isActiveNetworkMetered() {
+ try {
+ return mService.isActiveNetworkMetered();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * If the LockdownVpn mechanism is enabled, updates the vpn
+ * with a reload of its profile.
+ *
+ * @return a boolean with {@code} indicating success
+ *
+ * <p>This method can only be called by the system UID
+ * {@hide}
+ */
+ public boolean updateLockdownVpn() {
+ try {
+ return mService.updateLockdownVpn();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Check mobile provisioning.
+ *
+ * @param suggestedTimeOutMs, timeout in milliseconds
+ *
+ * @return time out that will be used, maybe less that suggestedTimeOutMs
+ * -1 if an error.
+ *
+ * {@hide}
+ */
+ public int checkMobileProvisioning(int suggestedTimeOutMs) {
+ int timeOutMs = -1;
+ try {
+ timeOutMs = mService.checkMobileProvisioning(suggestedTimeOutMs);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return timeOutMs;
+ }
+
+ /**
+ * Get the mobile provisioning url.
+ * {@hide}
+ */
+ public String getMobileProvisioningUrl() {
+ try {
+ return mService.getMobileProvisioningUrl();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set sign in error notification to visible or in visible
+ *
+ * {@hide}
+ * @deprecated Doesn't properly deal with multiple connected networks of the same type.
+ */
+ @Deprecated
+ public void setProvisioningNotificationVisible(boolean visible, int networkType,
+ String action) {
+ try {
+ mService.setProvisioningNotificationVisible(visible, networkType, action);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set the value for enabling/disabling airplane mode
+ *
+ * @param enable whether to enable airplane mode or not
+ *
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD,
+ android.Manifest.permission.NETWORK_STACK})
+ @SystemApi
+ public void setAirplaneMode(boolean enable) {
+ try {
+ mService.setAirplaneMode(enable);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} - returns the factory serial number */
+ @UnsupportedAppUsage
+ public int registerNetworkFactory(Messenger messenger, String name) {
+ try {
+ return mService.registerNetworkFactory(messenger, name);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public void unregisterNetworkFactory(Messenger messenger) {
+ try {
+ mService.unregisterNetworkFactory(messenger);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ // TODO : remove this method. It is a stopgap measure to help sheperding a number
+ // of dependent changes that would conflict throughout the automerger graph. Having this
+ // temporarily helps with the process of going through with all these dependent changes across
+ // the entire tree.
+ /**
+ * @hide
+ * Register a NetworkAgent with ConnectivityService.
+ * @return NetID corresponding to NetworkAgent.
+ */
+ public int registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
+ NetworkCapabilities nc, int score, NetworkMisc misc) {
+ return registerNetworkAgent(messenger, ni, lp, nc, score, misc,
+ NetworkFactory.SerialNumber.NONE);
+ }
+
+ /**
+ * @hide
+ * Register a NetworkAgent with ConnectivityService.
+ * @return NetID corresponding to NetworkAgent.
+ */
+ public int registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
+ NetworkCapabilities nc, int score, NetworkMisc misc, int factorySerialNumber) {
+ try {
+ return mService.registerNetworkAgent(messenger, ni, lp, nc, score, misc,
+ factorySerialNumber);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Base class for {@code NetworkRequest} callbacks. Used for notifications about network
+ * changes. Should be extended by applications wanting notifications.
+ *
+ * A {@code NetworkCallback} is registered by calling
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)},
+ * {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)},
+ * or {@link #registerDefaultNetworkCallback(NetworkCallback)}. A {@code NetworkCallback} is
+ * unregistered by calling {@link #unregisterNetworkCallback(NetworkCallback)}.
+ * A {@code NetworkCallback} should be registered at most once at any time.
+ * A {@code NetworkCallback} that has been unregistered can be registered again.
+ */
+ public static class NetworkCallback {
+ /**
+ * Called when the framework connects to a new network to evaluate whether it satisfies this
+ * request. If evaluation succeeds, this callback may be followed by an {@link #onAvailable}
+ * callback. There is no guarantee that this new network will satisfy any requests, or that
+ * the network will stay connected for longer than the time necessary to evaluate it.
+ * <p>
+ * Most applications <b>should not</b> act on this callback, and should instead use
+ * {@link #onAvailable}. This callback is intended for use by applications that can assist
+ * the framework in properly evaluating the network — for example, an application that
+ * can automatically log in to a captive portal without user intervention.
+ *
+ * @param network The {@link Network} of the network that is being evaluated.
+ *
+ * @hide
+ */
+ public void onPreCheck(@NonNull Network network) {}
+
+ /**
+ * Called when the framework connects and has declared a new network ready for use.
+ * This callback may be called more than once if the {@link Network} that is
+ * satisfying the request changes.
+ *
+ * @param network The {@link Network} of the satisfying network.
+ * @param networkCapabilities The {@link NetworkCapabilities} of the satisfying network.
+ * @param linkProperties The {@link LinkProperties} of the satisfying network.
+ * @param blocked Whether access to the {@link Network} is blocked due to system policy.
+ * @hide
+ */
+ public void onAvailable(@NonNull Network network,
+ @NonNull NetworkCapabilities networkCapabilities,
+ @NonNull LinkProperties linkProperties, boolean blocked) {
+ // Internally only this method is called when a new network is available, and
+ // it calls the callback in the same way and order that older versions used
+ // to call so as not to change the behavior.
+ onAvailable(network);
+ if (!networkCapabilities.hasCapability(
+ NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)) {
+ onNetworkSuspended(network);
+ }
+ onCapabilitiesChanged(network, networkCapabilities);
+ onLinkPropertiesChanged(network, linkProperties);
+ onBlockedStatusChanged(network, blocked);
+ }
+
+ /**
+ * Called when the framework connects and has declared a new network ready for use.
+ * This callback may be called more than once if the {@link Network} that is
+ * satisfying the request changes. This will always immediately be followed by a
+ * call to {@link #onCapabilitiesChanged(Network, NetworkCapabilities)} then by a
+ * call to {@link #onLinkPropertiesChanged(Network, LinkProperties)}, and a call to
+ * {@link #onBlockedStatusChanged(Network, boolean)}.
+ *
+ * @param network The {@link Network} of the satisfying network.
+ */
+ public void onAvailable(@NonNull Network network) {}
+
+ /**
+ * Called when the network is about to be disconnected. Often paired with an
+ * {@link NetworkCallback#onAvailable} call with the new replacement network
+ * for graceful handover. This may not be called if we have a hard loss
+ * (loss without warning). This may be followed by either a
+ * {@link NetworkCallback#onLost} call or a
+ * {@link NetworkCallback#onAvailable} call for this network depending
+ * on whether we lose or regain it.
+ *
+ * @param network The {@link Network} that is about to be disconnected.
+ * @param maxMsToLive The time in ms the framework will attempt to keep the
+ * network connected. Note that the network may suffer a
+ * hard loss at any time.
+ */
+ public void onLosing(@NonNull Network network, int maxMsToLive) {}
+
+ /**
+ * Called when the framework has a hard loss of the network or when the
+ * graceful failure ends.
+ *
+ * @param network The {@link Network} lost.
+ */
+ public void onLost(@NonNull Network network) {}
+
+ /**
+ * Called if no network is found in the timeout time specified in
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} call or if the
+ * requested network request cannot be fulfilled (whether or not a timeout was
+ * specified). When this callback is invoked the associated
+ * {@link NetworkRequest} will have already been removed and released, as if
+ * {@link #unregisterNetworkCallback(NetworkCallback)} had been called.
+ */
+ public void onUnavailable() {}
+
+ /**
+ * Called when the network the framework connected to for this request
+ * changes capabilities but still satisfies the stated need.
+ *
+ * @param network The {@link Network} whose capabilities have changed.
+ * @param networkCapabilities The new {@link android.net.NetworkCapabilities} for this
+ * network.
+ */
+ public void onCapabilitiesChanged(@NonNull Network network,
+ @NonNull NetworkCapabilities networkCapabilities) {}
+
+ /**
+ * Called when the network the framework connected to for this request
+ * changes {@link LinkProperties}.
+ *
+ * @param network The {@link Network} whose link properties have changed.
+ * @param linkProperties The new {@link LinkProperties} for this network.
+ */
+ public void onLinkPropertiesChanged(@NonNull Network network,
+ @NonNull LinkProperties linkProperties) {}
+
+ /**
+ * Called when the network the framework connected to for this request
+ * goes into {@link NetworkInfo.State#SUSPENDED}.
+ * This generally means that while the TCP connections are still live,
+ * temporarily network data fails to transfer. Specifically this is used
+ * on cellular networks to mask temporary outages when driving through
+ * a tunnel, etc.
+ * @hide
+ */
+ public void onNetworkSuspended(@NonNull Network network) {}
+
+ /**
+ * Called when the network the framework connected to for this request
+ * returns from a {@link NetworkInfo.State#SUSPENDED} state. This should always be
+ * preceded by a matching {@link NetworkCallback#onNetworkSuspended} call.
+ * @hide
+ */
+ public void onNetworkResumed(@NonNull Network network) {}
+
+ /**
+ * Called when access to the specified network is blocked or unblocked.
+ *
+ * @param network The {@link Network} whose blocked status has changed.
+ * @param blocked The blocked status of this {@link Network}.
+ */
+ public void onBlockedStatusChanged(@NonNull Network network, boolean blocked) {}
+
+ private NetworkRequest networkRequest;
+ }
+
+ /**
+ * Constant error codes used by ConnectivityService to communicate about failures and errors
+ * across a Binder boundary.
+ * @hide
+ */
+ public interface Errors {
+ int TOO_MANY_REQUESTS = 1;
+ }
+
+ /** @hide */
+ public static class TooManyRequestsException extends RuntimeException {}
+
+ private static RuntimeException convertServiceException(ServiceSpecificException e) {
+ switch (e.errorCode) {
+ case Errors.TOO_MANY_REQUESTS:
+ return new TooManyRequestsException();
+ default:
+ Log.w(TAG, "Unknown service error code " + e.errorCode);
+ return new RuntimeException(e);
+ }
+ }
+
+ private static final int BASE = Protocol.BASE_CONNECTIVITY_MANAGER;
+ /** @hide */
+ public static final int CALLBACK_PRECHECK = BASE + 1;
+ /** @hide */
+ public static final int CALLBACK_AVAILABLE = BASE + 2;
+ /** @hide arg1 = TTL */
+ public static final int CALLBACK_LOSING = BASE + 3;
+ /** @hide */
+ public static final int CALLBACK_LOST = BASE + 4;
+ /** @hide */
+ public static final int CALLBACK_UNAVAIL = BASE + 5;
+ /** @hide */
+ public static final int CALLBACK_CAP_CHANGED = BASE + 6;
+ /** @hide */
+ public static final int CALLBACK_IP_CHANGED = BASE + 7;
+ /** @hide obj = NetworkCapabilities, arg1 = seq number */
+ private static final int EXPIRE_LEGACY_REQUEST = BASE + 8;
+ /** @hide */
+ public static final int CALLBACK_SUSPENDED = BASE + 9;
+ /** @hide */
+ public static final int CALLBACK_RESUMED = BASE + 10;
+ /** @hide */
+ public static final int CALLBACK_BLK_CHANGED = BASE + 11;
+
+ /** @hide */
+ public static String getCallbackName(int whichCallback) {
+ switch (whichCallback) {
+ case CALLBACK_PRECHECK: return "CALLBACK_PRECHECK";
+ case CALLBACK_AVAILABLE: return "CALLBACK_AVAILABLE";
+ case CALLBACK_LOSING: return "CALLBACK_LOSING";
+ case CALLBACK_LOST: return "CALLBACK_LOST";
+ case CALLBACK_UNAVAIL: return "CALLBACK_UNAVAIL";
+ case CALLBACK_CAP_CHANGED: return "CALLBACK_CAP_CHANGED";
+ case CALLBACK_IP_CHANGED: return "CALLBACK_IP_CHANGED";
+ case EXPIRE_LEGACY_REQUEST: return "EXPIRE_LEGACY_REQUEST";
+ case CALLBACK_SUSPENDED: return "CALLBACK_SUSPENDED";
+ case CALLBACK_RESUMED: return "CALLBACK_RESUMED";
+ case CALLBACK_BLK_CHANGED: return "CALLBACK_BLK_CHANGED";
+ default:
+ return Integer.toString(whichCallback);
+ }
+ }
+
+ private class CallbackHandler extends Handler {
+ private static final String TAG = "ConnectivityManager.CallbackHandler";
+ private static final boolean DBG = false;
+
+ CallbackHandler(Looper looper) {
+ super(looper);
+ }
+
+ CallbackHandler(Handler handler) {
+ this(Preconditions.checkNotNull(handler, "Handler cannot be null.").getLooper());
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ if (message.what == EXPIRE_LEGACY_REQUEST) {
+ expireRequest((NetworkCapabilities) message.obj, message.arg1);
+ return;
+ }
+
+ final NetworkRequest request = getObject(message, NetworkRequest.class);
+ final Network network = getObject(message, Network.class);
+ final NetworkCallback callback;
+ synchronized (sCallbacks) {
+ callback = sCallbacks.get(request);
+ if (callback == null) {
+ Log.w(TAG,
+ "callback not found for " + getCallbackName(message.what) + " message");
+ return;
+ }
+ if (message.what == CALLBACK_UNAVAIL) {
+ sCallbacks.remove(request);
+ callback.networkRequest = ALREADY_UNREGISTERED;
+ }
+ }
+ if (DBG) {
+ Log.d(TAG, getCallbackName(message.what) + " for network " + network);
+ }
+
+ switch (message.what) {
+ case CALLBACK_PRECHECK: {
+ callback.onPreCheck(network);
+ break;
+ }
+ case CALLBACK_AVAILABLE: {
+ NetworkCapabilities cap = getObject(message, NetworkCapabilities.class);
+ LinkProperties lp = getObject(message, LinkProperties.class);
+ callback.onAvailable(network, cap, lp, message.arg1 != 0);
+ break;
+ }
+ case CALLBACK_LOSING: {
+ callback.onLosing(network, message.arg1);
+ break;
+ }
+ case CALLBACK_LOST: {
+ callback.onLost(network);
+ break;
+ }
+ case CALLBACK_UNAVAIL: {
+ callback.onUnavailable();
+ break;
+ }
+ case CALLBACK_CAP_CHANGED: {
+ NetworkCapabilities cap = getObject(message, NetworkCapabilities.class);
+ callback.onCapabilitiesChanged(network, cap);
+ break;
+ }
+ case CALLBACK_IP_CHANGED: {
+ LinkProperties lp = getObject(message, LinkProperties.class);
+ callback.onLinkPropertiesChanged(network, lp);
+ break;
+ }
+ case CALLBACK_SUSPENDED: {
+ callback.onNetworkSuspended(network);
+ break;
+ }
+ case CALLBACK_RESUMED: {
+ callback.onNetworkResumed(network);
+ break;
+ }
+ case CALLBACK_BLK_CHANGED: {
+ boolean blocked = message.arg1 != 0;
+ callback.onBlockedStatusChanged(network, blocked);
+ }
+ }
+ }
+
+ private <T> T getObject(Message msg, Class<T> c) {
+ return (T) msg.getData().getParcelable(c.getSimpleName());
+ }
+ }
+
+ private CallbackHandler getDefaultHandler() {
+ synchronized (sCallbacks) {
+ if (sCallbackHandler == null) {
+ sCallbackHandler = new CallbackHandler(ConnectivityThread.getInstanceLooper());
+ }
+ return sCallbackHandler;
+ }
+ }
+
+ private static final HashMap<NetworkRequest, NetworkCallback> sCallbacks = new HashMap<>();
+ private static CallbackHandler sCallbackHandler;
+
+ private static final int LISTEN = 1;
+ private static final int REQUEST = 2;
+
+ private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback,
+ int timeoutMs, int action, int legacyType, CallbackHandler handler) {
+ printStackTrace();
+ checkCallbackNotNull(callback);
+ Preconditions.checkArgument(action == REQUEST || need != null, "null NetworkCapabilities");
+ final NetworkRequest request;
+ try {
+ synchronized(sCallbacks) {
+ if (callback.networkRequest != null
+ && callback.networkRequest != ALREADY_UNREGISTERED) {
+ // TODO: throw exception instead and enforce 1:1 mapping of callbacks
+ // and requests (http://b/20701525).
+ Log.e(TAG, "NetworkCallback was already registered");
+ }
+ Messenger messenger = new Messenger(handler);
+ Binder binder = new Binder();
+ if (action == LISTEN) {
+ request = mService.listenForNetwork(need, messenger, binder);
+ } else {
+ request = mService.requestNetwork(
+ need, messenger, timeoutMs, binder, legacyType);
+ }
+ if (request != null) {
+ sCallbacks.put(request, callback);
+ }
+ callback.networkRequest = request;
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw convertServiceException(e);
+ }
+ return request;
+ }
+
+ /**
+ * Helper function to request a network with a particular legacy type.
+ *
+ * This is temporarily public @hide so it can be called by system code that uses the
+ * NetworkRequest API to request networks but relies on CONNECTIVITY_ACTION broadcasts for
+ * instead network notifications.
+ *
+ * TODO: update said system code to rely on NetworkCallbacks and make this method private.
+ *
+ * @hide
+ */
+ public void requestNetwork(@NonNull NetworkRequest request,
+ @NonNull NetworkCallback networkCallback, int timeoutMs, int legacyType,
+ @NonNull Handler handler) {
+ CallbackHandler cbHandler = new CallbackHandler(handler);
+ NetworkCapabilities nc = request.networkCapabilities;
+ sendRequestForNetwork(nc, networkCallback, timeoutMs, REQUEST, legacyType, cbHandler);
+ }
+
+ /**
+ * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}.
+ *
+ * This {@link NetworkRequest} will live until released via
+ * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits. A
+ * version of the method which takes a timeout is
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)}.
+ * Status of the request can be followed by listening to the various
+ * callbacks described in {@link NetworkCallback}. The {@link Network}
+ * can be used to direct traffic to the network.
+ * <p>It is presently unsupported to request a network with mutable
+ * {@link NetworkCapabilities} such as
+ * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or
+ * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}
+ * as these {@code NetworkCapabilities} represent states that a particular
+ * network may never attain, and whether a network will attain these states
+ * is unknown prior to bringing up the network so the framework does not
+ * know how to go about satisfing a request with these capabilities.
+ *
+ * <p>This method requires the caller to hold either the
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+ * or the ability to modify system settings as determined by
+ * {@link android.provider.Settings.System#canWrite}.</p>
+ *
+ * @param request {@link NetworkRequest} describing this request.
+ * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
+ * the callback must not be shared - it uniquely specifies this request.
+ * The callback is invoked on the default internal Handler.
+ * @throws IllegalArgumentException if {@code request} contains invalid network capabilities.
+ * @throws SecurityException if missing the appropriate permissions.
+ * @throws RuntimeException if request limit per UID is exceeded.
+ */
+ public void requestNetwork(@NonNull NetworkRequest request,
+ @NonNull NetworkCallback networkCallback) {
+ requestNetwork(request, networkCallback, getDefaultHandler());
+ }
+
+ /**
+ * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}.
+ *
+ * This {@link NetworkRequest} will live until released via
+ * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits. A
+ * version of the method which takes a timeout is
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)}.
+ * Status of the request can be followed by listening to the various
+ * callbacks described in {@link NetworkCallback}. The {@link Network}
+ * can be used to direct traffic to the network.
+ * <p>It is presently unsupported to request a network with mutable
+ * {@link NetworkCapabilities} such as
+ * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or
+ * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}
+ * as these {@code NetworkCapabilities} represent states that a particular
+ * network may never attain, and whether a network will attain these states
+ * is unknown prior to bringing up the network so the framework does not
+ * know how to go about satisfying a request with these capabilities.
+ *
+ * <p>This method requires the caller to hold either the
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+ * or the ability to modify system settings as determined by
+ * {@link android.provider.Settings.System#canWrite}.</p>
+ *
+ * @param request {@link NetworkRequest} describing this request.
+ * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
+ * the callback must not be shared - it uniquely specifies this request.
+ * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+ * @throws IllegalArgumentException if {@code request} contains invalid network capabilities.
+ * @throws SecurityException if missing the appropriate permissions.
+ * @throws RuntimeException if request limit per UID is exceeded.
+ */
+ public void requestNetwork(@NonNull NetworkRequest request,
+ @NonNull NetworkCallback networkCallback, @NonNull Handler handler) {
+ int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
+ CallbackHandler cbHandler = new CallbackHandler(handler);
+ requestNetwork(request, networkCallback, 0, legacyType, cbHandler);
+ }
+
+ /**
+ * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited
+ * by a timeout.
+ *
+ * This function behaves identically to the non-timed-out version
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, but if a suitable network
+ * is not found within the given time (in milliseconds) the
+ * {@link NetworkCallback#onUnavailable()} callback is called. The request can still be
+ * released normally by calling {@link #unregisterNetworkCallback(NetworkCallback)} but does
+ * not have to be released if timed-out (it is automatically released). Unregistering a
+ * request that timed out is not an error.
+ *
+ * <p>Do not use this method to poll for the existence of specific networks (e.g. with a small
+ * timeout) - {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} is provided
+ * for that purpose. Calling this method will attempt to bring up the requested network.
+ *
+ * <p>This method requires the caller to hold either the
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+ * or the ability to modify system settings as determined by
+ * {@link android.provider.Settings.System#canWrite}.</p>
+ *
+ * @param request {@link NetworkRequest} describing this request.
+ * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
+ * the callback must not be shared - it uniquely specifies this request.
+ * @param timeoutMs The time in milliseconds to attempt looking for a suitable network
+ * before {@link NetworkCallback#onUnavailable()} is called. The timeout must
+ * be a positive value (i.e. >0).
+ * @throws IllegalArgumentException if {@code request} contains invalid network capabilities.
+ * @throws SecurityException if missing the appropriate permissions.
+ * @throws RuntimeException if request limit per UID is exceeded.
+ */
+ public void requestNetwork(@NonNull NetworkRequest request,
+ @NonNull NetworkCallback networkCallback, int timeoutMs) {
+ checkTimeout(timeoutMs);
+ int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
+ requestNetwork(request, networkCallback, timeoutMs, legacyType, getDefaultHandler());
+ }
+
+ /**
+ * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited
+ * by a timeout.
+ *
+ * This function behaves identically to the version without timeout, but if a suitable
+ * network is not found within the given time (in milliseconds) the
+ * {@link NetworkCallback#onUnavailable} callback is called. The request can still be
+ * released normally by calling {@link #unregisterNetworkCallback(NetworkCallback)} but does
+ * not have to be released if timed-out (it is automatically released). Unregistering a
+ * request that timed out is not an error.
+ *
+ * <p>Do not use this method to poll for the existence of specific networks (e.g. with a small
+ * timeout) - {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} is provided
+ * for that purpose. Calling this method will attempt to bring up the requested network.
+ *
+ * <p>This method requires the caller to hold either the
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+ * or the ability to modify system settings as determined by
+ * {@link android.provider.Settings.System#canWrite}.</p>
+ *
+ * @param request {@link NetworkRequest} describing this request.
+ * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
+ * the callback must not be shared - it uniquely specifies this request.
+ * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+ * @param timeoutMs The time in milliseconds to attempt looking for a suitable network
+ * before {@link NetworkCallback#onUnavailable} is called.
+ * @throws IllegalArgumentException if {@code request} contains invalid network capabilities.
+ * @throws SecurityException if missing the appropriate permissions.
+ * @throws RuntimeException if request limit per UID is exceeded.
+ */
+ public void requestNetwork(@NonNull NetworkRequest request,
+ @NonNull NetworkCallback networkCallback, @NonNull Handler handler, int timeoutMs) {
+ checkTimeout(timeoutMs);
+ int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
+ CallbackHandler cbHandler = new CallbackHandler(handler);
+ requestNetwork(request, networkCallback, timeoutMs, legacyType, cbHandler);
+ }
+
+ /**
+ * The lookup key for a {@link Network} object included with the intent after
+ * successfully finding a network for the applications request. Retrieve it with
+ * {@link android.content.Intent#getParcelableExtra(String)}.
+ * <p>
+ * Note that if you intend to invoke {@link Network#openConnection(java.net.URL)}
+ * then you must get a ConnectivityManager instance before doing so.
+ */
+ public static final String EXTRA_NETWORK = "android.net.extra.NETWORK";
+
+ /**
+ * The lookup key for a {@link NetworkRequest} object included with the intent after
+ * successfully finding a network for the applications request. Retrieve it with
+ * {@link android.content.Intent#getParcelableExtra(String)}.
+ */
+ public static final String EXTRA_NETWORK_REQUEST = "android.net.extra.NETWORK_REQUEST";
+
+
+ /**
+ * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}.
+ *
+ * This function behaves identically to the version that takes a NetworkCallback, but instead
+ * of {@link NetworkCallback} a {@link PendingIntent} is used. This means
+ * the request may outlive the calling application and get called back when a suitable
+ * network is found.
+ * <p>
+ * The operation is an Intent broadcast that goes to a broadcast receiver that
+ * you registered with {@link Context#registerReceiver} or through the
+ * <receiver> tag in an AndroidManifest.xml file
+ * <p>
+ * The operation Intent is delivered with two extras, a {@link Network} typed
+ * extra called {@link #EXTRA_NETWORK} and a {@link NetworkRequest}
+ * typed extra called {@link #EXTRA_NETWORK_REQUEST} containing
+ * the original requests parameters. It is important to create a new,
+ * {@link NetworkCallback} based request before completing the processing of the
+ * Intent to reserve the network or it will be released shortly after the Intent
+ * is processed.
+ * <p>
+ * If there is already a request for this Intent registered (with the equality of
+ * two Intents defined by {@link Intent#filterEquals}), then it will be removed and
+ * replaced by this one, effectively releasing the previous {@link NetworkRequest}.
+ * <p>
+ * The request may be released normally by calling
+ * {@link #releaseNetworkRequest(android.app.PendingIntent)}.
+ * <p>It is presently unsupported to request a network with either
+ * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or
+ * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}
+ * as these {@code NetworkCapabilities} represent states that a particular
+ * network may never attain, and whether a network will attain these states
+ * is unknown prior to bringing up the network so the framework does not
+ * know how to go about satisfying a request with these capabilities.
+ *
+ * <p>This method requires the caller to hold either the
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
+ * or the ability to modify system settings as determined by
+ * {@link android.provider.Settings.System#canWrite}.</p>
+ *
+ * @param request {@link NetworkRequest} describing this request.
+ * @param operation Action to perform when the network is available (corresponds
+ * to the {@link NetworkCallback#onAvailable} call. Typically
+ * comes from {@link PendingIntent#getBroadcast}. Cannot be null.
+ * @throws IllegalArgumentException if {@code request} contains invalid network capabilities.
+ * @throws SecurityException if missing the appropriate permissions.
+ * @throws RuntimeException if request limit per UID is exceeded.
+ */
+ public void requestNetwork(@NonNull NetworkRequest request,
+ @NonNull PendingIntent operation) {
+ printStackTrace();
+ checkPendingIntentNotNull(operation);
+ try {
+ mService.pendingRequestForNetwork(request.networkCapabilities, operation);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw convertServiceException(e);
+ }
+ }
+
+ /**
+ * Removes a request made via {@link #requestNetwork(NetworkRequest, android.app.PendingIntent)}
+ * <p>
+ * This method has the same behavior as
+ * {@link #unregisterNetworkCallback(android.app.PendingIntent)} with respect to
+ * releasing network resources and disconnecting.
+ *
+ * @param operation A PendingIntent equal (as defined by {@link Intent#filterEquals}) to the
+ * PendingIntent passed to
+ * {@link #requestNetwork(NetworkRequest, android.app.PendingIntent)} with the
+ * corresponding NetworkRequest you'd like to remove. Cannot be null.
+ */
+ public void releaseNetworkRequest(@NonNull PendingIntent operation) {
+ printStackTrace();
+ checkPendingIntentNotNull(operation);
+ try {
+ mService.releasePendingNetworkRequest(operation);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private static void checkPendingIntentNotNull(PendingIntent intent) {
+ Preconditions.checkNotNull(intent, "PendingIntent cannot be null.");
+ }
+
+ private static void checkCallbackNotNull(NetworkCallback callback) {
+ Preconditions.checkNotNull(callback, "null NetworkCallback");
+ }
+
+ private static void checkTimeout(int timeoutMs) {
+ Preconditions.checkArgumentPositive(timeoutMs, "timeoutMs must be strictly positive.");
+ }
+
+ /**
+ * Registers to receive notifications about all networks which satisfy the given
+ * {@link NetworkRequest}. The callbacks will continue to be called until
+ * either the application exits or {@link #unregisterNetworkCallback(NetworkCallback)} is
+ * called.
+ *
+ * @param request {@link NetworkRequest} describing this request.
+ * @param networkCallback The {@link NetworkCallback} that the system will call as suitable
+ * networks change state.
+ * The callback is invoked on the default internal Handler.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ public void registerNetworkCallback(@NonNull NetworkRequest request,
+ @NonNull NetworkCallback networkCallback) {
+ registerNetworkCallback(request, networkCallback, getDefaultHandler());
+ }
+
+ /**
+ * Registers to receive notifications about all networks which satisfy the given
+ * {@link NetworkRequest}. The callbacks will continue to be called until
+ * either the application exits or {@link #unregisterNetworkCallback(NetworkCallback)} is
+ * called.
+ *
+ * @param request {@link NetworkRequest} describing this request.
+ * @param networkCallback The {@link NetworkCallback} that the system will call as suitable
+ * networks change state.
+ * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ public void registerNetworkCallback(@NonNull NetworkRequest request,
+ @NonNull NetworkCallback networkCallback, @NonNull Handler handler) {
+ CallbackHandler cbHandler = new CallbackHandler(handler);
+ NetworkCapabilities nc = request.networkCapabilities;
+ sendRequestForNetwork(nc, networkCallback, 0, LISTEN, TYPE_NONE, cbHandler);
+ }
+
+ /**
+ * Registers a PendingIntent to be sent when a network is available which satisfies the given
+ * {@link NetworkRequest}.
+ *
+ * This function behaves identically to the version that takes a NetworkCallback, but instead
+ * of {@link NetworkCallback} a {@link PendingIntent} is used. This means
+ * the request may outlive the calling application and get called back when a suitable
+ * network is found.
+ * <p>
+ * The operation is an Intent broadcast that goes to a broadcast receiver that
+ * you registered with {@link Context#registerReceiver} or through the
+ * <receiver> tag in an AndroidManifest.xml file
+ * <p>
+ * The operation Intent is delivered with two extras, a {@link Network} typed
+ * extra called {@link #EXTRA_NETWORK} and a {@link NetworkRequest}
+ * typed extra called {@link #EXTRA_NETWORK_REQUEST} containing
+ * the original requests parameters.
+ * <p>
+ * If there is already a request for this Intent registered (with the equality of
+ * two Intents defined by {@link Intent#filterEquals}), then it will be removed and
+ * replaced by this one, effectively releasing the previous {@link NetworkRequest}.
+ * <p>
+ * The request may be released normally by calling
+ * {@link #unregisterNetworkCallback(android.app.PendingIntent)}.
+ * @param request {@link NetworkRequest} describing this request.
+ * @param operation Action to perform when the network is available (corresponds
+ * to the {@link NetworkCallback#onAvailable} call. Typically
+ * comes from {@link PendingIntent#getBroadcast}. Cannot be null.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ public void registerNetworkCallback(@NonNull NetworkRequest request,
+ @NonNull PendingIntent operation) {
+ printStackTrace();
+ checkPendingIntentNotNull(operation);
+ try {
+ mService.pendingListenForNetwork(request.networkCapabilities, operation);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw convertServiceException(e);
+ }
+ }
+
+ /**
+ * Registers to receive notifications about changes in the system default network. The callbacks
+ * will continue to be called until either the application exits or
+ * {@link #unregisterNetworkCallback(NetworkCallback)} is called.
+ *
+ * @param networkCallback The {@link NetworkCallback} that the system will call as the
+ * system default network changes.
+ * The callback is invoked on the default internal Handler.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback) {
+ registerDefaultNetworkCallback(networkCallback, getDefaultHandler());
+ }
+
+ /**
+ * Registers to receive notifications about changes in the system default network. The callbacks
+ * will continue to be called until either the application exits or
+ * {@link #unregisterNetworkCallback(NetworkCallback)} is called.
+ *
+ * @param networkCallback The {@link NetworkCallback} that the system will call as the
+ * system default network changes.
+ * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback,
+ @NonNull Handler handler) {
+ // This works because if the NetworkCapabilities are null,
+ // ConnectivityService takes them from the default request.
+ //
+ // Since the capabilities are exactly the same as the default request's
+ // capabilities, this request is guaranteed, at all times, to be
+ // satisfied by the same network, if any, that satisfies the default
+ // request, i.e., the system default network.
+ CallbackHandler cbHandler = new CallbackHandler(handler);
+ sendRequestForNetwork(null /* NetworkCapabilities need */, networkCallback, 0,
+ REQUEST, TYPE_NONE, cbHandler);
+ }
+
+ /**
+ * Requests bandwidth update for a given {@link Network} and returns whether the update request
+ * is accepted by ConnectivityService. Once accepted, ConnectivityService will poll underlying
+ * network connection for updated bandwidth information. The caller will be notified via
+ * {@link ConnectivityManager.NetworkCallback} if there is an update. Notice that this
+ * method assumes that the caller has previously called
+ * {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} to listen for network
+ * changes.
+ *
+ * @param network {@link Network} specifying which network you're interested.
+ * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid.
+ */
+ public boolean requestBandwidthUpdate(@NonNull Network network) {
+ try {
+ return mService.requestBandwidthUpdate(network);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregisters a {@code NetworkCallback} and possibly releases networks originating from
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)} and
+ * {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} calls.
+ * If the given {@code NetworkCallback} had previously been used with
+ * {@code #requestNetwork}, any networks that had been connected to only to satisfy that request
+ * will be disconnected.
+ *
+ * Notifications that would have triggered that {@code NetworkCallback} will immediately stop
+ * triggering it as soon as this call returns.
+ *
+ * @param networkCallback The {@link NetworkCallback} used when making the request.
+ */
+ public void unregisterNetworkCallback(@NonNull NetworkCallback networkCallback) {
+ printStackTrace();
+ checkCallbackNotNull(networkCallback);
+ final List<NetworkRequest> reqs = new ArrayList<>();
+ // Find all requests associated to this callback and stop callback triggers immediately.
+ // Callback is reusable immediately. http://b/20701525, http://b/35921499.
+ synchronized (sCallbacks) {
+ Preconditions.checkArgument(networkCallback.networkRequest != null,
+ "NetworkCallback was not registered");
+ if (networkCallback.networkRequest == ALREADY_UNREGISTERED) {
+ Log.d(TAG, "NetworkCallback was already unregistered");
+ return;
+ }
+ for (Map.Entry<NetworkRequest, NetworkCallback> e : sCallbacks.entrySet()) {
+ if (e.getValue() == networkCallback) {
+ reqs.add(e.getKey());
+ }
+ }
+ // TODO: throw exception if callback was registered more than once (http://b/20701525).
+ for (NetworkRequest r : reqs) {
+ try {
+ mService.releaseNetworkRequest(r);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ // Only remove mapping if rpc was successful.
+ sCallbacks.remove(r);
+ }
+ networkCallback.networkRequest = ALREADY_UNREGISTERED;
+ }
+ }
+
+ /**
+ * Unregisters a callback previously registered via
+ * {@link #registerNetworkCallback(NetworkRequest, android.app.PendingIntent)}.
+ *
+ * @param operation A PendingIntent equal (as defined by {@link Intent#filterEquals}) to the
+ * PendingIntent passed to
+ * {@link #registerNetworkCallback(NetworkRequest, android.app.PendingIntent)}.
+ * Cannot be null.
+ */
+ public void unregisterNetworkCallback(@NonNull PendingIntent operation) {
+ checkPendingIntentNotNull(operation);
+ releaseNetworkRequest(operation);
+ }
+
+ /**
+ * Informs the system whether it should switch to {@code network} regardless of whether it is
+ * validated or not. If {@code accept} is true, and the network was explicitly selected by the
+ * user (e.g., by selecting a Wi-Fi network in the Settings app), then the network will become
+ * the system default network regardless of any other network that's currently connected. If
+ * {@code always} is true, then the choice is remembered, so that the next time the user
+ * connects to this network, the system will switch to it.
+ *
+ * @param network The network to accept.
+ * @param accept Whether to accept the network even if unvalidated.
+ * @param always Whether to remember this choice in the future.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
+ try {
+ mService.setAcceptUnvalidated(network, accept, always);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Informs the system whether it should consider the network as validated even if it only has
+ * partial connectivity. If {@code accept} is true, then the network will be considered as
+ * validated even if connectivity is only partial. If {@code always} is true, then the choice
+ * is remembered, so that the next time the user connects to this network, the system will
+ * switch to it.
+ *
+ * @param network The network to accept.
+ * @param accept Whether to consider the network as validated even if it has partial
+ * connectivity.
+ * @param always Whether to remember this choice in the future.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
+ public void setAcceptPartialConnectivity(Network network, boolean accept, boolean always) {
+ try {
+ mService.setAcceptPartialConnectivity(network, accept, always);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Informs the system to penalize {@code network}'s score when it becomes unvalidated. This is
+ * only meaningful if the system is configured not to penalize such networks, e.g., if the
+ * {@code config_networkAvoidBadWifi} configuration variable is set to 0 and the {@code
+ * NETWORK_AVOID_BAD_WIFI setting is unset}.
+ *
+ * @param network The network to accept.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public void setAvoidUnvalidated(Network network) {
+ try {
+ mService.setAvoidUnvalidated(network);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Requests that the system open the captive portal app on the specified network.
+ *
+ * @param network The network to log into.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL)
+ public void startCaptivePortalApp(Network network) {
+ try {
+ mService.startCaptivePortalApp(network);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Requests that the system open the captive portal app with the specified extras.
+ *
+ * <p>This endpoint is exclusively for use by the NetworkStack and is protected by the
+ * corresponding permission.
+ * @param network Network on which the captive portal was detected.
+ * @param appExtras Extras to include in the app start intent.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
+ public void startCaptivePortalApp(@NonNull Network network, @NonNull Bundle appExtras) {
+ try {
+ mService.startCaptivePortalAppInternal(network, appExtras);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Determine whether the device is configured to avoid bad wifi.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK})
+ public boolean shouldAvoidBadWifi() {
+ try {
+ return mService.shouldAvoidBadWifi();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * It is acceptable to briefly use multipath data to provide seamless connectivity for
+ * time-sensitive user-facing operations when the system default network is temporarily
+ * unresponsive. The amount of data should be limited (less than one megabyte for every call to
+ * this method), and the operation should be infrequent to ensure that data usage is limited.
+ *
+ * An example of such an operation might be a time-sensitive foreground activity, such as a
+ * voice command, that the user is performing while walking out of range of a Wi-Fi network.
+ */
+ public static final int MULTIPATH_PREFERENCE_HANDOVER = 1 << 0;
+
+ /**
+ * It is acceptable to use small amounts of multipath data on an ongoing basis to provide
+ * a backup channel for traffic that is primarily going over another network.
+ *
+ * An example might be maintaining backup connections to peers or servers for the purpose of
+ * fast fallback if the default network is temporarily unresponsive or disconnects. The traffic
+ * on backup paths should be negligible compared to the traffic on the main path.
+ */
+ public static final int MULTIPATH_PREFERENCE_RELIABILITY = 1 << 1;
+
+ /**
+ * It is acceptable to use metered data to improve network latency and performance.
+ */
+ public static final int MULTIPATH_PREFERENCE_PERFORMANCE = 1 << 2;
+
+ /**
+ * Return value to use for unmetered networks. On such networks we currently set all the flags
+ * to true.
+ * @hide
+ */
+ public static final int MULTIPATH_PREFERENCE_UNMETERED =
+ MULTIPATH_PREFERENCE_HANDOVER |
+ MULTIPATH_PREFERENCE_RELIABILITY |
+ MULTIPATH_PREFERENCE_PERFORMANCE;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, value = {
+ MULTIPATH_PREFERENCE_HANDOVER,
+ MULTIPATH_PREFERENCE_RELIABILITY,
+ MULTIPATH_PREFERENCE_PERFORMANCE,
+ })
+ public @interface MultipathPreference {
+ }
+
+ /**
+ * Provides a hint to the calling application on whether it is desirable to use the
+ * multinetwork APIs (e.g., {@link Network#openConnection}, {@link Network#bindSocket}, etc.)
+ * for multipath data transfer on this network when it is not the system default network.
+ * Applications desiring to use multipath network protocols should call this method before
+ * each such operation.
+ *
+ * @param network The network on which the application desires to use multipath data.
+ * If {@code null}, this method will return the a preference that will generally
+ * apply to metered networks.
+ * @return a bitwise OR of zero or more of the {@code MULTIPATH_PREFERENCE_*} constants.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ public @MultipathPreference int getMultipathPreference(@Nullable Network network) {
+ try {
+ return mService.getMultipathPreference(network);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Resets all connectivity manager settings back to factory defaults.
+ * @hide
+ */
+ public void factoryReset() {
+ try {
+ mService.factoryReset();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Binds the current process to {@code network}. All Sockets created in the future
+ * (and not explicitly bound via a bound SocketFactory from
+ * {@link Network#getSocketFactory() Network.getSocketFactory()}) will be bound to
+ * {@code network}. All host name resolutions will be limited to {@code network} as well.
+ * Note that if {@code network} ever disconnects, all Sockets created in this way will cease to
+ * work and all host name resolutions will fail. This is by design so an application doesn't
+ * accidentally use Sockets it thinks are still bound to a particular {@link Network}.
+ * To clear binding pass {@code null} for {@code network}. Using individually bound
+ * Sockets created by Network.getSocketFactory().createSocket() and
+ * performing network-specific host name resolutions via
+ * {@link Network#getAllByName Network.getAllByName} is preferred to calling
+ * {@code bindProcessToNetwork}.
+ *
+ * @param network The {@link Network} to bind the current process to, or {@code null} to clear
+ * the current binding.
+ * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid.
+ */
+ public boolean bindProcessToNetwork(@Nullable Network network) {
+ // Forcing callers to call through non-static function ensures ConnectivityManager
+ // instantiated.
+ return setProcessDefaultNetwork(network);
+ }
+
+ /**
+ * Binds the current process to {@code network}. All Sockets created in the future
+ * (and not explicitly bound via a bound SocketFactory from
+ * {@link Network#getSocketFactory() Network.getSocketFactory()}) will be bound to
+ * {@code network}. All host name resolutions will be limited to {@code network} as well.
+ * Note that if {@code network} ever disconnects, all Sockets created in this way will cease to
+ * work and all host name resolutions will fail. This is by design so an application doesn't
+ * accidentally use Sockets it thinks are still bound to a particular {@link Network}.
+ * To clear binding pass {@code null} for {@code network}. Using individually bound
+ * Sockets created by Network.getSocketFactory().createSocket() and
+ * performing network-specific host name resolutions via
+ * {@link Network#getAllByName Network.getAllByName} is preferred to calling
+ * {@code setProcessDefaultNetwork}.
+ *
+ * @param network The {@link Network} to bind the current process to, or {@code null} to clear
+ * the current binding.
+ * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid.
+ * @deprecated This function can throw {@link IllegalStateException}. Use
+ * {@link #bindProcessToNetwork} instead. {@code bindProcessToNetwork}
+ * is a direct replacement.
+ */
+ @Deprecated
+ public static boolean setProcessDefaultNetwork(@Nullable Network network) {
+ int netId = (network == null) ? NETID_UNSET : network.netId;
+ boolean isSameNetId = (netId == NetworkUtils.getBoundNetworkForProcess());
+
+ if (netId != NETID_UNSET) {
+ netId = network.getNetIdForResolv();
+ }
+
+ if (!NetworkUtils.bindProcessToNetwork(netId)) {
+ return false;
+ }
+
+ if (!isSameNetId) {
+ // Set HTTP proxy system properties to match network.
+ // TODO: Deprecate this static method and replace it with a non-static version.
+ try {
+ Proxy.setHttpProxySystemProperty(getInstance().getDefaultProxy());
+ } catch (SecurityException e) {
+ // The process doesn't have ACCESS_NETWORK_STATE, so we can't fetch the proxy.
+ Log.e(TAG, "Can't set proxy properties", e);
+ }
+ // Must flush DNS cache as new network may have different DNS resolutions.
+ InetAddress.clearDnsCache();
+ // Must flush socket pool as idle sockets will be bound to previous network and may
+ // cause subsequent fetches to be performed on old network.
+ NetworkEventDispatcher.getInstance().onNetworkConfigurationChanged();
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the {@link Network} currently bound to this process via
+ * {@link #bindProcessToNetwork}, or {@code null} if no {@link Network} is explicitly bound.
+ *
+ * @return {@code Network} to which this process is bound, or {@code null}.
+ */
+ @Nullable
+ public Network getBoundNetworkForProcess() {
+ // Forcing callers to call thru non-static function ensures ConnectivityManager
+ // instantiated.
+ return getProcessDefaultNetwork();
+ }
+
+ /**
+ * Returns the {@link Network} currently bound to this process via
+ * {@link #bindProcessToNetwork}, or {@code null} if no {@link Network} is explicitly bound.
+ *
+ * @return {@code Network} to which this process is bound, or {@code null}.
+ * @deprecated Using this function can lead to other functions throwing
+ * {@link IllegalStateException}. Use {@link #getBoundNetworkForProcess} instead.
+ * {@code getBoundNetworkForProcess} is a direct replacement.
+ */
+ @Deprecated
+ @Nullable
+ public static Network getProcessDefaultNetwork() {
+ int netId = NetworkUtils.getBoundNetworkForProcess();
+ if (netId == NETID_UNSET) return null;
+ return new Network(netId);
+ }
+
+ private void unsupportedStartingFrom(int version) {
+ if (Process.myUid() == Process.SYSTEM_UID) {
+ // The getApplicationInfo() call we make below is not supported in system context. Let
+ // the call through here, and rely on the fact that ConnectivityService will refuse to
+ // allow the system to use these APIs anyway.
+ return;
+ }
+
+ if (mContext.getApplicationInfo().targetSdkVersion >= version) {
+ throw new UnsupportedOperationException(
+ "This method is not supported in target SDK version " + version + " and above");
+ }
+ }
+
+ // Checks whether the calling app can use the legacy routing API (startUsingNetworkFeature,
+ // stopUsingNetworkFeature, requestRouteToHost), and if not throw UnsupportedOperationException.
+ // TODO: convert the existing system users (Tethering, GnssLocationProvider) to the new APIs and
+ // remove these exemptions. Note that this check is not secure, and apps can still access these
+ // functions by accessing ConnectivityService directly. However, it should be clear that doing
+ // so is unsupported and may break in the future. http://b/22728205
+ private void checkLegacyRoutingApiAccess() {
+ unsupportedStartingFrom(VERSION_CODES.M);
+ }
+
+ /**
+ * Binds host resolutions performed by this process to {@code network}.
+ * {@link #bindProcessToNetwork} takes precedence over this setting.
+ *
+ * @param network The {@link Network} to bind host resolutions from the current process to, or
+ * {@code null} to clear the current binding.
+ * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid.
+ * @hide
+ * @deprecated This is strictly for legacy usage to support {@link #startUsingNetworkFeature}.
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public static boolean setProcessDefaultNetworkForHostResolution(Network network) {
+ return NetworkUtils.bindProcessToNetworkForHostResolution(
+ (network == null) ? NETID_UNSET : network.getNetIdForResolv());
+ }
+
+ /**
+ * Device is not restricting metered network activity while application is running on
+ * background.
+ */
+ public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1;
+
+ /**
+ * Device is restricting metered network activity while application is running on background,
+ * but application is allowed to bypass it.
+ * <p>
+ * In this state, application should take action to mitigate metered network access.
+ * For example, a music streaming application should switch to a low-bandwidth bitrate.
+ */
+ public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2;
+
+ /**
+ * Device is restricting metered network activity while application is running on background.
+ * <p>
+ * In this state, application should not try to use the network while running on background,
+ * because it would be denied.
+ */
+ public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3;
+
+ /**
+ * A change in the background metered network activity restriction has occurred.
+ * <p>
+ * Applications should call {@link #getRestrictBackgroundStatus()} to check if the restriction
+ * applies to them.
+ * <p>
+ * This is only sent to registered receivers, not manifest receivers.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_RESTRICT_BACKGROUND_CHANGED =
+ "android.net.conn.RESTRICT_BACKGROUND_CHANGED";
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = false, value = {
+ RESTRICT_BACKGROUND_STATUS_DISABLED,
+ RESTRICT_BACKGROUND_STATUS_WHITELISTED,
+ RESTRICT_BACKGROUND_STATUS_ENABLED,
+ })
+ public @interface RestrictBackgroundStatus {
+ }
+
+ private INetworkPolicyManager getNetworkPolicyManager() {
+ synchronized (this) {
+ if (mNPManager != null) {
+ return mNPManager;
+ }
+ mNPManager = INetworkPolicyManager.Stub.asInterface(ServiceManager
+ .getService(Context.NETWORK_POLICY_SERVICE));
+ return mNPManager;
+ }
+ }
+
+ /**
+ * Determines if the calling application is subject to metered network restrictions while
+ * running on background.
+ *
+ * @return {@link #RESTRICT_BACKGROUND_STATUS_DISABLED},
+ * {@link #RESTRICT_BACKGROUND_STATUS_ENABLED},
+ * or {@link #RESTRICT_BACKGROUND_STATUS_WHITELISTED}
+ */
+ public @RestrictBackgroundStatus int getRestrictBackgroundStatus() {
+ try {
+ return getNetworkPolicyManager().getRestrictBackgroundByCaller();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * The network watchlist is a list of domains and IP addresses that are associated with
+ * potentially harmful apps. This method returns the SHA-256 of the watchlist config file
+ * currently used by the system for validation purposes.
+ *
+ * @return Hash of network watchlist config file. Null if config does not exist.
+ */
+ @Nullable
+ public byte[] getNetworkWatchlistConfigHash() {
+ try {
+ return mService.getNetworkWatchlistConfigHash();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to get watchlist config hash");
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the {@code uid} of the owner of a network connection.
+ *
+ * @param protocol The protocol of the connection. Only {@code IPPROTO_TCP} and
+ * {@code IPPROTO_UDP} currently supported.
+ * @param local The local {@link InetSocketAddress} of a connection.
+ * @param remote The remote {@link InetSocketAddress} of a connection.
+ *
+ * @return {@code uid} if the connection is found and the app has permission to observe it
+ * (e.g., if it is associated with the calling VPN app's tunnel) or
+ * {@link android.os.Process#INVALID_UID} if the connection is not found.
+ * Throws {@link SecurityException} if the caller is not the active VPN for the current user.
+ * Throws {@link IllegalArgumentException} if an unsupported protocol is requested.
+ */
+ public int getConnectionOwnerUid(int protocol, @NonNull InetSocketAddress local,
+ @NonNull InetSocketAddress remote) {
+ ConnectionInfo connectionInfo = new ConnectionInfo(protocol, local, remote);
+ try {
+ return mService.getConnectionOwnerUid(connectionInfo);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private void printStackTrace() {
+ if (DEBUG) {
+ final StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
+ final StringBuffer sb = new StringBuffer();
+ for (int i = 3; i < callStack.length; i++) {
+ final String stackTrace = callStack[i].toString();
+ if (stackTrace == null || stackTrace.contains("android.os")) {
+ break;
+ }
+ sb.append(" [").append(stackTrace).append("]");
+ }
+ Log.d(TAG, "StackLog:" + sb.toString());
+ }
+ }
+}
diff --git a/android/net/ConnectivityMetricsEvent.java b/android/net/ConnectivityMetricsEvent.java
new file mode 100644
index 0000000..522add1
--- /dev/null
+++ b/android/net/ConnectivityMetricsEvent.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.BitUtils;
+
+/**
+ * Represents a core networking event defined in package android.net.metrics.
+ * Logged by IpConnectivityLog and managed by ConnectivityMetrics service.
+ * {@hide}
+ * */
+public final class ConnectivityMetricsEvent implements Parcelable {
+
+ /** Time when this event was collected, as returned by System.currentTimeMillis(). */
+ public long timestamp;
+ /** Transports of the network associated with the event, as defined in NetworkCapabilities. */
+ public long transports;
+ /** Network id of the network associated with the event, or 0 if unspecified. */
+ public int netId;
+ /** Name of the network interface associated with the event, or null if unspecified. */
+ public String ifname;
+ /** Opaque event-specific data. */
+ public Parcelable data;
+
+ public ConnectivityMetricsEvent() {
+ }
+
+ private ConnectivityMetricsEvent(Parcel in) {
+ timestamp = in.readLong();
+ transports = in.readLong();
+ netId = in.readInt();
+ ifname = in.readString();
+ data = in.readParcelable(null);
+ }
+
+ /** Implement the Parcelable interface */
+ public static final @android.annotation.NonNull Parcelable.Creator<ConnectivityMetricsEvent> CREATOR
+ = new Parcelable.Creator<ConnectivityMetricsEvent> (){
+ public ConnectivityMetricsEvent createFromParcel(Parcel source) {
+ return new ConnectivityMetricsEvent(source);
+ }
+
+ public ConnectivityMetricsEvent[] newArray(int size) {
+ return new ConnectivityMetricsEvent[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(timestamp);
+ dest.writeLong(transports);
+ dest.writeInt(netId);
+ dest.writeString(ifname);
+ dest.writeParcelable(data, 0);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buffer = new StringBuilder("ConnectivityMetricsEvent(");
+ buffer.append(String.format("%tT.%tL", timestamp, timestamp));
+ if (netId != 0) {
+ buffer.append(", ").append("netId=").append(netId);
+ }
+ if (ifname != null) {
+ buffer.append(", ").append(ifname);
+ }
+ for (int t : BitUtils.unpackBits(transports)) {
+ buffer.append(", ").append(NetworkCapabilities.transportNameOf(t));
+ }
+ buffer.append("): ").append(data.toString());
+ return buffer.toString();
+ }
+}
diff --git a/android/net/ConnectivityThread.java b/android/net/ConnectivityThread.java
new file mode 100644
index 0000000..0b218e7
--- /dev/null
+++ b/android/net/ConnectivityThread.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2016 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;
+
+import android.os.HandlerThread;
+import android.os.Looper;
+
+/**
+ * Shared singleton connectivity thread for the system. This is a thread for
+ * connectivity operations such as AsyncChannel connections to system services.
+ * Various connectivity manager objects can use this singleton as a common
+ * resource for their handlers instead of creating separate threads of their own.
+ * @hide
+ */
+public final class ConnectivityThread extends HandlerThread {
+
+ // A class implementing the lazy holder idiom: the unique static instance
+ // of ConnectivityThread is instantiated in a thread-safe way (guaranteed by
+ // the language specs) the first time that Singleton is referenced in get()
+ // or getInstanceLooper().
+ private static class Singleton {
+ private static final ConnectivityThread INSTANCE = createInstance();
+ }
+
+ private ConnectivityThread() {
+ super("ConnectivityThread");
+ }
+
+ private static ConnectivityThread createInstance() {
+ ConnectivityThread t = new ConnectivityThread();
+ t.start();
+ return t;
+ }
+
+ public static ConnectivityThread get() {
+ return Singleton.INSTANCE;
+ }
+
+ public static Looper getInstanceLooper() {
+ return Singleton.INSTANCE.getLooper();
+ }
+}
diff --git a/android/net/Credentials.java b/android/net/Credentials.java
new file mode 100644
index 0000000..7f6cf9d
--- /dev/null
+++ b/android/net/Credentials.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2007 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;
+
+/**
+ * A class for representing UNIX credentials passed via ancillary data
+ * on UNIX domain sockets. See "man 7 unix" on a desktop linux distro.
+ */
+public class Credentials {
+ /** pid of process. root peers may lie. */
+ private final int pid;
+ /** uid of process. root peers may lie. */
+ private final int uid;
+ /** gid of process. root peers may lie. */
+ private final int gid;
+
+ public Credentials (int pid, int uid, int gid) {
+ this.pid = pid;
+ this.uid = uid;
+ this.gid = gid;
+ }
+
+ public int getPid() {
+ return pid;
+ }
+
+ public int getUid() {
+ return uid;
+ }
+
+ public int getGid() {
+ return gid;
+ }
+}
diff --git a/android/net/DataUsageRequest.java b/android/net/DataUsageRequest.java
new file mode 100644
index 0000000..0ac8f7e
--- /dev/null
+++ b/android/net/DataUsageRequest.java
@@ -0,0 +1,111 @@
+/**
+ * Copyright (C) 2016 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;
+
+import android.net.NetworkTemplate;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Defines a request to register a callbacks. Used to be notified on data usage via
+ * {@link android.app.usage.NetworkStatsManager#registerDataUsageCallback}.
+ * If no {@code uid}s are set, callbacks are restricted to device-owners,
+ * carrier-privileged apps, or system apps.
+ *
+ * @hide
+ */
+public final class DataUsageRequest implements Parcelable {
+
+ public static final String PARCELABLE_KEY = "DataUsageRequest";
+ public static final int REQUEST_ID_UNSET = 0;
+
+ /**
+ * Identifies the request. {@link DataUsageRequest}s should only be constructed by
+ * the Framework and it is used internally to identify the request.
+ */
+ public final int requestId;
+
+ /**
+ * {@link NetworkTemplate} describing the network to monitor.
+ */
+ public final NetworkTemplate template;
+
+ /**
+ * Threshold in bytes to be notified on.
+ */
+ public final long thresholdInBytes;
+
+ public DataUsageRequest(int requestId, NetworkTemplate template, long thresholdInBytes) {
+ this.requestId = requestId;
+ this.template = template;
+ this.thresholdInBytes = thresholdInBytes;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(requestId);
+ dest.writeParcelable(template, flags);
+ dest.writeLong(thresholdInBytes);
+ }
+
+ public static final @android.annotation.NonNull Creator<DataUsageRequest> CREATOR =
+ new Creator<DataUsageRequest>() {
+ @Override
+ public DataUsageRequest createFromParcel(Parcel in) {
+ int requestId = in.readInt();
+ NetworkTemplate template = in.readParcelable(null);
+ long thresholdInBytes = in.readLong();
+ DataUsageRequest result = new DataUsageRequest(requestId, template,
+ thresholdInBytes);
+ return result;
+ }
+
+ @Override
+ public DataUsageRequest[] newArray(int size) {
+ return new DataUsageRequest[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "DataUsageRequest [ requestId=" + requestId
+ + ", networkTemplate=" + template
+ + ", thresholdInBytes=" + thresholdInBytes + " ]";
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof DataUsageRequest == false) return false;
+ DataUsageRequest that = (DataUsageRequest) obj;
+ return that.requestId == this.requestId
+ && Objects.equals(that.template, this.template)
+ && that.thresholdInBytes == this.thresholdInBytes;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(requestId, template, thresholdInBytes);
+ }
+
+}
diff --git a/android/net/DhcpInfo.java b/android/net/DhcpInfo.java
new file mode 100644
index 0000000..98bab44
--- /dev/null
+++ b/android/net/DhcpInfo.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+/**
+ * A simple object for retrieving the results of a DHCP request.
+ */
+public class DhcpInfo implements Parcelable {
+ public int ipAddress;
+ public int gateway;
+ public int netmask;
+ public int dns1;
+ public int dns2;
+ public int serverAddress;
+
+ public int leaseDuration;
+
+ public DhcpInfo() {
+ super();
+ }
+
+ /** copy constructor {@hide} */
+ public DhcpInfo(DhcpInfo source) {
+ if (source != null) {
+ ipAddress = source.ipAddress;
+ gateway = source.gateway;
+ netmask = source.netmask;
+ dns1 = source.dns1;
+ dns2 = source.dns2;
+ serverAddress = source.serverAddress;
+ leaseDuration = source.leaseDuration;
+ }
+ }
+
+ public String toString() {
+ StringBuffer str = new StringBuffer();
+
+ str.append("ipaddr "); putAddress(str, ipAddress);
+ str.append(" gateway "); putAddress(str, gateway);
+ str.append(" netmask "); putAddress(str, netmask);
+ str.append(" dns1 "); putAddress(str, dns1);
+ str.append(" dns2 "); putAddress(str, dns2);
+ str.append(" DHCP server "); putAddress(str, serverAddress);
+ str.append(" lease ").append(leaseDuration).append(" seconds");
+
+ return str.toString();
+ }
+
+ private static void putAddress(StringBuffer buf, int addr) {
+ buf.append(NetworkUtils.intToInetAddress(addr).getHostAddress());
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(ipAddress);
+ dest.writeInt(gateway);
+ dest.writeInt(netmask);
+ dest.writeInt(dns1);
+ dest.writeInt(dns2);
+ dest.writeInt(serverAddress);
+ dest.writeInt(leaseDuration);
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final @android.annotation.NonNull Creator<DhcpInfo> CREATOR =
+ new Creator<DhcpInfo>() {
+ public DhcpInfo createFromParcel(Parcel in) {
+ DhcpInfo info = new DhcpInfo();
+ info.ipAddress = in.readInt();
+ info.gateway = in.readInt();
+ info.netmask = in.readInt();
+ info.dns1 = in.readInt();
+ info.dns2 = in.readInt();
+ info.serverAddress = in.readInt();
+ info.leaseDuration = in.readInt();
+ return info;
+ }
+
+ public DhcpInfo[] newArray(int size) {
+ return new DhcpInfo[size];
+ }
+ };
+}
diff --git a/android/net/DhcpResults.java b/android/net/DhcpResults.java
new file mode 100644
index 0000000..97be51a
--- /dev/null
+++ b/android/net/DhcpResults.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2012 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;
+
+import android.annotation.UnsupportedAppUsage;
+import android.net.shared.InetAddressUtils;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A simple object for retrieving the results of a DHCP request.
+ * Optimized (attempted) for that jni interface
+ * TODO: remove this class and replace with other existing constructs
+ * @hide
+ */
+public final class DhcpResults implements Parcelable {
+ private static final String TAG = "DhcpResults";
+
+ @UnsupportedAppUsage
+ public LinkAddress ipAddress;
+
+ @UnsupportedAppUsage
+ public InetAddress gateway;
+
+ @UnsupportedAppUsage
+ public final ArrayList<InetAddress> dnsServers = new ArrayList<>();
+
+ @UnsupportedAppUsage
+ public String domains;
+
+ @UnsupportedAppUsage
+ public Inet4Address serverAddress;
+
+ /** Vendor specific information (from RFC 2132). */
+ @UnsupportedAppUsage
+ public String vendorInfo;
+
+ @UnsupportedAppUsage
+ public int leaseDuration;
+
+ /** Link MTU option. 0 means unset. */
+ @UnsupportedAppUsage
+ public int mtu;
+
+ public String serverHostName;
+
+ public DhcpResults() {
+ super();
+ }
+
+ /**
+ * Create a {@link StaticIpConfiguration} based on the DhcpResults.
+ */
+ public StaticIpConfiguration toStaticIpConfiguration() {
+ return new StaticIpConfiguration.Builder()
+ .setIpAddress(ipAddress)
+ .setGateway(gateway)
+ .setDnsServers(dnsServers)
+ .setDomains(domains)
+ .build();
+ }
+
+ public DhcpResults(StaticIpConfiguration source) {
+ if (source != null) {
+ ipAddress = source.getIpAddress();
+ gateway = source.getGateway();
+ dnsServers.addAll(source.getDnsServers());
+ domains = source.getDomains();
+ }
+ }
+
+ /** copy constructor */
+ public DhcpResults(DhcpResults source) {
+ this(source == null ? null : source.toStaticIpConfiguration());
+ if (source != null) {
+ serverAddress = source.serverAddress;
+ vendorInfo = source.vendorInfo;
+ leaseDuration = source.leaseDuration;
+ mtu = source.mtu;
+ serverHostName = source.serverHostName;
+ }
+ }
+
+ /**
+ * @see StaticIpConfiguration#getRoutes(String)
+ * @hide
+ */
+ public List<RouteInfo> getRoutes(String iface) {
+ return toStaticIpConfiguration().getRoutes(iface);
+ }
+
+ /**
+ * Test if this DHCP lease includes vendor hint that network link is
+ * metered, and sensitive to heavy data transfers.
+ */
+ public boolean hasMeteredHint() {
+ if (vendorInfo != null) {
+ return vendorInfo.contains("ANDROID_METERED");
+ } else {
+ return false;
+ }
+ }
+
+ public void clear() {
+ ipAddress = null;
+ gateway = null;
+ dnsServers.clear();
+ domains = null;
+ serverAddress = null;
+ vendorInfo = null;
+ leaseDuration = 0;
+ mtu = 0;
+ serverHostName = null;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer str = new StringBuffer(super.toString());
+
+ str.append(" DHCP server ").append(serverAddress);
+ str.append(" Vendor info ").append(vendorInfo);
+ str.append(" lease ").append(leaseDuration).append(" seconds");
+ if (mtu != 0) str.append(" MTU ").append(mtu);
+ str.append(" Servername ").append(serverHostName);
+
+ return str.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+
+ if (!(obj instanceof DhcpResults)) return false;
+
+ DhcpResults target = (DhcpResults)obj;
+
+ return toStaticIpConfiguration().equals(target.toStaticIpConfiguration())
+ && Objects.equals(serverAddress, target.serverAddress)
+ && Objects.equals(vendorInfo, target.vendorInfo)
+ && Objects.equals(serverHostName, target.serverHostName)
+ && leaseDuration == target.leaseDuration
+ && mtu == target.mtu;
+ }
+
+ /**
+ * Implement the Parcelable interface
+ */
+ public static final @android.annotation.NonNull Creator<DhcpResults> CREATOR =
+ new Creator<DhcpResults>() {
+ public DhcpResults createFromParcel(Parcel in) {
+ return readFromParcel(in);
+ }
+
+ public DhcpResults[] newArray(int size) {
+ return new DhcpResults[size];
+ }
+ };
+
+ /** Implement the Parcelable interface */
+ public void writeToParcel(Parcel dest, int flags) {
+ toStaticIpConfiguration().writeToParcel(dest, flags);
+ dest.writeInt(leaseDuration);
+ dest.writeInt(mtu);
+ InetAddressUtils.parcelInetAddress(dest, serverAddress, flags);
+ dest.writeString(vendorInfo);
+ dest.writeString(serverHostName);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private static DhcpResults readFromParcel(Parcel in) {
+ final StaticIpConfiguration s = StaticIpConfiguration.CREATOR.createFromParcel(in);
+ final DhcpResults dhcpResults = new DhcpResults(s);
+ dhcpResults.leaseDuration = in.readInt();
+ dhcpResults.mtu = in.readInt();
+ dhcpResults.serverAddress = (Inet4Address) InetAddressUtils.unparcelInetAddress(in);
+ dhcpResults.vendorInfo = in.readString();
+ dhcpResults.serverHostName = in.readString();
+ return dhcpResults;
+ }
+
+ // Utils for jni population - false on success
+ // Not part of the superclass because they're only used by the JNI iterface to the DHCP daemon.
+ public boolean setIpAddress(String addrString, int prefixLength) {
+ try {
+ Inet4Address addr = (Inet4Address) InetAddresses.parseNumericAddress(addrString);
+ ipAddress = new LinkAddress(addr, prefixLength);
+ } catch (IllegalArgumentException|ClassCastException e) {
+ Log.e(TAG, "setIpAddress failed with addrString " + addrString + "/" + prefixLength);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean setGateway(String addrString) {
+ try {
+ gateway = InetAddresses.parseNumericAddress(addrString);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "setGateway failed with addrString " + addrString);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean addDns(String addrString) {
+ if (TextUtils.isEmpty(addrString) == false) {
+ try {
+ dnsServers.add(InetAddresses.parseNumericAddress(addrString));
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "addDns failed with addrString " + addrString);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public LinkAddress getIpAddress() {
+ return ipAddress;
+ }
+
+ public void setIpAddress(LinkAddress ipAddress) {
+ this.ipAddress = ipAddress;
+ }
+
+ public InetAddress getGateway() {
+ return gateway;
+ }
+
+ public void setGateway(InetAddress gateway) {
+ this.gateway = gateway;
+ }
+
+ public List<InetAddress> getDnsServers() {
+ return dnsServers;
+ }
+
+ /**
+ * Add a DNS server to this configuration.
+ */
+ public void addDnsServer(InetAddress server) {
+ dnsServers.add(server);
+ }
+
+ public String getDomains() {
+ return domains;
+ }
+
+ public void setDomains(String domains) {
+ this.domains = domains;
+ }
+
+ public Inet4Address getServerAddress() {
+ return serverAddress;
+ }
+
+ public void setServerAddress(Inet4Address addr) {
+ serverAddress = addr;
+ }
+
+ public int getLeaseDuration() {
+ return leaseDuration;
+ }
+
+ public void setLeaseDuration(int duration) {
+ leaseDuration = duration;
+ }
+
+ public String getVendorInfo() {
+ return vendorInfo;
+ }
+
+ public void setVendorInfo(String info) {
+ vendorInfo = info;
+ }
+
+ public int getMtu() {
+ return mtu;
+ }
+
+ public void setMtu(int mtu) {
+ this.mtu = mtu;
+ }
+}
diff --git a/android/net/DnsPacket.java b/android/net/DnsPacket.java
new file mode 100644
index 0000000..83e57e0
--- /dev/null
+++ b/android/net/DnsPacket.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+
+import com.android.internal.util.BitUtils;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Defines basic data for DNS protocol based on RFC 1035.
+ * Subclasses create the specific format used in DNS packet.
+ *
+ * @hide
+ */
+public abstract class DnsPacket {
+ public class DnsHeader {
+ private static final String TAG = "DnsHeader";
+ public final int id;
+ public final int flags;
+ public final int rcode;
+ private final int[] mRecordCount;
+
+ /**
+ * Create a new DnsHeader from a positioned ByteBuffer.
+ *
+ * The ByteBuffer must be in network byte order (which is the default).
+ * Reads the passed ByteBuffer from its current position and decodes a DNS header.
+ * When this constructor returns, the reading position of the ByteBuffer has been
+ * advanced to the end of the DNS header record.
+ * This is meant to chain with other methods reading a DNS response in sequence.
+ */
+ DnsHeader(@NonNull ByteBuffer buf) throws BufferUnderflowException {
+ id = BitUtils.uint16(buf.getShort());
+ flags = BitUtils.uint16(buf.getShort());
+ rcode = flags & 0xF;
+ mRecordCount = new int[NUM_SECTIONS];
+ for (int i = 0; i < NUM_SECTIONS; ++i) {
+ mRecordCount[i] = BitUtils.uint16(buf.getShort());
+ }
+ }
+
+ /**
+ * Get record count by type.
+ */
+ public int getRecordCount(int type) {
+ return mRecordCount[type];
+ }
+ }
+
+ /**
+ * Superclass for DNS questions and DNS resource records.
+ *
+ * DNS questions (No TTL/RDATA)
+ * DNS resource records (With TTL/RDATA)
+ */
+ public class DnsRecord {
+ private static final int MAXNAMESIZE = 255;
+ private static final int MAXLABELSIZE = 63;
+ private static final int MAXLABELCOUNT = 128;
+ private static final int NAME_NORMAL = 0;
+ private static final int NAME_COMPRESSION = 0xC0;
+ private final DecimalFormat byteFormat = new DecimalFormat();
+ private final FieldPosition pos = new FieldPosition(0);
+
+ private static final String TAG = "DnsRecord";
+
+ public final String dName;
+ public final int nsType;
+ public final int nsClass;
+ public final long ttl;
+ private final byte[] mRdata;
+
+ /**
+ * Create a new DnsRecord from a positioned ByteBuffer.
+ *
+ * Reads the passed ByteBuffer from its current position and decodes a DNS record.
+ * When this constructor returns, the reading position of the ByteBuffer has been
+ * advanced to the end of the DNS header record.
+ * This is meant to chain with other methods reading a DNS response in sequence.
+ *
+ * @param ByteBuffer input of record, must be in network byte order
+ * (which is the default).
+ */
+ DnsRecord(int recordType, @NonNull ByteBuffer buf)
+ throws BufferUnderflowException, ParseException {
+ dName = parseName(buf, 0 /* Parse depth */);
+ if (dName.length() > MAXNAMESIZE) {
+ throw new ParseException(
+ "Parse name fail, name size is too long: " + dName.length());
+ }
+ nsType = BitUtils.uint16(buf.getShort());
+ nsClass = BitUtils.uint16(buf.getShort());
+
+ if (recordType != QDSECTION) {
+ ttl = BitUtils.uint32(buf.getInt());
+ final int length = BitUtils.uint16(buf.getShort());
+ mRdata = new byte[length];
+ buf.get(mRdata);
+ } else {
+ ttl = 0;
+ mRdata = null;
+ }
+ }
+
+ /**
+ * Get a copy of rdata.
+ */
+ @Nullable
+ public byte[] getRR() {
+ return (mRdata == null) ? null : mRdata.clone();
+ }
+
+ /**
+ * Convert label from {@code byte[]} to {@code String}
+ *
+ * Follows the same conversion rules of the native code (ns_name.c in libc)
+ */
+ private String labelToString(@NonNull byte[] label) {
+ final StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < label.length; ++i) {
+ int b = BitUtils.uint8(label[i]);
+ // Control characters and non-ASCII characters.
+ if (b <= 0x20 || b >= 0x7f) {
+ // Append the byte as an escaped decimal number, e.g., "\19" for 0x13.
+ sb.append('\\');
+ byteFormat.format(b, sb, pos);
+ } else if (b == '"' || b == '.' || b == ';' || b == '\\'
+ || b == '(' || b == ')' || b == '@' || b == '$') {
+ // Append the byte as an escaped character, e.g., "\:" for 0x3a.
+ sb.append('\\');
+ sb.append((char) b);
+ } else {
+ // Append the byte as a character, e.g., "a" for 0x61.
+ sb.append((char) b);
+ }
+ }
+ return sb.toString();
+ }
+
+ private String parseName(@NonNull ByteBuffer buf, int depth) throws
+ BufferUnderflowException, ParseException {
+ if (depth > MAXLABELCOUNT) {
+ throw new ParseException("Failed to parse name, too many labels");
+ }
+ final int len = BitUtils.uint8(buf.get());
+ final int mask = len & NAME_COMPRESSION;
+ if (0 == len) {
+ return "";
+ } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION) {
+ throw new ParseException("Parse name fail, bad label type");
+ } else if (mask == NAME_COMPRESSION) {
+ // Name compression based on RFC 1035 - 4.1.4 Message compression
+ final int offset = ((len & ~NAME_COMPRESSION) << 8) + BitUtils.uint8(buf.get());
+ final int oldPos = buf.position();
+ if (offset >= oldPos - 2) {
+ throw new ParseException("Parse compression name fail, invalid compression");
+ }
+ buf.position(offset);
+ final String pointed = parseName(buf, depth + 1);
+ buf.position(oldPos);
+ return pointed;
+ } else {
+ final byte[] label = new byte[len];
+ buf.get(label);
+ final String head = labelToString(label);
+ if (head.length() > MAXLABELSIZE) {
+ throw new ParseException("Parse name fail, invalid label length");
+ }
+ final String tail = parseName(buf, depth + 1);
+ return TextUtils.isEmpty(tail) ? head : head + "." + tail;
+ }
+ }
+ }
+
+ public static final int QDSECTION = 0;
+ public static final int ANSECTION = 1;
+ public static final int NSSECTION = 2;
+ public static final int ARSECTION = 3;
+ private static final int NUM_SECTIONS = ARSECTION + 1;
+
+ private static final String TAG = DnsPacket.class.getSimpleName();
+
+ protected final DnsHeader mHeader;
+ protected final List<DnsRecord>[] mRecords;
+
+ protected DnsPacket(@NonNull byte[] data) throws ParseException {
+ if (null == data) throw new ParseException("Parse header failed, null input data");
+ final ByteBuffer buffer;
+ try {
+ buffer = ByteBuffer.wrap(data);
+ mHeader = new DnsHeader(buffer);
+ } catch (BufferUnderflowException e) {
+ throw new ParseException("Parse Header fail, bad input data", e);
+ }
+
+ mRecords = new ArrayList[NUM_SECTIONS];
+
+ for (int i = 0; i < NUM_SECTIONS; ++i) {
+ final int count = mHeader.getRecordCount(i);
+ if (count > 0) {
+ mRecords[i] = new ArrayList(count);
+ }
+ for (int j = 0; j < count; ++j) {
+ try {
+ mRecords[i].add(new DnsRecord(i, buffer));
+ } catch (BufferUnderflowException e) {
+ throw new ParseException("Parse record fail", e);
+ }
+ }
+ }
+ }
+}
diff --git a/android/net/DnsResolver.java b/android/net/DnsResolver.java
new file mode 100644
index 0000000..0b1a845
--- /dev/null
+++ b/android/net/DnsResolver.java
@@ -0,0 +1,568 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.net.NetworkUtils.getDnsNetwork;
+import static android.net.NetworkUtils.resNetworkCancel;
+import static android.net.NetworkUtils.resNetworkQuery;
+import static android.net.NetworkUtils.resNetworkResult;
+import static android.net.NetworkUtils.resNetworkSend;
+import static android.net.util.DnsUtils.haveIpv4;
+import static android.net.util.DnsUtils.haveIpv6;
+import static android.net.util.DnsUtils.rfc6724Sort;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
+import static android.system.OsConstants.ENONET;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.CancellationSignal;
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.system.ErrnoException;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Dns resolver class for asynchronous dns querying
+ *
+ * Note that if a client sends a query with more than 1 record in the question section but
+ * the remote dns server does not support this, it may not respond at all, leading to a timeout.
+ *
+ */
+public final class DnsResolver {
+ private static final String TAG = "DnsResolver";
+ private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
+ private static final int MAXPACKET = 8 * 1024;
+ private static final int SLEEP_TIME_MS = 2;
+
+ @IntDef(prefix = { "CLASS_" }, value = {
+ CLASS_IN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface QueryClass {}
+ public static final int CLASS_IN = 1;
+
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_A,
+ TYPE_AAAA
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface QueryType {}
+ public static final int TYPE_A = 1;
+ public static final int TYPE_AAAA = 28;
+
+ @IntDef(prefix = { "FLAG_" }, value = {
+ FLAG_EMPTY,
+ FLAG_NO_RETRY,
+ FLAG_NO_CACHE_STORE,
+ FLAG_NO_CACHE_LOOKUP
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface QueryFlag {}
+ public static final int FLAG_EMPTY = 0;
+ public static final int FLAG_NO_RETRY = 1 << 0;
+ public static final int FLAG_NO_CACHE_STORE = 1 << 1;
+ public static final int FLAG_NO_CACHE_LOOKUP = 1 << 2;
+
+ @IntDef(prefix = { "ERROR_" }, value = {
+ ERROR_PARSE,
+ ERROR_SYSTEM
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface DnsError {}
+ /**
+ * Indicates that there was an error parsing the response the query.
+ * The cause of this error is available via getCause() and is a ParseException.
+ */
+ public static final int ERROR_PARSE = 0;
+ /**
+ * Indicates that there was an error sending the query.
+ * The cause of this error is available via getCause() and is an ErrnoException.
+ */
+ public static final int ERROR_SYSTEM = 1;
+
+ private static final int NETID_UNSET = 0;
+
+ private static final DnsResolver sInstance = new DnsResolver();
+
+ /**
+ * Get instance for DnsResolver
+ */
+ public static @NonNull DnsResolver getInstance() {
+ return sInstance;
+ }
+
+ private DnsResolver() {}
+
+ /**
+ * Base interface for answer callbacks
+ *
+ * @param <T> The type of the answer
+ */
+ public interface Callback<T> {
+ /**
+ * Success response to
+ * {@link android.net.DnsResolver#query query()} or
+ * {@link android.net.DnsResolver#rawQuery rawQuery()}.
+ *
+ * Invoked when the answer to a query was successfully parsed.
+ *
+ * @param answer <T> answer to the query.
+ * @param rcode The response code in the DNS response.
+ *
+ * {@see android.net.DnsResolver#query query()}
+ */
+ void onAnswer(@NonNull T answer, int rcode);
+ /**
+ * Error response to
+ * {@link android.net.DnsResolver#query query()} or
+ * {@link android.net.DnsResolver#rawQuery rawQuery()}.
+ *
+ * Invoked when there is no valid answer to
+ * {@link android.net.DnsResolver#query query()}
+ * {@link android.net.DnsResolver#rawQuery rawQuery()}.
+ *
+ * @param error a {@link DnsException} object with additional
+ * detail regarding the failure
+ */
+ void onError(@NonNull DnsException error);
+ }
+
+ /**
+ * Class to represent DNS error
+ */
+ public static class DnsException extends Exception {
+ /**
+ * DNS error code as one of the ERROR_* constants
+ */
+ @DnsError public final int code;
+
+ DnsException(@DnsError int code, @Nullable Throwable cause) {
+ super(cause);
+ this.code = code;
+ }
+ }
+
+ /**
+ * Send a raw DNS query.
+ * The answer will be provided asynchronously through the provided {@link Callback}.
+ *
+ * @param network {@link Network} specifying which network to query on.
+ * {@code null} for query on default network.
+ * @param query blob message to query
+ * @param flags flags as a combination of the FLAGS_* constants
+ * @param executor The {@link Executor} that the callback should be executed on.
+ * @param cancellationSignal used by the caller to signal if the query should be
+ * cancelled. May be {@code null}.
+ * @param callback a {@link Callback} which will be called to notify the caller
+ * of the result of dns query.
+ */
+ public void rawQuery(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags,
+ @NonNull @CallbackExecutor Executor executor,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull Callback<? super byte[]> callback) {
+ if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+ return;
+ }
+ final Object lock = new Object();
+ final FileDescriptor queryfd;
+ try {
+ queryfd = resNetworkSend((network != null)
+ ? network.getNetIdForResolv() : NETID_UNSET, query, query.length, flags);
+ } catch (ErrnoException e) {
+ executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
+ return;
+ }
+
+ synchronized (lock) {
+ registerFDListener(executor, queryfd, callback, cancellationSignal, lock);
+ if (cancellationSignal == null) return;
+ addCancellationSignal(cancellationSignal, queryfd, lock);
+ }
+ }
+
+ /**
+ * Send a DNS query with the specified name, class and query type.
+ * The answer will be provided asynchronously through the provided {@link Callback}.
+ *
+ * @param network {@link Network} specifying which network to query on.
+ * {@code null} for query on default network.
+ * @param domain domain name to query
+ * @param nsClass dns class as one of the CLASS_* constants
+ * @param nsType dns resource record (RR) type as one of the TYPE_* constants
+ * @param flags flags as a combination of the FLAGS_* constants
+ * @param executor The {@link Executor} that the callback should be executed on.
+ * @param cancellationSignal used by the caller to signal if the query should be
+ * cancelled. May be {@code null}.
+ * @param callback a {@link Callback} which will be called to notify the caller
+ * of the result of dns query.
+ */
+ public void rawQuery(@Nullable Network network, @NonNull String domain,
+ @QueryClass int nsClass, @QueryType int nsType, @QueryFlag int flags,
+ @NonNull @CallbackExecutor Executor executor,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull Callback<? super byte[]> callback) {
+ if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+ return;
+ }
+ final Object lock = new Object();
+ final FileDescriptor queryfd;
+ try {
+ queryfd = resNetworkQuery((network != null)
+ ? network.getNetIdForResolv() : NETID_UNSET, domain, nsClass, nsType, flags);
+ } catch (ErrnoException e) {
+ executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
+ return;
+ }
+ synchronized (lock) {
+ registerFDListener(executor, queryfd, callback, cancellationSignal, lock);
+ if (cancellationSignal == null) return;
+ addCancellationSignal(cancellationSignal, queryfd, lock);
+ }
+ }
+
+ private class InetAddressAnswerAccumulator implements Callback<byte[]> {
+ private final List<InetAddress> mAllAnswers;
+ private final Network mNetwork;
+ private int mRcode;
+ private DnsException mDnsException;
+ private final Callback<? super List<InetAddress>> mUserCallback;
+ private final int mTargetAnswerCount;
+ private int mReceivedAnswerCount = 0;
+
+ InetAddressAnswerAccumulator(@NonNull Network network, int size,
+ @NonNull Callback<? super List<InetAddress>> callback) {
+ mNetwork = network;
+ mTargetAnswerCount = size;
+ mAllAnswers = new ArrayList<>();
+ mUserCallback = callback;
+ }
+
+ private boolean maybeReportError() {
+ if (mRcode != 0) {
+ mUserCallback.onAnswer(mAllAnswers, mRcode);
+ return true;
+ }
+ if (mDnsException != null) {
+ mUserCallback.onError(mDnsException);
+ return true;
+ }
+ return false;
+ }
+
+ private void maybeReportAnswer() {
+ if (++mReceivedAnswerCount != mTargetAnswerCount) return;
+ if (mAllAnswers.isEmpty() && maybeReportError()) return;
+ mUserCallback.onAnswer(rfc6724Sort(mNetwork, mAllAnswers), mRcode);
+ }
+
+ @Override
+ public void onAnswer(@NonNull byte[] answer, int rcode) {
+ // If at least one query succeeded, return an rcode of 0.
+ // Otherwise, arbitrarily return the first rcode received.
+ if (mReceivedAnswerCount == 0 || rcode == 0) {
+ mRcode = rcode;
+ }
+ try {
+ mAllAnswers.addAll(new DnsAddressAnswer(answer).getAddresses());
+ } catch (ParseException e) {
+ mDnsException = new DnsException(ERROR_PARSE, e);
+ }
+ maybeReportAnswer();
+ }
+
+ @Override
+ public void onError(@NonNull DnsException error) {
+ mDnsException = error;
+ maybeReportAnswer();
+ }
+ }
+
+ /**
+ * Send a DNS query with the specified name on a network with both IPv4 and IPv6,
+ * get back a set of InetAddresses with rfc6724 sorting style asynchronously.
+ *
+ * This method will examine the connection ability on given network, and query IPv4
+ * and IPv6 if connection is available.
+ *
+ * If at least one query succeeded with valid answer, rcode will be 0
+ *
+ * The answer will be provided asynchronously through the provided {@link Callback}.
+ *
+ * @param network {@link Network} specifying which network to query on.
+ * {@code null} for query on default network.
+ * @param domain domain name to query
+ * @param flags flags as a combination of the FLAGS_* constants
+ * @param executor The {@link Executor} that the callback should be executed on.
+ * @param cancellationSignal used by the caller to signal if the query should be
+ * cancelled. May be {@code null}.
+ * @param callback a {@link Callback} which will be called to notify the
+ * caller of the result of dns query.
+ */
+ public void query(@Nullable Network network, @NonNull String domain, @QueryFlag int flags,
+ @NonNull @CallbackExecutor Executor executor,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull Callback<? super List<InetAddress>> callback) {
+ if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+ return;
+ }
+ final Object lock = new Object();
+ final Network queryNetwork;
+ try {
+ queryNetwork = (network != null) ? network : getDnsNetwork();
+ } catch (ErrnoException e) {
+ executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
+ return;
+ }
+ final boolean queryIpv6 = haveIpv6(queryNetwork);
+ final boolean queryIpv4 = haveIpv4(queryNetwork);
+
+ // This can only happen if queryIpv4 and queryIpv6 are both false.
+ // This almost certainly means that queryNetwork does not exist or no longer exists.
+ if (!queryIpv6 && !queryIpv4) {
+ executor.execute(() -> callback.onError(
+ new DnsException(ERROR_SYSTEM, new ErrnoException("resNetworkQuery", ENONET))));
+ return;
+ }
+
+ final FileDescriptor v4fd;
+ final FileDescriptor v6fd;
+
+ int queryCount = 0;
+
+ if (queryIpv6) {
+ try {
+ v6fd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN,
+ TYPE_AAAA, flags);
+ } catch (ErrnoException e) {
+ executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
+ return;
+ }
+ queryCount++;
+ } else v6fd = null;
+
+ // Avoiding gateways drop packets if queries are sent too close together
+ try {
+ Thread.sleep(SLEEP_TIME_MS);
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ }
+
+ if (queryIpv4) {
+ try {
+ v4fd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN, TYPE_A,
+ flags);
+ } catch (ErrnoException e) {
+ if (queryIpv6) resNetworkCancel(v6fd); // Closes fd, marks it invalid.
+ executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
+ return;
+ }
+ queryCount++;
+ } else v4fd = null;
+
+ final InetAddressAnswerAccumulator accumulator =
+ new InetAddressAnswerAccumulator(queryNetwork, queryCount, callback);
+
+ synchronized (lock) {
+ if (queryIpv6) {
+ registerFDListener(executor, v6fd, accumulator, cancellationSignal, lock);
+ }
+ if (queryIpv4) {
+ registerFDListener(executor, v4fd, accumulator, cancellationSignal, lock);
+ }
+ if (cancellationSignal == null) return;
+ cancellationSignal.setOnCancelListener(() -> {
+ synchronized (lock) {
+ if (queryIpv4) cancelQuery(v4fd);
+ if (queryIpv6) cancelQuery(v6fd);
+ }
+ });
+ }
+ }
+
+ /**
+ * Send a DNS query with the specified name and query type, get back a set of
+ * InetAddresses with rfc6724 sorting style asynchronously.
+ *
+ * The answer will be provided asynchronously through the provided {@link Callback}.
+ *
+ * @param network {@link Network} specifying which network to query on.
+ * {@code null} for query on default network.
+ * @param domain domain name to query
+ * @param nsType dns resource record (RR) type as one of the TYPE_* constants
+ * @param flags flags as a combination of the FLAGS_* constants
+ * @param executor The {@link Executor} that the callback should be executed on.
+ * @param cancellationSignal used by the caller to signal if the query should be
+ * cancelled. May be {@code null}.
+ * @param callback a {@link Callback} which will be called to notify the caller
+ * of the result of dns query.
+ */
+ public void query(@Nullable Network network, @NonNull String domain,
+ @QueryType int nsType, @QueryFlag int flags,
+ @NonNull @CallbackExecutor Executor executor,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull Callback<? super List<InetAddress>> callback) {
+ if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+ return;
+ }
+ final Object lock = new Object();
+ final FileDescriptor queryfd;
+ final Network queryNetwork;
+ try {
+ queryNetwork = (network != null) ? network : getDnsNetwork();
+ queryfd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN, nsType,
+ flags);
+ } catch (ErrnoException e) {
+ executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
+ return;
+ }
+ final InetAddressAnswerAccumulator accumulator =
+ new InetAddressAnswerAccumulator(queryNetwork, 1, callback);
+ synchronized (lock) {
+ registerFDListener(executor, queryfd, accumulator, cancellationSignal, lock);
+ if (cancellationSignal == null) return;
+ addCancellationSignal(cancellationSignal, queryfd, lock);
+ }
+ }
+
+ /**
+ * Class to retrieve DNS response
+ *
+ * @hide
+ */
+ public static final class DnsResponse {
+ public final @NonNull byte[] answerbuf;
+ public final int rcode;
+ public DnsResponse(@NonNull byte[] answerbuf, int rcode) {
+ this.answerbuf = answerbuf;
+ this.rcode = rcode;
+ }
+ }
+
+ private void registerFDListener(@NonNull Executor executor,
+ @NonNull FileDescriptor queryfd, @NonNull Callback<? super byte[]> answerCallback,
+ @Nullable CancellationSignal cancellationSignal, @NonNull Object lock) {
+ final MessageQueue mainThreadMessageQueue = Looper.getMainLooper().getQueue();
+ mainThreadMessageQueue.addOnFileDescriptorEventListener(
+ queryfd,
+ FD_EVENTS,
+ (fd, events) -> {
+ // b/134310704
+ // Unregister fd event listener before resNetworkResult is called to prevent
+ // race condition caused by fd reused.
+ // For example when querying v4 and v6, it's possible that the first query ends
+ // and the fd is closed before the second request starts, which might return
+ // the same fd for the second request. By that time, the looper must have
+ // unregistered the fd, otherwise another event listener can't be registered.
+ mainThreadMessageQueue.removeOnFileDescriptorEventListener(fd);
+
+ executor.execute(() -> {
+ DnsResponse resp = null;
+ ErrnoException exception = null;
+ synchronized (lock) {
+ if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+ return;
+ }
+ try {
+ resp = resNetworkResult(fd); // Closes fd, marks it invalid.
+ } catch (ErrnoException e) {
+ Log.e(TAG, "resNetworkResult:" + e.toString());
+ exception = e;
+ }
+ }
+ if (exception != null) {
+ answerCallback.onError(new DnsException(ERROR_SYSTEM, exception));
+ return;
+ }
+ answerCallback.onAnswer(resp.answerbuf, resp.rcode);
+ });
+
+ // The file descriptor has already been unregistered, so it does not really
+ // matter what is returned here. In spirit 0 (meaning "unregister this FD")
+ // is still the closest to what the looper needs to do. When returning 0,
+ // Looper knows to ignore the fd if it has already been unregistered.
+ return 0;
+ });
+ }
+
+ private void cancelQuery(@NonNull FileDescriptor queryfd) {
+ if (!queryfd.valid()) return;
+ Looper.getMainLooper().getQueue().removeOnFileDescriptorEventListener(queryfd);
+ resNetworkCancel(queryfd); // Closes fd, marks it invalid.
+ }
+
+ private void addCancellationSignal(@NonNull CancellationSignal cancellationSignal,
+ @NonNull FileDescriptor queryfd, @NonNull Object lock) {
+ cancellationSignal.setOnCancelListener(() -> {
+ synchronized (lock) {
+ cancelQuery(queryfd);
+ }
+ });
+ }
+
+ private static class DnsAddressAnswer extends DnsPacket {
+ private static final String TAG = "DnsResolver.DnsAddressAnswer";
+ private static final boolean DBG = false;
+
+ private final int mQueryType;
+
+ DnsAddressAnswer(@NonNull byte[] data) throws ParseException {
+ super(data);
+ if ((mHeader.flags & (1 << 15)) == 0) {
+ throw new ParseException("Not an answer packet");
+ }
+ if (mHeader.getRecordCount(QDSECTION) == 0) {
+ throw new ParseException("No question found");
+ }
+ // Expect only one question in question section.
+ mQueryType = mRecords[QDSECTION].get(0).nsType;
+ }
+
+ public @NonNull List<InetAddress> getAddresses() {
+ final List<InetAddress> results = new ArrayList<InetAddress>();
+ if (mHeader.getRecordCount(ANSECTION) == 0) return results;
+
+ for (final DnsRecord ansSec : mRecords[ANSECTION]) {
+ // Only support A and AAAA, also ignore answers if query type != answer type.
+ int nsType = ansSec.nsType;
+ if (nsType != mQueryType || (nsType != TYPE_A && nsType != TYPE_AAAA)) {
+ continue;
+ }
+ try {
+ results.add(InetAddress.getByAddress(ansSec.getRR()));
+ } catch (UnknownHostException e) {
+ if (DBG) {
+ Log.w(TAG, "rr to address fail");
+ }
+ }
+ }
+ return results;
+ }
+ }
+
+}
diff --git a/android/net/EthernetManager.java b/android/net/EthernetManager.java
new file mode 100644
index 0000000..7256502
--- /dev/null
+++ b/android/net/EthernetManager.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2014 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;
+
+import android.annotation.SystemService;
+import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+
+import java.util.ArrayList;
+
+/**
+ * A class representing the IP configuration of the Ethernet network.
+ *
+ * @hide
+ */
+@SystemService(Context.ETHERNET_SERVICE)
+public class EthernetManager {
+ private static final String TAG = "EthernetManager";
+ private static final int MSG_AVAILABILITY_CHANGED = 1000;
+
+ private final Context mContext;
+ private final IEthernetManager mService;
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_AVAILABILITY_CHANGED) {
+ boolean isAvailable = (msg.arg1 == 1);
+ for (Listener listener : mListeners) {
+ listener.onAvailabilityChanged((String) msg.obj, isAvailable);
+ }
+ }
+ }
+ };
+ private final ArrayList<Listener> mListeners = new ArrayList<>();
+ private final IEthernetServiceListener.Stub mServiceListener =
+ new IEthernetServiceListener.Stub() {
+ @Override
+ public void onAvailabilityChanged(String iface, boolean isAvailable) {
+ mHandler.obtainMessage(
+ MSG_AVAILABILITY_CHANGED, isAvailable ? 1 : 0, 0, iface).sendToTarget();
+ }
+ };
+
+ /**
+ * A listener interface to receive notification on changes in Ethernet.
+ */
+ public interface Listener {
+ /**
+ * Called when Ethernet port's availability is changed.
+ * @param iface Ethernet interface name
+ * @param isAvailable {@code true} if Ethernet port exists.
+ */
+ @UnsupportedAppUsage
+ void onAvailabilityChanged(String iface, boolean isAvailable);
+ }
+
+ /**
+ * Create a new EthernetManager instance.
+ * Applications will almost always want to use
+ * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
+ * the standard {@link android.content.Context#ETHERNET_SERVICE Context.ETHERNET_SERVICE}.
+ */
+ public EthernetManager(Context context, IEthernetManager service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Get Ethernet configuration.
+ * @return the Ethernet Configuration, contained in {@link IpConfiguration}.
+ */
+ @UnsupportedAppUsage
+ public IpConfiguration getConfiguration(String iface) {
+ try {
+ return mService.getConfiguration(iface);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set Ethernet configuration.
+ */
+ @UnsupportedAppUsage
+ public void setConfiguration(String iface, IpConfiguration config) {
+ try {
+ mService.setConfiguration(iface, config);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Indicates whether the system currently has one or more Ethernet interfaces.
+ */
+ @UnsupportedAppUsage
+ public boolean isAvailable() {
+ return getAvailableInterfaces().length > 0;
+ }
+
+ /**
+ * Indicates whether the system has given interface.
+ *
+ * @param iface Ethernet interface name
+ */
+ @UnsupportedAppUsage
+ public boolean isAvailable(String iface) {
+ try {
+ return mService.isAvailable(iface);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Adds a listener.
+ * @param listener A {@link Listener} to add.
+ * @throws IllegalArgumentException If the listener is null.
+ */
+ @UnsupportedAppUsage
+ public void addListener(Listener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+ mListeners.add(listener);
+ if (mListeners.size() == 1) {
+ try {
+ mService.addListener(mServiceListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Returns an array of available Ethernet interface names.
+ */
+ @UnsupportedAppUsage
+ public String[] getAvailableInterfaces() {
+ try {
+ return mService.getAvailableInterfaces();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Removes a listener.
+ * @param listener A {@link Listener} to remove.
+ * @throws IllegalArgumentException If the listener is null.
+ */
+ @UnsupportedAppUsage
+ public void removeListener(Listener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+ mListeners.remove(listener);
+ if (mListeners.isEmpty()) {
+ try {
+ mService.removeListener(mServiceListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+}
diff --git a/android/net/InetAddresses.java b/android/net/InetAddresses.java
new file mode 100644
index 0000000..01b795e
--- /dev/null
+++ b/android/net/InetAddresses.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+
+import libcore.net.InetAddressUtils;
+
+import java.net.InetAddress;
+
+/**
+ * Utility methods for {@link InetAddress} implementations.
+ */
+public class InetAddresses {
+
+ private InetAddresses() {}
+
+ /**
+ * Checks to see if the {@code address} is a numeric address (such as {@code "192.0.2.1"} or
+ * {@code "2001:db8::1:2"}).
+ *
+ * <p>A numeric address is either an IPv4 address containing exactly 4 decimal numbers or an
+ * IPv6 numeric address. IPv4 addresses that consist of either hexadecimal or octal digits or
+ * do not have exactly 4 numbers are not treated as numeric.
+ *
+ * <p>This method will never do a DNS lookup.
+ *
+ * @param address the address to parse.
+ * @return true if the supplied address is numeric, false otherwise.
+ */
+ public static boolean isNumericAddress(@NonNull String address) {
+ return InetAddressUtils.isNumericAddress(address);
+ }
+
+ /**
+ * Returns an InetAddress corresponding to the given numeric address (such
+ * as {@code "192.168.0.1"} or {@code "2001:4860:800d::68"}).
+ *
+ * <p>See {@link #isNumericAddress(String)} (String)} for a definition as to what constitutes a
+ * numeric address.
+ *
+ * <p>This method will never do a DNS lookup.
+ *
+ * @param address the address to parse, must be numeric.
+ * @return an {@link InetAddress} instance corresponding to the address.
+ * @throws IllegalArgumentException if {@code address} is not a numeric address.
+ */
+ public static @NonNull InetAddress parseNumericAddress(@NonNull String address) {
+ return InetAddressUtils.parseNumericAddress(address);
+ }
+}
diff --git a/android/net/InterfaceConfiguration.java b/android/net/InterfaceConfiguration.java
new file mode 100644
index 0000000..c97b37b
--- /dev/null
+++ b/android/net/InterfaceConfiguration.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.google.android.collect.Sets;
+
+import java.util.HashSet;
+
+/**
+ * Configuration details for a network interface.
+ *
+ * @hide
+ */
+public class InterfaceConfiguration implements Parcelable {
+ private String mHwAddr;
+ private LinkAddress mAddr;
+ private HashSet<String> mFlags = Sets.newHashSet();
+
+ // Must be kept in sync with constant in INetd.aidl
+ private static final String FLAG_UP = "up";
+ private static final String FLAG_DOWN = "down";
+
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("mHwAddr=").append(mHwAddr);
+ builder.append(" mAddr=").append(String.valueOf(mAddr));
+ builder.append(" mFlags=").append(getFlags());
+ return builder.toString();
+ }
+
+ @UnsupportedAppUsage
+ public Iterable<String> getFlags() {
+ return mFlags;
+ }
+
+ public boolean hasFlag(String flag) {
+ validateFlag(flag);
+ return mFlags.contains(flag);
+ }
+
+ @UnsupportedAppUsage
+ public void clearFlag(String flag) {
+ validateFlag(flag);
+ mFlags.remove(flag);
+ }
+
+ @UnsupportedAppUsage
+ public void setFlag(String flag) {
+ validateFlag(flag);
+ mFlags.add(flag);
+ }
+
+ /**
+ * Set flags to mark interface as up.
+ */
+ @UnsupportedAppUsage
+ public void setInterfaceUp() {
+ mFlags.remove(FLAG_DOWN);
+ mFlags.add(FLAG_UP);
+ }
+
+ /**
+ * Set flags to mark interface as down.
+ */
+ @UnsupportedAppUsage
+ public void setInterfaceDown() {
+ mFlags.remove(FLAG_UP);
+ mFlags.add(FLAG_DOWN);
+ }
+
+ /**
+ * Set flags so that no changes will be made to the up/down status.
+ */
+ public void ignoreInterfaceUpDownStatus() {
+ mFlags.remove(FLAG_UP);
+ mFlags.remove(FLAG_DOWN);
+ }
+
+ public LinkAddress getLinkAddress() {
+ return mAddr;
+ }
+
+ @UnsupportedAppUsage
+ public void setLinkAddress(LinkAddress addr) {
+ mAddr = addr;
+ }
+
+ public String getHardwareAddress() {
+ return mHwAddr;
+ }
+
+ public void setHardwareAddress(String hwAddr) {
+ mHwAddr = hwAddr;
+ }
+
+ /**
+ * This function determines if the interface is up and has a valid IP
+ * configuration (IP address has a non zero octet).
+ *
+ * Note: It is supposed to be quick and hence should not initiate
+ * any network activity
+ */
+ public boolean isActive() {
+ try {
+ if (isUp()) {
+ for (byte b : mAddr.getAddress().getAddress()) {
+ if (b != 0) return true;
+ }
+ }
+ } catch (NullPointerException e) {
+ return false;
+ }
+ return false;
+ }
+
+ public boolean isUp() {
+ return hasFlag(FLAG_UP);
+ }
+
+ /** {@inheritDoc} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mHwAddr);
+ if (mAddr != null) {
+ dest.writeByte((byte)1);
+ dest.writeParcelable(mAddr, flags);
+ } else {
+ dest.writeByte((byte)0);
+ }
+ dest.writeInt(mFlags.size());
+ for (String flag : mFlags) {
+ dest.writeString(flag);
+ }
+ }
+
+ public static final @android.annotation.NonNull Creator<InterfaceConfiguration> CREATOR = new Creator<
+ InterfaceConfiguration>() {
+ public InterfaceConfiguration createFromParcel(Parcel in) {
+ InterfaceConfiguration info = new InterfaceConfiguration();
+ info.mHwAddr = in.readString();
+ if (in.readByte() == 1) {
+ info.mAddr = in.readParcelable(null);
+ }
+ final int size = in.readInt();
+ for (int i = 0; i < size; i++) {
+ info.mFlags.add(in.readString());
+ }
+ return info;
+ }
+
+ public InterfaceConfiguration[] newArray(int size) {
+ return new InterfaceConfiguration[size];
+ }
+ };
+
+ private static void validateFlag(String flag) {
+ if (flag.indexOf(' ') >= 0) {
+ throw new IllegalArgumentException("flag contains space: " + flag);
+ }
+ }
+}
diff --git a/android/net/IpConfiguration.java b/android/net/IpConfiguration.java
new file mode 100644
index 0000000..2af82d7
--- /dev/null
+++ b/android/net/IpConfiguration.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2014 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;
+
+import android.annotation.UnsupportedAppUsage;
+import android.net.StaticIpConfiguration;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A class representing a configured network.
+ * @hide
+ */
+public class IpConfiguration implements Parcelable {
+ private static final String TAG = "IpConfiguration";
+
+ public enum IpAssignment {
+ /* Use statically configured IP settings. Configuration can be accessed
+ * with staticIpConfiguration */
+ @UnsupportedAppUsage
+ STATIC,
+ /* Use dynamically configured IP settings */
+ DHCP,
+ /* no IP details are assigned, this is used to indicate
+ * that any existing IP settings should be retained */
+ UNASSIGNED
+ }
+
+ public IpAssignment ipAssignment;
+
+ public StaticIpConfiguration staticIpConfiguration;
+
+ public enum ProxySettings {
+ /* No proxy is to be used. Any existing proxy settings
+ * should be cleared. */
+ @UnsupportedAppUsage
+ NONE,
+ /* Use statically configured proxy. Configuration can be accessed
+ * with httpProxy. */
+ STATIC,
+ /* no proxy details are assigned, this is used to indicate
+ * that any existing proxy settings should be retained */
+ UNASSIGNED,
+ /* Use a Pac based proxy.
+ */
+ PAC
+ }
+
+ public ProxySettings proxySettings;
+
+ @UnsupportedAppUsage
+ public ProxyInfo httpProxy;
+
+ private void init(IpAssignment ipAssignment,
+ ProxySettings proxySettings,
+ StaticIpConfiguration staticIpConfiguration,
+ ProxyInfo httpProxy) {
+ this.ipAssignment = ipAssignment;
+ this.proxySettings = proxySettings;
+ this.staticIpConfiguration = (staticIpConfiguration == null) ?
+ null : new StaticIpConfiguration(staticIpConfiguration);
+ this.httpProxy = (httpProxy == null) ?
+ null : new ProxyInfo(httpProxy);
+ }
+
+ public IpConfiguration() {
+ init(IpAssignment.UNASSIGNED, ProxySettings.UNASSIGNED, null, null);
+ }
+
+ @UnsupportedAppUsage
+ public IpConfiguration(IpAssignment ipAssignment,
+ ProxySettings proxySettings,
+ StaticIpConfiguration staticIpConfiguration,
+ ProxyInfo httpProxy) {
+ init(ipAssignment, proxySettings, staticIpConfiguration, httpProxy);
+ }
+
+ public IpConfiguration(IpConfiguration source) {
+ this();
+ if (source != null) {
+ init(source.ipAssignment, source.proxySettings,
+ source.staticIpConfiguration, source.httpProxy);
+ }
+ }
+
+ public IpAssignment getIpAssignment() {
+ return ipAssignment;
+ }
+
+ public void setIpAssignment(IpAssignment ipAssignment) {
+ this.ipAssignment = ipAssignment;
+ }
+
+ public StaticIpConfiguration getStaticIpConfiguration() {
+ return staticIpConfiguration;
+ }
+
+ public void setStaticIpConfiguration(StaticIpConfiguration staticIpConfiguration) {
+ this.staticIpConfiguration = staticIpConfiguration;
+ }
+
+ public ProxySettings getProxySettings() {
+ return proxySettings;
+ }
+
+ public void setProxySettings(ProxySettings proxySettings) {
+ this.proxySettings = proxySettings;
+ }
+
+ public ProxyInfo getHttpProxy() {
+ return httpProxy;
+ }
+
+ public void setHttpProxy(ProxyInfo httpProxy) {
+ this.httpProxy = httpProxy;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sbuf = new StringBuilder();
+ sbuf.append("IP assignment: " + ipAssignment.toString());
+ sbuf.append("\n");
+ if (staticIpConfiguration != null) {
+ sbuf.append("Static configuration: " + staticIpConfiguration.toString());
+ sbuf.append("\n");
+ }
+ sbuf.append("Proxy settings: " + proxySettings.toString());
+ sbuf.append("\n");
+ if (httpProxy != null) {
+ sbuf.append("HTTP proxy: " + httpProxy.toString());
+ sbuf.append("\n");
+ }
+
+ return sbuf.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+
+ if (!(o instanceof IpConfiguration)) {
+ return false;
+ }
+
+ IpConfiguration other = (IpConfiguration) o;
+ return this.ipAssignment == other.ipAssignment &&
+ this.proxySettings == other.proxySettings &&
+ Objects.equals(this.staticIpConfiguration, other.staticIpConfiguration) &&
+ Objects.equals(this.httpProxy, other.httpProxy);
+ }
+
+ @Override
+ public int hashCode() {
+ return 13 + (staticIpConfiguration != null ? staticIpConfiguration.hashCode() : 0) +
+ 17 * ipAssignment.ordinal() +
+ 47 * proxySettings.ordinal() +
+ 83 * httpProxy.hashCode();
+ }
+
+ /** Implement the Parcelable interface */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(ipAssignment.name());
+ dest.writeString(proxySettings.name());
+ dest.writeParcelable(staticIpConfiguration, flags);
+ dest.writeParcelable(httpProxy, flags);
+ }
+
+ /** Implement the Parcelable interface */
+ public static final @android.annotation.NonNull Creator<IpConfiguration> CREATOR =
+ new Creator<IpConfiguration>() {
+ public IpConfiguration createFromParcel(Parcel in) {
+ IpConfiguration config = new IpConfiguration();
+ config.ipAssignment = IpAssignment.valueOf(in.readString());
+ config.proxySettings = ProxySettings.valueOf(in.readString());
+ config.staticIpConfiguration = in.readParcelable(null);
+ config.httpProxy = in.readParcelable(null);
+ return config;
+ }
+
+ public IpConfiguration[] newArray(int size) {
+ return new IpConfiguration[size];
+ }
+ };
+}
diff --git a/android/net/IpMemoryStore.java b/android/net/IpMemoryStore.java
new file mode 100644
index 0000000..6f91e00
--- /dev/null
+++ b/android/net/IpMemoryStore.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+
+/**
+ * Manager class used to communicate with the ip memory store service in the network stack,
+ * which is running in a separate module.
+ * @hide
+*/
+public class IpMemoryStore extends IpMemoryStoreClient {
+ private static final String TAG = IpMemoryStore.class.getSimpleName();
+ @NonNull private final CompletableFuture<IIpMemoryStore> mService;
+ @NonNull private final AtomicReference<CompletableFuture<IIpMemoryStore>> mTailNode;
+
+ public IpMemoryStore(@NonNull final Context context) {
+ super(context);
+ mService = new CompletableFuture<>();
+ mTailNode = new AtomicReference<CompletableFuture<IIpMemoryStore>>(mService);
+ getNetworkStackClient().fetchIpMemoryStore(
+ new IIpMemoryStoreCallbacks.Stub() {
+ @Override
+ public void onIpMemoryStoreFetched(@NonNull final IIpMemoryStore memoryStore) {
+ mService.complete(memoryStore);
+ }
+
+ @Override
+ public int getInterfaceVersion() {
+ return this.VERSION;
+ }
+ });
+ }
+
+ /*
+ * If the IpMemoryStore is ready, this function will run the request synchronously.
+ * Otherwise, it will enqueue the requests for execution immediately after the
+ * service becomes ready. The requests are guaranteed to be executed in the order
+ * they are sumbitted.
+ */
+ @Override
+ protected void runWhenServiceReady(Consumer<IIpMemoryStore> cb) throws ExecutionException {
+ mTailNode.getAndUpdate(future -> future.handle((store, exception) -> {
+ if (exception != null) {
+ // this should never happens since we also catch the exception below
+ Log.wtf(TAG, "Error fetching IpMemoryStore", exception);
+ return store;
+ }
+
+ try {
+ cb.accept(store);
+ } catch (Exception e) {
+ Log.wtf(TAG, "Exception occured: " + e.getMessage());
+ }
+ return store;
+ }));
+ }
+
+ @VisibleForTesting
+ protected NetworkStackClient getNetworkStackClient() {
+ return NetworkStackClient.getInstance();
+ }
+
+ /** Gets an instance of the memory store */
+ @NonNull
+ public static IpMemoryStore getMemoryStore(final Context context) {
+ return new IpMemoryStore(context);
+ }
+}
diff --git a/android/net/IpMemoryStoreClient.java b/android/net/IpMemoryStoreClient.java
new file mode 100644
index 0000000..014b528
--- /dev/null
+++ b/android/net/IpMemoryStoreClient.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.NetworkAttributes;
+import android.net.ipmemorystore.OnBlobRetrievedListener;
+import android.net.ipmemorystore.OnL2KeyResponseListener;
+import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener;
+import android.net.ipmemorystore.OnSameL3NetworkResponseListener;
+import android.net.ipmemorystore.OnStatusListener;
+import android.net.ipmemorystore.Status;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+
+/**
+ * service used to communicate with the ip memory store service in network stack,
+ * which is running in a separate module.
+ * @hide
+ */
+public abstract class IpMemoryStoreClient {
+ private static final String TAG = IpMemoryStoreClient.class.getSimpleName();
+ private final Context mContext;
+
+ public IpMemoryStoreClient(@NonNull final Context context) {
+ if (context == null) throw new IllegalArgumentException("missing context");
+ mContext = context;
+ }
+
+ protected abstract void runWhenServiceReady(Consumer<IIpMemoryStore> cb)
+ throws ExecutionException;
+
+ @FunctionalInterface
+ private interface ThrowingRunnable {
+ void run() throws RemoteException;
+ }
+
+ private void ignoringRemoteException(ThrowingRunnable r) {
+ ignoringRemoteException("Failed to execute remote procedure call", r);
+ }
+
+ private void ignoringRemoteException(String message, ThrowingRunnable r) {
+ try {
+ r.run();
+ } catch (RemoteException e) {
+ Log.e(TAG, message, e);
+ }
+ }
+
+ /**
+ * Store network attributes for a given L2 key.
+ * If L2Key is null, choose automatically from the attributes ; passing null is equivalent to
+ * calling findL2Key with the attributes and storing in the returned value.
+ *
+ * @param l2Key The L2 key for the L2 network. Clients that don't know or care about the L2
+ * key and only care about grouping can pass a unique ID here like the ones
+ * generated by {@code java.util.UUID.randomUUID()}, but keep in mind the low
+ * relevance of such a network will lead to it being evicted soon if it's not
+ * refreshed. Use findL2Key to try and find a similar L2Key to these attributes.
+ * @param attributes The attributes for this network.
+ * @param listener A listener that will be invoked to inform of the completion of this call,
+ * or null if the client is not interested in learning about success/failure.
+ * Through the listener, returns the L2 key. This is useful if the L2 key was not specified.
+ * If the call failed, the L2 key will be null.
+ */
+ public void storeNetworkAttributes(@NonNull final String l2Key,
+ @NonNull final NetworkAttributes attributes,
+ @Nullable final OnStatusListener listener) {
+ try {
+ runWhenServiceReady(service -> ignoringRemoteException(
+ () -> service.storeNetworkAttributes(l2Key, attributes.toParcelable(),
+ OnStatusListener.toAIDL(listener))));
+ } catch (ExecutionException m) {
+ ignoringRemoteException("Error storing network attributes",
+ () -> listener.onComplete(new Status(Status.ERROR_UNKNOWN)));
+ }
+ }
+
+ /**
+ * Store a binary blob associated with an L2 key and a name.
+ *
+ * @param l2Key The L2 key for this network.
+ * @param clientId The ID of the client.
+ * @param name The name of this data.
+ * @param data The data to store.
+ * @param listener A listener to inform of the completion of this call, or null if the client
+ * is not interested in learning about success/failure.
+ * Through the listener, returns a status to indicate success or failure.
+ */
+ public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId,
+ @NonNull final String name, @NonNull final Blob data,
+ @Nullable final OnStatusListener listener) {
+ try {
+ runWhenServiceReady(service -> ignoringRemoteException(
+ () -> service.storeBlob(l2Key, clientId, name, data,
+ OnStatusListener.toAIDL(listener))));
+ } catch (ExecutionException m) {
+ ignoringRemoteException("Error storing blob",
+ () -> listener.onComplete(new Status(Status.ERROR_UNKNOWN)));
+ }
+ }
+
+ /**
+ * Returns the best L2 key associated with the attributes.
+ *
+ * This will find a record that would be in the same group as the passed attributes. This is
+ * useful to choose the key for storing a sample or private data when the L2 key is not known.
+ * If multiple records are group-close to these attributes, the closest match is returned.
+ * If multiple records have the same closeness, the one with the smaller (unicode codepoint
+ * order) L2 key is returned.
+ * If no record matches these attributes, null is returned.
+ *
+ * @param attributes The attributes of the network to find.
+ * @param listener The listener that will be invoked to return the answer.
+ * Through the listener, returns the L2 key if one matched, or null.
+ */
+ public void findL2Key(@NonNull final NetworkAttributes attributes,
+ @NonNull final OnL2KeyResponseListener listener) {
+ try {
+ runWhenServiceReady(service -> ignoringRemoteException(
+ () -> service.findL2Key(attributes.toParcelable(),
+ OnL2KeyResponseListener.toAIDL(listener))));
+ } catch (ExecutionException m) {
+ ignoringRemoteException("Error finding L2 Key",
+ () -> listener.onL2KeyResponse(new Status(Status.ERROR_UNKNOWN), null));
+ }
+ }
+
+ /**
+ * Returns whether, to the best of the store's ability to tell, the two specified L2 keys point
+ * to the same L3 network. Group-closeness is used to determine this.
+ *
+ * @param l2Key1 The key for the first network.
+ * @param l2Key2 The key for the second network.
+ * @param listener The listener that will be invoked to return the answer.
+ * Through the listener, a SameL3NetworkResponse containing the answer and confidence.
+ */
+ public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2,
+ @NonNull final OnSameL3NetworkResponseListener listener) {
+ try {
+ runWhenServiceReady(service -> ignoringRemoteException(
+ () -> service.isSameNetwork(l2Key1, l2Key2,
+ OnSameL3NetworkResponseListener.toAIDL(listener))));
+ } catch (ExecutionException m) {
+ ignoringRemoteException("Error checking for network sameness",
+ () -> listener.onSameL3NetworkResponse(new Status(Status.ERROR_UNKNOWN), null));
+ }
+ }
+
+ /**
+ * Retrieve the network attributes for a key.
+ * If no record is present for this key, this will return null attributes.
+ *
+ * @param l2Key The key of the network to query.
+ * @param listener The listener that will be invoked to return the answer.
+ * Through the listener, returns the network attributes and the L2 key associated with
+ * the query.
+ */
+ public void retrieveNetworkAttributes(@NonNull final String l2Key,
+ @NonNull final OnNetworkAttributesRetrievedListener listener) {
+ try {
+ runWhenServiceReady(service -> ignoringRemoteException(
+ () -> service.retrieveNetworkAttributes(l2Key,
+ OnNetworkAttributesRetrievedListener.toAIDL(listener))));
+ } catch (ExecutionException m) {
+ ignoringRemoteException("Error retrieving network attributes",
+ () -> listener.onNetworkAttributesRetrieved(new Status(Status.ERROR_UNKNOWN),
+ null, null));
+ }
+ }
+
+ /**
+ * Retrieve previously stored private data.
+ * If no data was stored for this L2 key and name this will return null.
+ *
+ * @param l2Key The L2 key.
+ * @param clientId The id of the client that stored this data.
+ * @param name The name of the data.
+ * @param listener The listener that will be invoked to return the answer.
+ * Through the listener, returns the private data (or null), with the L2 key
+ * and the name of the data associated with the query.
+ */
+ public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId,
+ @NonNull final String name, @NonNull final OnBlobRetrievedListener listener) {
+ try {
+ runWhenServiceReady(service -> ignoringRemoteException(
+ () -> service.retrieveBlob(l2Key, clientId, name,
+ OnBlobRetrievedListener.toAIDL(listener))));
+ } catch (ExecutionException m) {
+ ignoringRemoteException("Error retrieving blob",
+ () -> listener.onBlobRetrieved(new Status(Status.ERROR_UNKNOWN),
+ null, null, null));
+ }
+ }
+
+ /**
+ * Wipe the data in the database upon network factory reset.
+ */
+ public void factoryReset() {
+ try {
+ runWhenServiceReady(service -> ignoringRemoteException(
+ () -> service.factoryReset()));
+ } catch (ExecutionException m) {
+ Log.e(TAG, "Error executing factory reset", m);
+ }
+ }
+}
diff --git a/android/net/IpPrefix.java b/android/net/IpPrefix.java
new file mode 100644
index 0000000..8cfe6df
--- /dev/null
+++ b/android/net/IpPrefix.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2014 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;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Pair;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * This class represents an IP prefix, i.e., a contiguous block of IP addresses aligned on a
+ * power of two boundary (also known as an "IP subnet"). A prefix is specified by two pieces of
+ * information:
+ *
+ * <ul>
+ * <li>A starting IP address (IPv4 or IPv6). This is the first IP address of the prefix.
+ * <li>A prefix length. This specifies the length of the prefix by specifing the number of bits
+ * in the IP address, starting from the most significant bit in network byte order, that
+ * are constant for all addresses in the prefix.
+ * </ul>
+ *
+ * For example, the prefix <code>192.0.2.0/24</code> covers the 256 IPv4 addresses from
+ * <code>192.0.2.0</code> to <code>192.0.2.255</code>, inclusive, and the prefix
+ * <code>2001:db8:1:2</code> covers the 2^64 IPv6 addresses from <code>2001:db8:1:2::</code> to
+ * <code>2001:db8:1:2:ffff:ffff:ffff:ffff</code>, inclusive.
+ *
+ * Objects of this class are immutable.
+ */
+public final class IpPrefix implements Parcelable {
+ private final byte[] address; // network byte order
+ private final int prefixLength;
+
+ private void checkAndMaskAddressAndPrefixLength() {
+ if (address.length != 4 && address.length != 16) {
+ throw new IllegalArgumentException(
+ "IpPrefix has " + address.length + " bytes which is neither 4 nor 16");
+ }
+ NetworkUtils.maskRawAddress(address, prefixLength);
+ }
+
+ /**
+ * Constructs a new {@code IpPrefix} from a byte array containing an IPv4 or IPv6 address in
+ * network byte order and a prefix length. Silently truncates the address to the prefix length,
+ * so for example {@code 192.0.2.1/24} is silently converted to {@code 192.0.2.0/24}.
+ *
+ * @param address the IP address. Must be non-null and exactly 4 or 16 bytes long.
+ * @param prefixLength the prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6).
+ *
+ * @hide
+ */
+ public IpPrefix(@NonNull byte[] address, @IntRange(from = 0, to = 128) int prefixLength) {
+ this.address = address.clone();
+ this.prefixLength = prefixLength;
+ checkAndMaskAddressAndPrefixLength();
+ }
+
+ /**
+ * Constructs a new {@code IpPrefix} from an IPv4 or IPv6 address and a prefix length. Silently
+ * truncates the address to the prefix length, so for example {@code 192.0.2.1/24} is silently
+ * converted to {@code 192.0.2.0/24}.
+ *
+ * @param address the IP address. Must be non-null.
+ * @param prefixLength the prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6).
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public IpPrefix(@NonNull InetAddress address, @IntRange(from = 0, to = 128) int prefixLength) {
+ // We don't reuse the (byte[], int) constructor because it calls clone() on the byte array,
+ // which is unnecessary because getAddress() already returns a clone.
+ this.address = address.getAddress();
+ this.prefixLength = prefixLength;
+ checkAndMaskAddressAndPrefixLength();
+ }
+
+ /**
+ * Constructs a new IpPrefix from a string such as "192.0.2.1/24" or "2001:db8::1/64".
+ * Silently truncates the address to the prefix length, so for example {@code 192.0.2.1/24}
+ * is silently converted to {@code 192.0.2.0/24}.
+ *
+ * @param prefix the prefix to parse
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public IpPrefix(@NonNull String prefix) {
+ // We don't reuse the (InetAddress, int) constructor because "error: call to this must be
+ // first statement in constructor". We could factor out setting the member variables to an
+ // init() method, but if we did, then we'd have to make the members non-final, or "error:
+ // cannot assign a value to final variable address". So we just duplicate the code here.
+ Pair<InetAddress, Integer> ipAndMask = NetworkUtils.parseIpAndMask(prefix);
+ this.address = ipAndMask.first.getAddress();
+ this.prefixLength = ipAndMask.second;
+ checkAndMaskAddressAndPrefixLength();
+ }
+
+ /**
+ * Compares this {@code IpPrefix} object against the specified object in {@code obj}. Two
+ * objects are equal if they have the same startAddress and prefixLength.
+ *
+ * @param obj the object to be tested for equality.
+ * @return {@code true} if both objects are equal, {@code false} otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof IpPrefix)) {
+ return false;
+ }
+ IpPrefix that = (IpPrefix) obj;
+ return Arrays.equals(this.address, that.address) && this.prefixLength == that.prefixLength;
+ }
+
+ /**
+ * Gets the hashcode of the represented IP prefix.
+ *
+ * @return the appropriate hashcode value.
+ */
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(address) + 11 * prefixLength;
+ }
+
+ /**
+ * Returns a copy of the first IP address in the prefix. Modifying the returned object does not
+ * change this object's contents.
+ *
+ * @return the address in the form of a byte array.
+ */
+ public @NonNull InetAddress getAddress() {
+ try {
+ return InetAddress.getByAddress(address);
+ } catch (UnknownHostException e) {
+ // Cannot happen. InetAddress.getByAddress can only throw an exception if the byte
+ // array is the wrong length, but we check that in the constructor.
+ throw new IllegalArgumentException("Address is invalid");
+ }
+ }
+
+ /**
+ * Returns a copy of the IP address bytes in network order (the highest order byte is the zeroth
+ * element). Modifying the returned array does not change this object's contents.
+ *
+ * @return the address in the form of a byte array.
+ */
+ public @NonNull byte[] getRawAddress() {
+ return address.clone();
+ }
+
+ /**
+ * Returns the prefix length of this {@code IpPrefix}.
+ *
+ * @return the prefix length.
+ */
+ @IntRange(from = 0, to = 128)
+ public int getPrefixLength() {
+ return prefixLength;
+ }
+
+ /**
+ * Determines whether the prefix contains the specified address.
+ *
+ * @param address An {@link InetAddress} to test.
+ * @return {@code true} if the prefix covers the given address. {@code false} otherwise.
+ */
+ public boolean contains(@NonNull InetAddress address) {
+ byte[] addrBytes = address.getAddress();
+ if (addrBytes == null || addrBytes.length != this.address.length) {
+ return false;
+ }
+ NetworkUtils.maskRawAddress(addrBytes, prefixLength);
+ return Arrays.equals(this.address, addrBytes);
+ }
+
+ /**
+ * Returns whether the specified prefix is entirely contained in this prefix.
+ *
+ * Note this is mathematical inclusion, so a prefix is always contained within itself.
+ * @param otherPrefix the prefix to test
+ * @hide
+ */
+ public boolean containsPrefix(@NonNull IpPrefix otherPrefix) {
+ if (otherPrefix.getPrefixLength() < prefixLength) return false;
+ final byte[] otherAddress = otherPrefix.getRawAddress();
+ NetworkUtils.maskRawAddress(otherAddress, prefixLength);
+ return Arrays.equals(otherAddress, address);
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isIPv6() {
+ return getAddress() instanceof Inet6Address;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isIPv4() {
+ return getAddress() instanceof Inet4Address;
+ }
+
+ /**
+ * Returns a string representation of this {@code IpPrefix}.
+ *
+ * @return a string such as {@code "192.0.2.0/24"} or {@code "2001:db8:1:2::/64"}.
+ */
+ public String toString() {
+ try {
+ return InetAddress.getByAddress(address).getHostAddress() + "/" + prefixLength;
+ } catch(UnknownHostException e) {
+ // Cosmic rays?
+ throw new IllegalStateException("IpPrefix with invalid address! Shouldn't happen.", e);
+ }
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeByteArray(address);
+ dest.writeInt(prefixLength);
+ }
+
+ /**
+ * Returns a comparator ordering IpPrefixes by length, shorter to longer.
+ * Contents of the address will break ties.
+ * @hide
+ */
+ public static Comparator<IpPrefix> lengthComparator() {
+ return new Comparator<IpPrefix>() {
+ @Override
+ public int compare(IpPrefix prefix1, IpPrefix prefix2) {
+ if (prefix1.isIPv4()) {
+ if (prefix2.isIPv6()) return -1;
+ } else {
+ if (prefix2.isIPv4()) return 1;
+ }
+ final int p1len = prefix1.getPrefixLength();
+ final int p2len = prefix2.getPrefixLength();
+ if (p1len < p2len) return -1;
+ if (p2len < p1len) return 1;
+ final byte[] a1 = prefix1.address;
+ final byte[] a2 = prefix2.address;
+ final int len = a1.length < a2.length ? a1.length : a2.length;
+ for (int i = 0; i < len; ++i) {
+ if (a1[i] < a2[i]) return -1;
+ if (a1[i] > a2[i]) return 1;
+ }
+ if (a2.length < len) return 1;
+ if (a1.length < len) return -1;
+ return 0;
+ }
+ };
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ */
+ public static final @android.annotation.NonNull Creator<IpPrefix> CREATOR =
+ new Creator<IpPrefix>() {
+ public IpPrefix createFromParcel(Parcel in) {
+ byte[] address = in.createByteArray();
+ int prefixLength = in.readInt();
+ return new IpPrefix(address, prefixLength);
+ }
+
+ public IpPrefix[] newArray(int size) {
+ return new IpPrefix[size];
+ }
+ };
+}
diff --git a/android/net/IpSecAlgorithm.java b/android/net/IpSecAlgorithm.java
new file mode 100644
index 0000000..38d9883
--- /dev/null
+++ b/android/net/IpSecAlgorithm.java
@@ -0,0 +1,307 @@
+/*
+ * 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 android.net;
+
+import android.annotation.NonNull;
+import android.annotation.StringDef;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.HexDump;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
+/**
+ * This class represents a single algorithm that can be used by an {@link IpSecTransform}.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
+ * Internet Protocol</a>
+ */
+public final class IpSecAlgorithm implements Parcelable {
+ private static final String TAG = "IpSecAlgorithm";
+
+ /**
+ * Null cipher.
+ *
+ * @hide
+ */
+ public static final String CRYPT_NULL = "ecb(cipher_null)";
+
+ /**
+ * AES-CBC Encryption/Ciphering Algorithm.
+ *
+ * <p>Valid lengths for this key are {128, 192, 256}.
+ */
+ public static final String CRYPT_AES_CBC = "cbc(aes)";
+
+ /**
+ * MD5 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
+ * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
+ *
+ * <p>Keys for this algorithm must be 128 bits in length.
+ *
+ * <p>Valid truncation lengths are multiples of 8 bits from 96 to 128.
+ */
+ public static final String AUTH_HMAC_MD5 = "hmac(md5)";
+
+ /**
+ * SHA1 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
+ * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
+ *
+ * <p>Keys for this algorithm must be 160 bits in length.
+ *
+ * <p>Valid truncation lengths are multiples of 8 bits from 96 to 160.
+ */
+ public static final String AUTH_HMAC_SHA1 = "hmac(sha1)";
+
+ /**
+ * SHA256 HMAC Authentication/Integrity Algorithm.
+ *
+ * <p>Keys for this algorithm must be 256 bits in length.
+ *
+ * <p>Valid truncation lengths are multiples of 8 bits from 96 to 256.
+ */
+ public static final String AUTH_HMAC_SHA256 = "hmac(sha256)";
+
+ /**
+ * SHA384 HMAC Authentication/Integrity Algorithm.
+ *
+ * <p>Keys for this algorithm must be 384 bits in length.
+ *
+ * <p>Valid truncation lengths are multiples of 8 bits from 192 to 384.
+ */
+ public static final String AUTH_HMAC_SHA384 = "hmac(sha384)";
+
+ /**
+ * SHA512 HMAC Authentication/Integrity Algorithm.
+ *
+ * <p>Keys for this algorithm must be 512 bits in length.
+ *
+ * <p>Valid truncation lengths are multiples of 8 bits from 256 to 512.
+ */
+ public static final String AUTH_HMAC_SHA512 = "hmac(sha512)";
+
+ /**
+ * AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm.
+ *
+ * <p>Valid lengths for keying material are {160, 224, 288}.
+ *
+ * <p>As per <a href="https://tools.ietf.org/html/rfc4106#section-8.1">RFC4106 (Section
+ * 8.1)</a>, keying material consists of a 128, 192, or 256 bit AES key followed by a 32-bit
+ * salt. RFC compliance requires that the salt must be unique per invocation with the same key.
+ *
+ * <p>Valid ICV (truncation) lengths are {64, 96, 128}.
+ */
+ public static final String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
+
+ /** @hide */
+ @StringDef({
+ CRYPT_AES_CBC,
+ AUTH_HMAC_MD5,
+ AUTH_HMAC_SHA1,
+ AUTH_HMAC_SHA256,
+ AUTH_HMAC_SHA384,
+ AUTH_HMAC_SHA512,
+ AUTH_CRYPT_AES_GCM
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AlgorithmName {}
+
+ private final String mName;
+ private final byte[] mKey;
+ private final int mTruncLenBits;
+
+ /**
+ * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are
+ * defined as constants in this class.
+ *
+ * <p>For algorithms that produce an integrity check value, the truncation length is a required
+ * parameter. See {@link #IpSecAlgorithm(String algorithm, byte[] key, int truncLenBits)}
+ *
+ * @param algorithm name of the algorithm.
+ * @param key key padded to a multiple of 8 bits.
+ */
+ public IpSecAlgorithm(@NonNull @AlgorithmName String algorithm, @NonNull byte[] key) {
+ this(algorithm, key, 0);
+ }
+
+ /**
+ * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are
+ * defined as constants in this class.
+ *
+ * <p>This constructor only supports algorithms that use a truncation length. i.e.
+ * Authentication and Authenticated Encryption algorithms.
+ *
+ * @param algorithm name of the algorithm.
+ * @param key key padded to a multiple of 8 bits.
+ * @param truncLenBits number of bits of output hash to use.
+ */
+ public IpSecAlgorithm(
+ @NonNull @AlgorithmName String algorithm, @NonNull byte[] key, int truncLenBits) {
+ mName = algorithm;
+ mKey = key.clone();
+ mTruncLenBits = truncLenBits;
+ checkValidOrThrow(mName, mKey.length * 8, mTruncLenBits);
+ }
+
+ /** Get the algorithm name */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /** Get the key for this algorithm */
+ @NonNull
+ public byte[] getKey() {
+ return mKey.clone();
+ }
+
+ /** Get the truncation length of this algorithm, in bits */
+ public int getTruncationLengthBits() {
+ return mTruncLenBits;
+ }
+
+ /* Parcelable Implementation */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Write to parcel */
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(mName);
+ out.writeByteArray(mKey);
+ out.writeInt(mTruncLenBits);
+ }
+
+ /** Parcelable Creator */
+ public static final @android.annotation.NonNull Parcelable.Creator<IpSecAlgorithm> CREATOR =
+ new Parcelable.Creator<IpSecAlgorithm>() {
+ public IpSecAlgorithm createFromParcel(Parcel in) {
+ final String name = in.readString();
+ final byte[] key = in.createByteArray();
+ final int truncLenBits = in.readInt();
+
+ return new IpSecAlgorithm(name, key, truncLenBits);
+ }
+
+ public IpSecAlgorithm[] newArray(int size) {
+ return new IpSecAlgorithm[size];
+ }
+ };
+
+ private static void checkValidOrThrow(String name, int keyLen, int truncLen) {
+ boolean isValidLen = true;
+ boolean isValidTruncLen = true;
+
+ switch(name) {
+ case CRYPT_AES_CBC:
+ isValidLen = keyLen == 128 || keyLen == 192 || keyLen == 256;
+ break;
+ case AUTH_HMAC_MD5:
+ isValidLen = keyLen == 128;
+ isValidTruncLen = truncLen >= 96 && truncLen <= 128;
+ break;
+ case AUTH_HMAC_SHA1:
+ isValidLen = keyLen == 160;
+ isValidTruncLen = truncLen >= 96 && truncLen <= 160;
+ break;
+ case AUTH_HMAC_SHA256:
+ isValidLen = keyLen == 256;
+ isValidTruncLen = truncLen >= 96 && truncLen <= 256;
+ break;
+ case AUTH_HMAC_SHA384:
+ isValidLen = keyLen == 384;
+ isValidTruncLen = truncLen >= 192 && truncLen <= 384;
+ break;
+ case AUTH_HMAC_SHA512:
+ isValidLen = keyLen == 512;
+ isValidTruncLen = truncLen >= 256 && truncLen <= 512;
+ break;
+ case AUTH_CRYPT_AES_GCM:
+ // The keying material for GCM is a key plus a 32-bit salt
+ isValidLen = keyLen == 128 + 32 || keyLen == 192 + 32 || keyLen == 256 + 32;
+ isValidTruncLen = truncLen == 64 || truncLen == 96 || truncLen == 128;
+ break;
+ default:
+ throw new IllegalArgumentException("Couldn't find an algorithm: " + name);
+ }
+
+ if (!isValidLen) {
+ throw new IllegalArgumentException("Invalid key material keyLength: " + keyLen);
+ }
+ if (!isValidTruncLen) {
+ throw new IllegalArgumentException("Invalid truncation keyLength: " + truncLen);
+ }
+ }
+
+ /** @hide */
+ public boolean isAuthentication() {
+ switch (getName()) {
+ // Fallthrough
+ case AUTH_HMAC_MD5:
+ case AUTH_HMAC_SHA1:
+ case AUTH_HMAC_SHA256:
+ case AUTH_HMAC_SHA384:
+ case AUTH_HMAC_SHA512:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /** @hide */
+ public boolean isEncryption() {
+ return getName().equals(CRYPT_AES_CBC);
+ }
+
+ /** @hide */
+ public boolean isAead() {
+ return getName().equals(AUTH_CRYPT_AES_GCM);
+ }
+
+ // Because encryption keys are sensitive and userdebug builds are used by large user pools
+ // such as beta testers, we only allow sensitive info such as keys on eng builds.
+ private static boolean isUnsafeBuild() {
+ return Build.IS_DEBUGGABLE && Build.IS_ENG;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return new StringBuilder()
+ .append("{mName=")
+ .append(mName)
+ .append(", mKey=")
+ .append(isUnsafeBuild() ? HexDump.toHexString(mKey) : "<hidden>")
+ .append(", mTruncLenBits=")
+ .append(mTruncLenBits)
+ .append("}")
+ .toString();
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public static boolean equals(IpSecAlgorithm lhs, IpSecAlgorithm rhs) {
+ if (lhs == null || rhs == null) return (lhs == rhs);
+ return (lhs.mName.equals(rhs.mName)
+ && Arrays.equals(lhs.mKey, rhs.mKey)
+ && lhs.mTruncLenBits == rhs.mTruncLenBits);
+ }
+};
diff --git a/android/net/IpSecConfig.java b/android/net/IpSecConfig.java
new file mode 100644
index 0000000..a64014f
--- /dev/null
+++ b/android/net/IpSecConfig.java
@@ -0,0 +1,357 @@
+/*
+ * 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 android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * This class encapsulates all the configuration parameters needed to create IPsec transforms and
+ * policies.
+ *
+ * @hide
+ */
+public final class IpSecConfig implements Parcelable {
+ private static final String TAG = "IpSecConfig";
+
+ // MODE_TRANSPORT or MODE_TUNNEL
+ private int mMode = IpSecTransform.MODE_TRANSPORT;
+
+ // Preventing this from being null simplifies Java->Native binder
+ private String mSourceAddress = "";
+
+ // Preventing this from being null simplifies Java->Native binder
+ private String mDestinationAddress = "";
+
+ // The underlying Network that represents the "gateway" Network
+ // for outbound packets. It may also be used to select packets.
+ private Network mNetwork;
+
+ // Minimum requirements for identifying a transform
+ // SPI identifying the IPsec SA in packet processing
+ // and a destination IP address
+ private int mSpiResourceId = IpSecManager.INVALID_RESOURCE_ID;
+
+ // Encryption Algorithm
+ private IpSecAlgorithm mEncryption;
+
+ // Authentication Algorithm
+ private IpSecAlgorithm mAuthentication;
+
+ // Authenticated Encryption Algorithm
+ private IpSecAlgorithm mAuthenticatedEncryption;
+
+ // For tunnel mode IPv4 UDP Encapsulation
+ // IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE
+ private int mEncapType = IpSecTransform.ENCAP_NONE;
+ private int mEncapSocketResourceId = IpSecManager.INVALID_RESOURCE_ID;
+ private int mEncapRemotePort;
+
+ // An interval, in seconds between the NattKeepalive packets
+ private int mNattKeepaliveInterval;
+
+ // XFRM mark and mask; defaults to 0 (no mark/mask)
+ private int mMarkValue;
+ private int mMarkMask;
+
+ // XFRM interface id
+ private int mXfrmInterfaceId;
+
+ /** Set the mode for this IPsec transform */
+ public void setMode(int mode) {
+ mMode = mode;
+ }
+
+ /** Set the source IP addres for this IPsec transform */
+ public void setSourceAddress(String sourceAddress) {
+ mSourceAddress = sourceAddress;
+ }
+
+ /** Set the destination IP address for this IPsec transform */
+ public void setDestinationAddress(String destinationAddress) {
+ mDestinationAddress = destinationAddress;
+ }
+
+ /** Set the SPI by resource ID */
+ public void setSpiResourceId(int resourceId) {
+ mSpiResourceId = resourceId;
+ }
+
+ /** Set the encryption algorithm */
+ public void setEncryption(IpSecAlgorithm encryption) {
+ mEncryption = encryption;
+ }
+
+ /** Set the authentication algorithm */
+ public void setAuthentication(IpSecAlgorithm authentication) {
+ mAuthentication = authentication;
+ }
+
+ /** Set the authenticated encryption algorithm */
+ public void setAuthenticatedEncryption(IpSecAlgorithm authenticatedEncryption) {
+ mAuthenticatedEncryption = authenticatedEncryption;
+ }
+
+ /** Set the underlying network that will carry traffic for this transform */
+ public void setNetwork(Network network) {
+ mNetwork = network;
+ }
+
+ public void setEncapType(int encapType) {
+ mEncapType = encapType;
+ }
+
+ public void setEncapSocketResourceId(int resourceId) {
+ mEncapSocketResourceId = resourceId;
+ }
+
+ public void setEncapRemotePort(int port) {
+ mEncapRemotePort = port;
+ }
+
+ public void setNattKeepaliveInterval(int interval) {
+ mNattKeepaliveInterval = interval;
+ }
+
+ /**
+ * Sets the mark value
+ *
+ * <p>Internal (System server) use only. Marks passed in by users will be overwritten or
+ * ignored.
+ */
+ public void setMarkValue(int mark) {
+ mMarkValue = mark;
+ }
+
+ /**
+ * Sets the mark mask
+ *
+ * <p>Internal (System server) use only. Marks passed in by users will be overwritten or
+ * ignored.
+ */
+ public void setMarkMask(int mask) {
+ mMarkMask = mask;
+ }
+
+ public void setXfrmInterfaceId(int xfrmInterfaceId) {
+ mXfrmInterfaceId = xfrmInterfaceId;
+ }
+
+ // Transport or Tunnel
+ public int getMode() {
+ return mMode;
+ }
+
+ public String getSourceAddress() {
+ return mSourceAddress;
+ }
+
+ public int getSpiResourceId() {
+ return mSpiResourceId;
+ }
+
+ public String getDestinationAddress() {
+ return mDestinationAddress;
+ }
+
+ public IpSecAlgorithm getEncryption() {
+ return mEncryption;
+ }
+
+ public IpSecAlgorithm getAuthentication() {
+ return mAuthentication;
+ }
+
+ public IpSecAlgorithm getAuthenticatedEncryption() {
+ return mAuthenticatedEncryption;
+ }
+
+ public Network getNetwork() {
+ return mNetwork;
+ }
+
+ public int getEncapType() {
+ return mEncapType;
+ }
+
+ public int getEncapSocketResourceId() {
+ return mEncapSocketResourceId;
+ }
+
+ public int getEncapRemotePort() {
+ return mEncapRemotePort;
+ }
+
+ public int getNattKeepaliveInterval() {
+ return mNattKeepaliveInterval;
+ }
+
+ public int getMarkValue() {
+ return mMarkValue;
+ }
+
+ public int getMarkMask() {
+ return mMarkMask;
+ }
+
+ public int getXfrmInterfaceId() {
+ return mXfrmInterfaceId;
+ }
+
+ // Parcelable Methods
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mMode);
+ out.writeString(mSourceAddress);
+ out.writeString(mDestinationAddress);
+ out.writeParcelable(mNetwork, flags);
+ out.writeInt(mSpiResourceId);
+ out.writeParcelable(mEncryption, flags);
+ out.writeParcelable(mAuthentication, flags);
+ out.writeParcelable(mAuthenticatedEncryption, flags);
+ out.writeInt(mEncapType);
+ out.writeInt(mEncapSocketResourceId);
+ out.writeInt(mEncapRemotePort);
+ out.writeInt(mNattKeepaliveInterval);
+ out.writeInt(mMarkValue);
+ out.writeInt(mMarkMask);
+ out.writeInt(mXfrmInterfaceId);
+ }
+
+ @VisibleForTesting
+ public IpSecConfig() {}
+
+ /** Copy constructor */
+ @VisibleForTesting
+ public IpSecConfig(IpSecConfig c) {
+ mMode = c.mMode;
+ mSourceAddress = c.mSourceAddress;
+ mDestinationAddress = c.mDestinationAddress;
+ mNetwork = c.mNetwork;
+ mSpiResourceId = c.mSpiResourceId;
+ mEncryption = c.mEncryption;
+ mAuthentication = c.mAuthentication;
+ mAuthenticatedEncryption = c.mAuthenticatedEncryption;
+ mEncapType = c.mEncapType;
+ mEncapSocketResourceId = c.mEncapSocketResourceId;
+ mEncapRemotePort = c.mEncapRemotePort;
+ mNattKeepaliveInterval = c.mNattKeepaliveInterval;
+ mMarkValue = c.mMarkValue;
+ mMarkMask = c.mMarkMask;
+ mXfrmInterfaceId = c.mXfrmInterfaceId;
+ }
+
+ private IpSecConfig(Parcel in) {
+ mMode = in.readInt();
+ mSourceAddress = in.readString();
+ mDestinationAddress = in.readString();
+ mNetwork = (Network) in.readParcelable(Network.class.getClassLoader());
+ mSpiResourceId = in.readInt();
+ mEncryption =
+ (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+ mAuthentication =
+ (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+ mAuthenticatedEncryption =
+ (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+ mEncapType = in.readInt();
+ mEncapSocketResourceId = in.readInt();
+ mEncapRemotePort = in.readInt();
+ mNattKeepaliveInterval = in.readInt();
+ mMarkValue = in.readInt();
+ mMarkMask = in.readInt();
+ mXfrmInterfaceId = in.readInt();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder strBuilder = new StringBuilder();
+ strBuilder
+ .append("{mMode=")
+ .append(mMode == IpSecTransform.MODE_TUNNEL ? "TUNNEL" : "TRANSPORT")
+ .append(", mSourceAddress=")
+ .append(mSourceAddress)
+ .append(", mDestinationAddress=")
+ .append(mDestinationAddress)
+ .append(", mNetwork=")
+ .append(mNetwork)
+ .append(", mEncapType=")
+ .append(mEncapType)
+ .append(", mEncapSocketResourceId=")
+ .append(mEncapSocketResourceId)
+ .append(", mEncapRemotePort=")
+ .append(mEncapRemotePort)
+ .append(", mNattKeepaliveInterval=")
+ .append(mNattKeepaliveInterval)
+ .append("{mSpiResourceId=")
+ .append(mSpiResourceId)
+ .append(", mEncryption=")
+ .append(mEncryption)
+ .append(", mAuthentication=")
+ .append(mAuthentication)
+ .append(", mAuthenticatedEncryption=")
+ .append(mAuthenticatedEncryption)
+ .append(", mMarkValue=")
+ .append(mMarkValue)
+ .append(", mMarkMask=")
+ .append(mMarkMask)
+ .append(", mXfrmInterfaceId=")
+ .append(mXfrmInterfaceId)
+ .append("}");
+
+ return strBuilder.toString();
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<IpSecConfig> CREATOR =
+ new Parcelable.Creator<IpSecConfig>() {
+ public IpSecConfig createFromParcel(Parcel in) {
+ return new IpSecConfig(in);
+ }
+
+ public IpSecConfig[] newArray(int size) {
+ return new IpSecConfig[size];
+ }
+ };
+
+ @VisibleForTesting
+ /** Equals method used for testing */
+ public static boolean equals(IpSecConfig lhs, IpSecConfig rhs) {
+ if (lhs == null || rhs == null) return (lhs == rhs);
+ return (lhs.mMode == rhs.mMode
+ && lhs.mSourceAddress.equals(rhs.mSourceAddress)
+ && lhs.mDestinationAddress.equals(rhs.mDestinationAddress)
+ && ((lhs.mNetwork != null && lhs.mNetwork.equals(rhs.mNetwork))
+ || (lhs.mNetwork == rhs.mNetwork))
+ && lhs.mEncapType == rhs.mEncapType
+ && lhs.mEncapSocketResourceId == rhs.mEncapSocketResourceId
+ && lhs.mEncapRemotePort == rhs.mEncapRemotePort
+ && lhs.mNattKeepaliveInterval == rhs.mNattKeepaliveInterval
+ && lhs.mSpiResourceId == rhs.mSpiResourceId
+ && IpSecAlgorithm.equals(lhs.mEncryption, rhs.mEncryption)
+ && IpSecAlgorithm.equals(lhs.mAuthenticatedEncryption, rhs.mAuthenticatedEncryption)
+ && IpSecAlgorithm.equals(lhs.mAuthentication, rhs.mAuthentication)
+ && lhs.mMarkValue == rhs.mMarkValue
+ && lhs.mMarkMask == rhs.mMarkMask
+ && lhs.mXfrmInterfaceId == rhs.mXfrmInterfaceId);
+ }
+}
diff --git a/android/net/IpSecManager.java b/android/net/IpSecManager.java
new file mode 100644
index 0000000..83813da
--- /dev/null
+++ b/android/net/IpSecManager.java
@@ -0,0 +1,990 @@
+/*
+ * 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 android.net;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresFeature;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+import android.util.AndroidException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import dalvik.system.CloseGuard;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.Socket;
+
+/**
+ * This class contains methods for managing IPsec sessions. Once configured, the kernel will apply
+ * confidentiality (encryption) and integrity (authentication) to IP traffic.
+ *
+ * <p>Note that not all aspects of IPsec are permitted by this API. Applications may create
+ * transport mode security associations and apply them to individual sockets. Applications looking
+ * to create a VPN should use {@link VpnService}.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
+ * Internet Protocol</a>
+ */
+@SystemService(Context.IPSEC_SERVICE)
+public final class IpSecManager {
+ private static final String TAG = "IpSecManager";
+
+ /**
+ * Used when applying a transform to direct traffic through an {@link IpSecTransform}
+ * towards the host.
+ *
+ * <p>See {@link #applyTransportModeTransform(Socket, int, IpSecTransform)}.
+ */
+ public static final int DIRECTION_IN = 0;
+
+ /**
+ * Used when applying a transform to direct traffic through an {@link IpSecTransform}
+ * away from the host.
+ *
+ * <p>See {@link #applyTransportModeTransform(Socket, int, IpSecTransform)}.
+ */
+ public static final int DIRECTION_OUT = 1;
+
+ /** @hide */
+ @IntDef(value = {DIRECTION_IN, DIRECTION_OUT})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PolicyDirection {}
+
+ /**
+ * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index.
+ *
+ * <p>No IPsec packet may contain an SPI of 0.
+ *
+ * @hide
+ */
+ @TestApi public static final int INVALID_SECURITY_PARAMETER_INDEX = 0;
+
+ /** @hide */
+ public interface Status {
+ public static final int OK = 0;
+ public static final int RESOURCE_UNAVAILABLE = 1;
+ public static final int SPI_UNAVAILABLE = 2;
+ }
+
+ /** @hide */
+ public static final int INVALID_RESOURCE_ID = -1;
+
+ /**
+ * Thrown to indicate that a requested SPI is in use.
+ *
+ * <p>The combination of remote {@code InetAddress} and SPI must be unique across all apps on
+ * one device. If this error is encountered, a new SPI is required before a transform may be
+ * created. This error can be avoided by calling {@link
+ * IpSecManager#allocateSecurityParameterIndex}.
+ */
+ public static final class SpiUnavailableException extends AndroidException {
+ private final int mSpi;
+
+ /**
+ * Construct an exception indicating that a transform with the given SPI is already in use
+ * or otherwise unavailable.
+ *
+ * @param msg description indicating the colliding SPI
+ * @param spi the SPI that could not be used due to a collision
+ */
+ SpiUnavailableException(String msg, int spi) {
+ super(msg + " (spi: " + spi + ")");
+ mSpi = spi;
+ }
+
+ /** Get the SPI that caused a collision. */
+ public int getSpi() {
+ return mSpi;
+ }
+ }
+
+ /**
+ * Thrown to indicate that an IPsec resource is unavailable.
+ *
+ * <p>This could apply to resources such as sockets, {@link SecurityParameterIndex}, {@link
+ * IpSecTransform}, or other system resources. If this exception is thrown, users should release
+ * allocated objects of the type requested.
+ */
+ public static final class ResourceUnavailableException extends AndroidException {
+
+ ResourceUnavailableException(String msg) {
+ super(msg);
+ }
+ }
+
+ private final Context mContext;
+ private final IIpSecService mService;
+
+ /**
+ * This class represents a reserved SPI.
+ *
+ * <p>Objects of this type are used to track reserved security parameter indices. They can be
+ * obtained by calling {@link IpSecManager#allocateSecurityParameterIndex} and must be released
+ * by calling {@link #close()} when they are no longer needed.
+ */
+ public static final class SecurityParameterIndex implements AutoCloseable {
+ private final IIpSecService mService;
+ private final InetAddress mDestinationAddress;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+ private int mSpi = INVALID_SECURITY_PARAMETER_INDEX;
+ private int mResourceId = INVALID_RESOURCE_ID;
+
+ /** Get the underlying SPI held by this object. */
+ public int getSpi() {
+ return mSpi;
+ }
+
+ /**
+ * Release an SPI that was previously reserved.
+ *
+ * <p>Release an SPI for use by other users in the system. If a SecurityParameterIndex is
+ * applied to an IpSecTransform, it will become unusable for future transforms but should
+ * still be closed to ensure system resources are released.
+ */
+ @Override
+ public void close() {
+ try {
+ mService.releaseSecurityParameterIndex(mResourceId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (Exception e) {
+ // On close we swallow all random exceptions since failure to close is not
+ // actionable by the user.
+ Log.e(TAG, "Failed to close " + this + ", Exception=" + e);
+ } finally {
+ mResourceId = INVALID_RESOURCE_ID;
+ mCloseGuard.close();
+ }
+ }
+
+ /** Check that the SPI was closed properly. */
+ @Override
+ protected void finalize() throws Throwable {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
+ close();
+ }
+
+ private SecurityParameterIndex(
+ @NonNull IIpSecService service, InetAddress destinationAddress, int spi)
+ throws ResourceUnavailableException, SpiUnavailableException {
+ mService = service;
+ mDestinationAddress = destinationAddress;
+ try {
+ IpSecSpiResponse result =
+ mService.allocateSecurityParameterIndex(
+ destinationAddress.getHostAddress(), spi, new Binder());
+
+ if (result == null) {
+ throw new NullPointerException("Received null response from IpSecService");
+ }
+
+ int status = result.status;
+ switch (status) {
+ case Status.OK:
+ break;
+ case Status.RESOURCE_UNAVAILABLE:
+ throw new ResourceUnavailableException(
+ "No more SPIs may be allocated by this requester.");
+ case Status.SPI_UNAVAILABLE:
+ throw new SpiUnavailableException("Requested SPI is unavailable", spi);
+ default:
+ throw new RuntimeException(
+ "Unknown status returned by IpSecService: " + status);
+ }
+ mSpi = result.spi;
+ mResourceId = result.resourceId;
+
+ if (mSpi == INVALID_SECURITY_PARAMETER_INDEX) {
+ throw new RuntimeException("Invalid SPI returned by IpSecService: " + status);
+ }
+
+ if (mResourceId == INVALID_RESOURCE_ID) {
+ throw new RuntimeException(
+ "Invalid Resource ID returned by IpSecService: " + status);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mCloseGuard.open("open");
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public int getResourceId() {
+ return mResourceId;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("SecurityParameterIndex{spi=")
+ .append(mSpi)
+ .append(",resourceId=")
+ .append(mResourceId)
+ .append("}")
+ .toString();
+ }
+ }
+
+ /**
+ * Reserve a random SPI for traffic bound to or from the specified destination address.
+ *
+ * <p>If successful, this SPI is guaranteed available until released by a call to {@link
+ * SecurityParameterIndex#close()}.
+ *
+ * @param destinationAddress the destination address for traffic bearing the requested SPI.
+ * For inbound traffic, the destination should be an address currently assigned on-device.
+ * @return the reserved SecurityParameterIndex
+ * @throws {@link #ResourceUnavailableException} indicating that too many SPIs are
+ * currently allocated for this user
+ */
+ @NonNull
+ public SecurityParameterIndex allocateSecurityParameterIndex(
+ @NonNull InetAddress destinationAddress) throws ResourceUnavailableException {
+ try {
+ return new SecurityParameterIndex(
+ mService,
+ destinationAddress,
+ IpSecManager.INVALID_SECURITY_PARAMETER_INDEX);
+ } catch (ServiceSpecificException e) {
+ throw rethrowUncheckedExceptionFromServiceSpecificException(e);
+ } catch (SpiUnavailableException unlikely) {
+ // Because this function allocates a totally random SPI, it really shouldn't ever
+ // fail to allocate an SPI; we simply need this because the exception is checked.
+ throw new ResourceUnavailableException("No SPIs available");
+ }
+ }
+
+ /**
+ * Reserve the requested SPI for traffic bound to or from the specified destination address.
+ *
+ * <p>If successful, this SPI is guaranteed available until released by a call to {@link
+ * SecurityParameterIndex#close()}.
+ *
+ * @param destinationAddress the destination address for traffic bearing the requested SPI.
+ * For inbound traffic, the destination should be an address currently assigned on-device.
+ * @param requestedSpi the requested SPI. The range 1-255 is reserved and may not be used. See
+ * RFC 4303 Section 2.1.
+ * @return the reserved SecurityParameterIndex
+ * @throws {@link #ResourceUnavailableException} indicating that too many SPIs are
+ * currently allocated for this user
+ * @throws {@link #SpiUnavailableException} indicating that the requested SPI could not be
+ * reserved
+ */
+ @NonNull
+ public SecurityParameterIndex allocateSecurityParameterIndex(
+ @NonNull InetAddress destinationAddress, int requestedSpi)
+ throws SpiUnavailableException, ResourceUnavailableException {
+ if (requestedSpi == IpSecManager.INVALID_SECURITY_PARAMETER_INDEX) {
+ throw new IllegalArgumentException("Requested SPI must be a valid (non-zero) SPI");
+ }
+ try {
+ return new SecurityParameterIndex(mService, destinationAddress, requestedSpi);
+ } catch (ServiceSpecificException e) {
+ throw rethrowUncheckedExceptionFromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Apply an IPsec transform to a stream socket.
+ *
+ * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
+ * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
+ * the transform is removed from the socket by calling {@link #removeTransportModeTransforms},
+ * unprotected traffic can resume on that socket.
+ *
+ * <p>For security reasons, the destination address of any traffic on the socket must match the
+ * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
+ * other IP address will result in an IOException. In addition, reads and writes on the socket
+ * will throw IOException if the user deactivates the transform (by calling {@link
+ * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}.
+ *
+ * <p>Note that when applied to TCP sockets, calling {@link IpSecTransform#close()} on an
+ * applied transform before completion of graceful shutdown may result in the shutdown sequence
+ * failing to complete. As such, applications requiring graceful shutdown MUST close the socket
+ * prior to deactivating the applied transform. Socket closure may be performed asynchronously
+ * (in batches), so the returning of a close function does not guarantee shutdown of a socket.
+ * Setting an SO_LINGER timeout results in socket closure being performed synchronously, and is
+ * sufficient to ensure shutdown.
+ *
+ * Specifically, if the transform is deactivated (by calling {@link IpSecTransform#close()}),
+ * prior to the socket being closed, the standard [FIN - FIN/ACK - ACK], or the reset [RST]
+ * packets are dropped due to the lack of a valid Transform. Similarly, if a socket without the
+ * SO_LINGER option set is closed, the delayed/batched FIN packets may be dropped.
+ *
+ * <h4>Rekey Procedure</h4>
+ *
+ * <p>When applying a new tranform to a socket in the outbound direction, the previous transform
+ * will be removed and the new transform will take effect immediately, sending all traffic on
+ * the new transform; however, when applying a transform in the inbound direction, traffic
+ * on the old transform will continue to be decrypted and delivered until that transform is
+ * deallocated by calling {@link IpSecTransform#close()}. This overlap allows lossless rekey
+ * procedures where both transforms are valid until both endpoints are using the new transform
+ * and all in-flight packets have been received.
+ *
+ * @param socket a stream socket
+ * @param direction the direction in which the transform should be applied
+ * @param transform a transport mode {@code IpSecTransform}
+ * @throws IOException indicating that the transform could not be applied
+ */
+ public void applyTransportModeTransform(@NonNull Socket socket,
+ @PolicyDirection int direction, @NonNull IpSecTransform transform) throws IOException {
+ // Ensure creation of FD. See b/77548890 for more details.
+ socket.getSoLinger();
+
+ applyTransportModeTransform(socket.getFileDescriptor$(), direction, transform);
+ }
+
+ /**
+ * Apply an IPsec transform to a datagram socket.
+ *
+ * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
+ * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
+ * the transform is removed from the socket by calling {@link #removeTransportModeTransforms},
+ * unprotected traffic can resume on that socket.
+ *
+ * <p>For security reasons, the destination address of any traffic on the socket must match the
+ * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
+ * other IP address will result in an IOException. In addition, reads and writes on the socket
+ * will throw IOException if the user deactivates the transform (by calling {@link
+ * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}.
+ *
+ * <h4>Rekey Procedure</h4>
+ *
+ * <p>When applying a new tranform to a socket in the outbound direction, the previous transform
+ * will be removed and the new transform will take effect immediately, sending all traffic on
+ * the new transform; however, when applying a transform in the inbound direction, traffic
+ * on the old transform will continue to be decrypted and delivered until that transform is
+ * deallocated by calling {@link IpSecTransform#close()}. This overlap allows lossless rekey
+ * procedures where both transforms are valid until both endpoints are using the new transform
+ * and all in-flight packets have been received.
+ *
+ * @param socket a datagram socket
+ * @param direction the direction in which the transform should be applied
+ * @param transform a transport mode {@code IpSecTransform}
+ * @throws IOException indicating that the transform could not be applied
+ */
+ public void applyTransportModeTransform(@NonNull DatagramSocket socket,
+ @PolicyDirection int direction, @NonNull IpSecTransform transform) throws IOException {
+ applyTransportModeTransform(socket.getFileDescriptor$(), direction, transform);
+ }
+
+ /**
+ * Apply an IPsec transform to a socket.
+ *
+ * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
+ * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
+ * the transform is removed from the socket by calling {@link #removeTransportModeTransforms},
+ * unprotected traffic can resume on that socket.
+ *
+ * <p>For security reasons, the destination address of any traffic on the socket must match the
+ * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
+ * other IP address will result in an IOException. In addition, reads and writes on the socket
+ * will throw IOException if the user deactivates the transform (by calling {@link
+ * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}.
+ *
+ * <p>Note that when applied to TCP sockets, calling {@link IpSecTransform#close()} on an
+ * applied transform before completion of graceful shutdown may result in the shutdown sequence
+ * failing to complete. As such, applications requiring graceful shutdown MUST close the socket
+ * prior to deactivating the applied transform. Socket closure may be performed asynchronously
+ * (in batches), so the returning of a close function does not guarantee shutdown of a socket.
+ * Setting an SO_LINGER timeout results in socket closure being performed synchronously, and is
+ * sufficient to ensure shutdown.
+ *
+ * Specifically, if the transform is deactivated (by calling {@link IpSecTransform#close()}),
+ * prior to the socket being closed, the standard [FIN - FIN/ACK - ACK], or the reset [RST]
+ * packets are dropped due to the lack of a valid Transform. Similarly, if a socket without the
+ * SO_LINGER option set is closed, the delayed/batched FIN packets may be dropped.
+ *
+ * <h4>Rekey Procedure</h4>
+ *
+ * <p>When applying a new tranform to a socket in the outbound direction, the previous transform
+ * will be removed and the new transform will take effect immediately, sending all traffic on
+ * the new transform; however, when applying a transform in the inbound direction, traffic
+ * on the old transform will continue to be decrypted and delivered until that transform is
+ * deallocated by calling {@link IpSecTransform#close()}. This overlap allows lossless rekey
+ * procedures where both transforms are valid until both endpoints are using the new transform
+ * and all in-flight packets have been received.
+ *
+ * @param socket a socket file descriptor
+ * @param direction the direction in which the transform should be applied
+ * @param transform a transport mode {@code IpSecTransform}
+ * @throws IOException indicating that the transform could not be applied
+ */
+ public void applyTransportModeTransform(@NonNull FileDescriptor socket,
+ @PolicyDirection int direction, @NonNull IpSecTransform transform) throws IOException {
+ // We dup() the FileDescriptor here because if we don't, then the ParcelFileDescriptor()
+ // constructor takes control and closes the user's FD when we exit the method.
+ try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) {
+ mService.applyTransportModeTransform(pfd, direction, transform.getResourceId());
+ } catch (ServiceSpecificException e) {
+ throw rethrowCheckedExceptionFromServiceSpecificException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove an IPsec transform from a stream socket.
+ *
+ * <p>Once removed, traffic on the socket will not be encrypted. Removing transforms from a
+ * socket allows the socket to be reused for communication in the clear.
+ *
+ * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling
+ * {@link IpSecTransform#close()}, then communication on the socket will fail until this method
+ * is called.
+ *
+ * @param socket a socket that previously had a transform applied to it
+ * @throws IOException indicating that the transform could not be removed from the socket
+ */
+ public void removeTransportModeTransforms(@NonNull Socket socket) throws IOException {
+ // Ensure creation of FD. See b/77548890 for more details.
+ socket.getSoLinger();
+
+ removeTransportModeTransforms(socket.getFileDescriptor$());
+ }
+
+ /**
+ * Remove an IPsec transform from a datagram socket.
+ *
+ * <p>Once removed, traffic on the socket will not be encrypted. Removing transforms from a
+ * socket allows the socket to be reused for communication in the clear.
+ *
+ * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling
+ * {@link IpSecTransform#close()}, then communication on the socket will fail until this method
+ * is called.
+ *
+ * @param socket a socket that previously had a transform applied to it
+ * @throws IOException indicating that the transform could not be removed from the socket
+ */
+ public void removeTransportModeTransforms(@NonNull DatagramSocket socket) throws IOException {
+ removeTransportModeTransforms(socket.getFileDescriptor$());
+ }
+
+ /**
+ * Remove an IPsec transform from a socket.
+ *
+ * <p>Once removed, traffic on the socket will not be encrypted. Removing transforms from a
+ * socket allows the socket to be reused for communication in the clear.
+ *
+ * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling
+ * {@link IpSecTransform#close()}, then communication on the socket will fail until this method
+ * is called.
+ *
+ * @param socket a socket that previously had a transform applied to it
+ * @throws IOException indicating that the transform could not be removed from the socket
+ */
+ public void removeTransportModeTransforms(@NonNull FileDescriptor socket) throws IOException {
+ try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) {
+ mService.removeTransportModeTransforms(pfd);
+ } catch (ServiceSpecificException e) {
+ throw rethrowCheckedExceptionFromServiceSpecificException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove a Tunnel Mode IPsec Transform from a {@link Network}. This must be used as part of
+ * cleanup if a tunneled Network experiences a change in default route. The Network will drop
+ * all traffic that cannot be routed to the Tunnel's outbound interface. If that interface is
+ * lost, all traffic will drop.
+ *
+ * <p>TODO: Update javadoc for tunnel mode APIs at the same time the APIs are re-worked.
+ *
+ * @param net a network that currently has transform applied to it.
+ * @param transform a Tunnel Mode IPsec Transform that has been previously applied to the given
+ * network
+ * @hide
+ */
+ public void removeTunnelModeTransform(Network net, IpSecTransform transform) {}
+
+ /**
+ * This class provides access to a UDP encapsulation Socket.
+ *
+ * <p>{@code UdpEncapsulationSocket} wraps a system-provided datagram socket intended for IKEv2
+ * signalling and UDP encapsulated IPsec traffic. Instances can be obtained by calling {@link
+ * IpSecManager#openUdpEncapsulationSocket}. The provided socket cannot be re-bound by the
+ * caller. The caller should not close the {@code FileDescriptor} returned by {@link
+ * #getFileDescriptor}, but should use {@link #close} instead.
+ *
+ * <p>Allowing the user to close or unbind a UDP encapsulation socket could impact the traffic
+ * of the next user who binds to that port. To prevent this scenario, these sockets are held
+ * open by the system so that they may only be closed by calling {@link #close} or when the user
+ * process exits.
+ */
+ public static final class UdpEncapsulationSocket implements AutoCloseable {
+ private final ParcelFileDescriptor mPfd;
+ private final IIpSecService mService;
+ private int mResourceId = INVALID_RESOURCE_ID;
+ private final int mPort;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ private UdpEncapsulationSocket(@NonNull IIpSecService service, int port)
+ throws ResourceUnavailableException, IOException {
+ mService = service;
+ try {
+ IpSecUdpEncapResponse result =
+ mService.openUdpEncapsulationSocket(port, new Binder());
+ switch (result.status) {
+ case Status.OK:
+ break;
+ case Status.RESOURCE_UNAVAILABLE:
+ throw new ResourceUnavailableException(
+ "No more Sockets may be allocated by this requester.");
+ default:
+ throw new RuntimeException(
+ "Unknown status returned by IpSecService: " + result.status);
+ }
+ mResourceId = result.resourceId;
+ mPort = result.port;
+ mPfd = result.fileDescriptor;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mCloseGuard.open("constructor");
+ }
+
+ /** Get the encapsulation socket's file descriptor. */
+ public FileDescriptor getFileDescriptor() {
+ if (mPfd == null) {
+ return null;
+ }
+ return mPfd.getFileDescriptor();
+ }
+
+ /** Get the bound port of the wrapped socket. */
+ public int getPort() {
+ return mPort;
+ }
+
+ /**
+ * Close this socket.
+ *
+ * <p>This closes the wrapped socket. Open encapsulation sockets count against a user's
+ * resource limits, and forgetting to close them eventually will result in {@link
+ * ResourceUnavailableException} being thrown.
+ */
+ @Override
+ public void close() throws IOException {
+ try {
+ mService.closeUdpEncapsulationSocket(mResourceId);
+ mResourceId = INVALID_RESOURCE_ID;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (Exception e) {
+ // On close we swallow all random exceptions since failure to close is not
+ // actionable by the user.
+ Log.e(TAG, "Failed to close " + this + ", Exception=" + e);
+ } finally {
+ mResourceId = INVALID_RESOURCE_ID;
+ mCloseGuard.close();
+ }
+
+ try {
+ mPfd.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to close UDP Encapsulation Socket with Port= " + mPort);
+ throw e;
+ }
+ }
+
+ /** Check that the socket was closed properly. */
+ @Override
+ protected void finalize() throws Throwable {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ close();
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public int getResourceId() {
+ return mResourceId;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("UdpEncapsulationSocket{port=")
+ .append(mPort)
+ .append(",resourceId=")
+ .append(mResourceId)
+ .append("}")
+ .toString();
+ }
+ };
+
+ /**
+ * Open a socket for UDP encapsulation and bind to the given port.
+ *
+ * <p>See {@link UdpEncapsulationSocket} for the proper way to close the returned socket.
+ *
+ * @param port a local UDP port
+ * @return a socket that is bound to the given port
+ * @throws IOException indicating that the socket could not be opened or bound
+ * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open
+ */
+ // Returning a socket in this fashion that has been created and bound by the system
+ // is the only safe way to ensure that a socket is both accessible to the user and
+ // safely usable for Encapsulation without allowing a user to possibly unbind from/close
+ // the port, which could potentially impact the traffic of the next user who binds to that
+ // socket.
+ @NonNull
+ public UdpEncapsulationSocket openUdpEncapsulationSocket(int port)
+ throws IOException, ResourceUnavailableException {
+ /*
+ * Most range checking is done in the service, but this version of the constructor expects
+ * a valid port number, and zero cannot be checked after being passed to the service.
+ */
+ if (port == 0) {
+ throw new IllegalArgumentException("Specified port must be a valid port number!");
+ }
+ try {
+ return new UdpEncapsulationSocket(mService, port);
+ } catch (ServiceSpecificException e) {
+ throw rethrowCheckedExceptionFromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Open a socket for UDP encapsulation.
+ *
+ * <p>See {@link UdpEncapsulationSocket} for the proper way to close the returned socket.
+ *
+ * <p>The local port of the returned socket can be obtained by calling {@link
+ * UdpEncapsulationSocket#getPort()}.
+ *
+ * @return a socket that is bound to a local port
+ * @throws IOException indicating that the socket could not be opened or bound
+ * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open
+ */
+ // Returning a socket in this fashion that has been created and bound by the system
+ // is the only safe way to ensure that a socket is both accessible to the user and
+ // safely usable for Encapsulation without allowing a user to possibly unbind from/close
+ // the port, which could potentially impact the traffic of the next user who binds to that
+ // socket.
+ @NonNull
+ public UdpEncapsulationSocket openUdpEncapsulationSocket()
+ throws IOException, ResourceUnavailableException {
+ try {
+ return new UdpEncapsulationSocket(mService, 0);
+ } catch (ServiceSpecificException e) {
+ throw rethrowCheckedExceptionFromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * This class represents an IpSecTunnelInterface
+ *
+ * <p>IpSecTunnelInterface objects track tunnel interfaces that serve as
+ * local endpoints for IPsec tunnels.
+ *
+ * <p>Creating an IpSecTunnelInterface creates a device to which IpSecTransforms may be
+ * applied to provide IPsec security to packets sent through the tunnel. While a tunnel
+ * cannot be used in standalone mode within Android, the higher layers may use the tunnel
+ * to create Network objects which are accessible to the Android system.
+ * @hide
+ */
+ @SystemApi
+ public static final class IpSecTunnelInterface implements AutoCloseable {
+ private final String mOpPackageName;
+ private final IIpSecService mService;
+ private final InetAddress mRemoteAddress;
+ private final InetAddress mLocalAddress;
+ private final Network mUnderlyingNetwork;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+ private String mInterfaceName;
+ private int mResourceId = INVALID_RESOURCE_ID;
+
+ /** Get the underlying SPI held by this object. */
+ @NonNull
+ public String getInterfaceName() {
+ return mInterfaceName;
+ }
+
+ /**
+ * Add an address to the IpSecTunnelInterface
+ *
+ * <p>Add an address which may be used as the local inner address for
+ * tunneled traffic.
+ *
+ * @param address the local address for traffic inside the tunnel
+ * @param prefixLen length of the InetAddress prefix
+ * @hide
+ */
+ @SystemApi
+ @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+ @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
+ public void addAddress(@NonNull InetAddress address, int prefixLen) throws IOException {
+ try {
+ mService.addAddressToTunnelInterface(
+ mResourceId, new LinkAddress(address, prefixLen), mOpPackageName);
+ } catch (ServiceSpecificException e) {
+ throw rethrowCheckedExceptionFromServiceSpecificException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove an address from the IpSecTunnelInterface
+ *
+ * <p>Remove an address which was previously added to the IpSecTunnelInterface
+ *
+ * @param address to be removed
+ * @param prefixLen length of the InetAddress prefix
+ * @hide
+ */
+ @SystemApi
+ @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+ @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
+ public void removeAddress(@NonNull InetAddress address, int prefixLen) throws IOException {
+ try {
+ mService.removeAddressFromTunnelInterface(
+ mResourceId, new LinkAddress(address, prefixLen), mOpPackageName);
+ } catch (ServiceSpecificException e) {
+ throw rethrowCheckedExceptionFromServiceSpecificException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private IpSecTunnelInterface(@NonNull Context ctx, @NonNull IIpSecService service,
+ @NonNull InetAddress localAddress, @NonNull InetAddress remoteAddress,
+ @NonNull Network underlyingNetwork)
+ throws ResourceUnavailableException, IOException {
+ mOpPackageName = ctx.getOpPackageName();
+ mService = service;
+ mLocalAddress = localAddress;
+ mRemoteAddress = remoteAddress;
+ mUnderlyingNetwork = underlyingNetwork;
+
+ try {
+ IpSecTunnelInterfaceResponse result =
+ mService.createTunnelInterface(
+ localAddress.getHostAddress(),
+ remoteAddress.getHostAddress(),
+ underlyingNetwork,
+ new Binder(),
+ mOpPackageName);
+ switch (result.status) {
+ case Status.OK:
+ break;
+ case Status.RESOURCE_UNAVAILABLE:
+ throw new ResourceUnavailableException(
+ "No more tunnel interfaces may be allocated by this requester.");
+ default:
+ throw new RuntimeException(
+ "Unknown status returned by IpSecService: " + result.status);
+ }
+ mResourceId = result.resourceId;
+ mInterfaceName = result.interfaceName;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mCloseGuard.open("constructor");
+ }
+
+ /**
+ * Delete an IpSecTunnelInterface
+ *
+ * <p>Calling close will deallocate the IpSecTunnelInterface and all of its system
+ * resources. Any packets bound for this interface either inbound or outbound will
+ * all be lost.
+ */
+ @Override
+ public void close() {
+ try {
+ mService.deleteTunnelInterface(mResourceId, mOpPackageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (Exception e) {
+ // On close we swallow all random exceptions since failure to close is not
+ // actionable by the user.
+ Log.e(TAG, "Failed to close " + this + ", Exception=" + e);
+ } finally {
+ mResourceId = INVALID_RESOURCE_ID;
+ mCloseGuard.close();
+ }
+ }
+
+ /** Check that the Interface was closed properly. */
+ @Override
+ protected void finalize() throws Throwable {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ close();
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public int getResourceId() {
+ return mResourceId;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("IpSecTunnelInterface{ifname=")
+ .append(mInterfaceName)
+ .append(",resourceId=")
+ .append(mResourceId)
+ .append("}")
+ .toString();
+ }
+ }
+
+ /**
+ * Create a new IpSecTunnelInterface as a local endpoint for tunneled IPsec traffic.
+ *
+ * <p>An application that creates tunnels is responsible for cleaning up the tunnel when the
+ * underlying network goes away, and the onLost() callback is received.
+ *
+ * @param localAddress The local addres of the tunnel
+ * @param remoteAddress The local addres of the tunnel
+ * @param underlyingNetwork the {@link Network} that will carry traffic for this tunnel.
+ * This network should almost certainly be a network such as WiFi with an L2 address.
+ * @return a new {@link IpSecManager#IpSecTunnelInterface} with the specified properties
+ * @throws IOException indicating that the socket could not be opened or bound
+ * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+ @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
+ public IpSecTunnelInterface createIpSecTunnelInterface(@NonNull InetAddress localAddress,
+ @NonNull InetAddress remoteAddress, @NonNull Network underlyingNetwork)
+ throws ResourceUnavailableException, IOException {
+ try {
+ return new IpSecTunnelInterface(
+ mContext, mService, localAddress, remoteAddress, underlyingNetwork);
+ } catch (ServiceSpecificException e) {
+ throw rethrowCheckedExceptionFromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Apply an active Tunnel Mode IPsec Transform to a {@link IpSecTunnelInterface}, which will
+ * tunnel all traffic for the given direction through the underlying network's interface with
+ * IPsec (applies an outer IP header and IPsec Header to all traffic, and expects an additional
+ * IP header and IPsec Header on all inbound traffic).
+ * <p>Applications should probably not use this API directly.
+ *
+ *
+ * @param tunnel The {@link IpSecManager#IpSecTunnelInterface} that will use the supplied
+ * transform.
+ * @param direction the direction, {@link DIRECTION_OUT} or {@link #DIRECTION_IN} in which
+ * the transform will be used.
+ * @param transform an {@link IpSecTransform} created in tunnel mode
+ * @throws IOException indicating that the transform could not be applied due to a lower
+ * layer failure.
+ * @hide
+ */
+ @SystemApi
+ @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+ @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
+ public void applyTunnelModeTransform(@NonNull IpSecTunnelInterface tunnel,
+ @PolicyDirection int direction, @NonNull IpSecTransform transform) throws IOException {
+ try {
+ mService.applyTunnelModeTransform(
+ tunnel.getResourceId(), direction,
+ transform.getResourceId(), mContext.getOpPackageName());
+ } catch (ServiceSpecificException e) {
+ throw rethrowCheckedExceptionFromServiceSpecificException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Construct an instance of IpSecManager within an application context.
+ *
+ * @param context the application context for this manager
+ * @hide
+ */
+ public IpSecManager(Context ctx, IIpSecService service) {
+ mContext = ctx;
+ mService = checkNotNull(service, "missing service");
+ }
+
+ private static void maybeHandleServiceSpecificException(ServiceSpecificException sse) {
+ // OsConstants are late binding, so switch statements can't be used.
+ if (sse.errorCode == OsConstants.EINVAL) {
+ throw new IllegalArgumentException(sse);
+ } else if (sse.errorCode == OsConstants.EAGAIN) {
+ throw new IllegalStateException(sse);
+ } else if (sse.errorCode == OsConstants.EOPNOTSUPP
+ || sse.errorCode == OsConstants.EPROTONOSUPPORT) {
+ throw new UnsupportedOperationException(sse);
+ }
+ }
+
+ /**
+ * Convert an Errno SSE to the correct Unchecked exception type.
+ *
+ * This method never actually returns.
+ */
+ // package
+ static RuntimeException
+ rethrowUncheckedExceptionFromServiceSpecificException(ServiceSpecificException sse) {
+ maybeHandleServiceSpecificException(sse);
+ throw new RuntimeException(sse);
+ }
+
+ /**
+ * Convert an Errno SSE to the correct Checked or Unchecked exception type.
+ *
+ * This method may throw IOException, or it may throw an unchecked exception; it will never
+ * actually return.
+ */
+ // package
+ static IOException rethrowCheckedExceptionFromServiceSpecificException(
+ ServiceSpecificException sse) throws IOException {
+ // First see if this is an unchecked exception of a type we know.
+ // If so, then we prefer the unchecked (specific) type of exception.
+ maybeHandleServiceSpecificException(sse);
+ // If not, then all we can do is provide the SSE in the form of an IOException.
+ throw new ErrnoException(
+ "IpSec encountered errno=" + sse.errorCode, sse.errorCode).rethrowAsIOException();
+ }
+}
diff --git a/android/net/IpSecSpiResponse.java b/android/net/IpSecSpiResponse.java
new file mode 100644
index 0000000..f99e570
--- /dev/null
+++ b/android/net/IpSecSpiResponse.java
@@ -0,0 +1,78 @@
+/*
+ * 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 android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class is used to return an SPI and corresponding status from the IpSecService to an
+ * IpSecManager.SecurityParameterIndex.
+ *
+ * @hide
+ */
+public final class IpSecSpiResponse implements Parcelable {
+ private static final String TAG = "IpSecSpiResponse";
+
+ public final int resourceId;
+ public final int status;
+ public final int spi;
+ // Parcelable Methods
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(status);
+ out.writeInt(resourceId);
+ out.writeInt(spi);
+ }
+
+ public IpSecSpiResponse(int inStatus, int inResourceId, int inSpi) {
+ status = inStatus;
+ resourceId = inResourceId;
+ spi = inSpi;
+ }
+
+ public IpSecSpiResponse(int inStatus) {
+ if (inStatus == IpSecManager.Status.OK) {
+ throw new IllegalArgumentException("Valid status implies other args must be provided");
+ }
+ status = inStatus;
+ resourceId = IpSecManager.INVALID_RESOURCE_ID;
+ spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
+ }
+
+ private IpSecSpiResponse(Parcel in) {
+ status = in.readInt();
+ resourceId = in.readInt();
+ spi = in.readInt();
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<IpSecSpiResponse> CREATOR =
+ new Parcelable.Creator<IpSecSpiResponse>() {
+ public IpSecSpiResponse createFromParcel(Parcel in) {
+ return new IpSecSpiResponse(in);
+ }
+
+ public IpSecSpiResponse[] newArray(int size) {
+ return new IpSecSpiResponse[size];
+ }
+ };
+}
diff --git a/android/net/IpSecTransform.java b/android/net/IpSecTransform.java
new file mode 100644
index 0000000..36111f2
--- /dev/null
+++ b/android/net/IpSecTransform.java
@@ -0,0 +1,526 @@
+/*
+ * 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 android.net;
+
+import static android.net.IpSecManager.INVALID_RESOURCE_ID;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresFeature;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import dalvik.system.CloseGuard;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.InetAddress;
+
+/**
+ * This class represents a transform, which roughly corresponds to an IPsec Security Association.
+ *
+ * <p>Transforms are created using {@link IpSecTransform.Builder}. Each {@code IpSecTransform}
+ * object encapsulates the properties and state of an IPsec security association. That includes,
+ * but is not limited to, algorithm choice, key material, and allocated system resources.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
+ * Internet Protocol</a>
+ */
+public final class IpSecTransform implements AutoCloseable {
+ private static final String TAG = "IpSecTransform";
+
+ /** @hide */
+ public static final int MODE_TRANSPORT = 0;
+
+ /** @hide */
+ public static final int MODE_TUNNEL = 1;
+
+ /** @hide */
+ public static final int ENCAP_NONE = 0;
+
+ /**
+ * IPsec traffic will be encapsulated within UDP, but with 8 zero-value bytes between the UDP
+ * header and payload. This prevents traffic from being interpreted as ESP or IKEv2.
+ *
+ * @hide
+ */
+ public static final int ENCAP_ESPINUDP_NON_IKE = 1;
+
+ /**
+ * IPsec traffic will be encapsulated within UDP as per
+ * <a href="https://tools.ietf.org/html/rfc3948">RFC 3498</a>.
+ *
+ * @hide
+ */
+ public static final int ENCAP_ESPINUDP = 2;
+
+ /** @hide */
+ @IntDef(value = {ENCAP_NONE, ENCAP_ESPINUDP, ENCAP_ESPINUDP_NON_IKE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EncapType {}
+
+ /** @hide */
+ @VisibleForTesting
+ public IpSecTransform(Context context, IpSecConfig config) {
+ mContext = context;
+ mConfig = new IpSecConfig(config);
+ mResourceId = INVALID_RESOURCE_ID;
+ }
+
+ private IIpSecService getIpSecService() {
+ IBinder b = ServiceManager.getService(android.content.Context.IPSEC_SERVICE);
+ if (b == null) {
+ throw new RemoteException("Failed to connect to IpSecService")
+ .rethrowAsRuntimeException();
+ }
+
+ return IIpSecService.Stub.asInterface(b);
+ }
+
+ /**
+ * Checks the result status and throws an appropriate exception if the status is not Status.OK.
+ */
+ private void checkResultStatus(int status)
+ throws IOException, IpSecManager.ResourceUnavailableException,
+ IpSecManager.SpiUnavailableException {
+ switch (status) {
+ case IpSecManager.Status.OK:
+ return;
+ // TODO: Pass Error string back from bundle so that errors can be more specific
+ case IpSecManager.Status.RESOURCE_UNAVAILABLE:
+ throw new IpSecManager.ResourceUnavailableException(
+ "Failed to allocate a new IpSecTransform");
+ case IpSecManager.Status.SPI_UNAVAILABLE:
+ Log.wtf(TAG, "Attempting to use an SPI that was somehow not reserved");
+ // Fall through
+ default:
+ throw new IllegalStateException(
+ "Failed to Create a Transform with status code " + status);
+ }
+ }
+
+ private IpSecTransform activate()
+ throws IOException, IpSecManager.ResourceUnavailableException,
+ IpSecManager.SpiUnavailableException {
+ synchronized (this) {
+ try {
+ IIpSecService svc = getIpSecService();
+ IpSecTransformResponse result = svc.createTransform(
+ mConfig, new Binder(), mContext.getOpPackageName());
+ int status = result.status;
+ checkResultStatus(status);
+ mResourceId = result.resourceId;
+ Log.d(TAG, "Added Transform with Id " + mResourceId);
+ mCloseGuard.open("build");
+ } catch (ServiceSpecificException e) {
+ throw IpSecManager.rethrowUncheckedExceptionFromServiceSpecificException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ return this;
+ }
+
+ /**
+ * Equals method used for testing
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static boolean equals(IpSecTransform lhs, IpSecTransform rhs) {
+ if (lhs == null || rhs == null) return (lhs == rhs);
+ return IpSecConfig.equals(lhs.getConfig(), rhs.getConfig())
+ && lhs.mResourceId == rhs.mResourceId;
+ }
+
+ /**
+ * Deactivate this {@code IpSecTransform} and free allocated resources.
+ *
+ * <p>Deactivating a transform while it is still applied to a socket will result in errors on
+ * that socket. Make sure to remove transforms by calling {@link
+ * IpSecManager#removeTransportModeTransforms}. Note, removing an {@code IpSecTransform} from a
+ * socket will not deactivate it (because one transform may be applied to multiple sockets).
+ *
+ * <p>It is safe to call this method on a transform that has already been deactivated.
+ */
+ public void close() {
+ Log.d(TAG, "Removing Transform with Id " + mResourceId);
+
+ // Always safe to attempt cleanup
+ if (mResourceId == INVALID_RESOURCE_ID) {
+ mCloseGuard.close();
+ return;
+ }
+ try {
+ IIpSecService svc = getIpSecService();
+ svc.deleteTransform(mResourceId);
+ stopNattKeepalive();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } catch (Exception e) {
+ // On close we swallow all random exceptions since failure to close is not
+ // actionable by the user.
+ Log.e(TAG, "Failed to close " + this + ", Exception=" + e);
+ } finally {
+ mResourceId = INVALID_RESOURCE_ID;
+ mCloseGuard.close();
+ }
+ }
+
+ /** Check that the transform was closed properly. */
+ @Override
+ protected void finalize() throws Throwable {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ close();
+ }
+
+ /* Package */
+ IpSecConfig getConfig() {
+ return mConfig;
+ }
+
+ private final IpSecConfig mConfig;
+ private int mResourceId;
+ private final Context mContext;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+ private ConnectivityManager.PacketKeepalive mKeepalive;
+ private Handler mCallbackHandler;
+ private final ConnectivityManager.PacketKeepaliveCallback mKeepaliveCallback =
+ new ConnectivityManager.PacketKeepaliveCallback() {
+
+ @Override
+ public void onStarted() {
+ synchronized (this) {
+ mCallbackHandler.post(() -> mUserKeepaliveCallback.onStarted());
+ }
+ }
+
+ @Override
+ public void onStopped() {
+ synchronized (this) {
+ mKeepalive = null;
+ mCallbackHandler.post(() -> mUserKeepaliveCallback.onStopped());
+ }
+ }
+
+ @Override
+ public void onError(int error) {
+ synchronized (this) {
+ mKeepalive = null;
+ mCallbackHandler.post(() -> mUserKeepaliveCallback.onError(error));
+ }
+ }
+ };
+
+ private NattKeepaliveCallback mUserKeepaliveCallback;
+
+ /** @hide */
+ @VisibleForTesting
+ public int getResourceId() {
+ return mResourceId;
+ }
+
+ /**
+ * A callback class to provide status information regarding a NAT-T keepalive session
+ *
+ * <p>Use this callback to receive status information regarding a NAT-T keepalive session
+ * by registering it when calling {@link #startNattKeepalive}.
+ *
+ * @hide
+ */
+ public static class NattKeepaliveCallback {
+ /** The specified {@code Network} is not connected. */
+ public static final int ERROR_INVALID_NETWORK = 1;
+ /** The hardware does not support this request. */
+ public static final int ERROR_HARDWARE_UNSUPPORTED = 2;
+ /** The hardware returned an error. */
+ public static final int ERROR_HARDWARE_ERROR = 3;
+
+ /** The requested keepalive was successfully started. */
+ public void onStarted() {}
+ /** The keepalive was successfully stopped. */
+ public void onStopped() {}
+ /** An error occurred. */
+ public void onError(int error) {}
+ }
+
+ /**
+ * Start a NAT-T keepalive session for the current transform.
+ *
+ * For a transform that is using UDP encapsulated IPv4, NAT-T offloading provides
+ * a power efficient mechanism of sending NAT-T packets at a specified interval.
+ *
+ * @param userCallback a {@link #NattKeepaliveCallback} to receive asynchronous status
+ * information about the requested NAT-T keepalive session.
+ * @param intervalSeconds the interval between NAT-T keepalives being sent. The
+ * the allowed range is between 20 and 3600 seconds.
+ * @param handler a handler on which to post callbacks when received.
+ *
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_IPSEC_TUNNELS,
+ android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD
+ })
+ public void startNattKeepalive(@NonNull NattKeepaliveCallback userCallback,
+ int intervalSeconds, @NonNull Handler handler) throws IOException {
+ checkNotNull(userCallback);
+ if (intervalSeconds < 20 || intervalSeconds > 3600) {
+ throw new IllegalArgumentException("Invalid NAT-T keepalive interval");
+ }
+ checkNotNull(handler);
+ if (mResourceId == INVALID_RESOURCE_ID) {
+ throw new IllegalStateException(
+ "Packet keepalive cannot be started for an inactive transform");
+ }
+
+ synchronized (mKeepaliveCallback) {
+ if (mKeepaliveCallback != null) {
+ throw new IllegalStateException("Keepalive already active");
+ }
+
+ mUserKeepaliveCallback = userCallback;
+ ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ mKeepalive = cm.startNattKeepalive(
+ mConfig.getNetwork(), intervalSeconds, mKeepaliveCallback,
+ NetworkUtils.numericToInetAddress(mConfig.getSourceAddress()),
+ 4500, // FIXME urgently, we need to get the port number from the Encap socket
+ NetworkUtils.numericToInetAddress(mConfig.getDestinationAddress()));
+ mCallbackHandler = handler;
+ }
+ }
+
+ /**
+ * Stop an ongoing NAT-T keepalive session.
+ *
+ * Calling this API will request that an ongoing NAT-T keepalive session be terminated.
+ * If this API is not called when a Transform is closed, the underlying NAT-T session will
+ * be terminated automatically.
+ *
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_IPSEC_TUNNELS,
+ android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD
+ })
+ public void stopNattKeepalive() {
+ synchronized (mKeepaliveCallback) {
+ if (mKeepalive == null) {
+ Log.e(TAG, "No active keepalive to stop");
+ return;
+ }
+ mKeepalive.stop();
+ }
+ }
+
+ /** This class is used to build {@link IpSecTransform} objects. */
+ public static class Builder {
+ private Context mContext;
+ private IpSecConfig mConfig;
+
+ /**
+ * Set the encryption algorithm.
+ *
+ * <p>Encryption is mutually exclusive with authenticated encryption.
+ *
+ * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied.
+ */
+ @NonNull
+ public IpSecTransform.Builder setEncryption(@NonNull IpSecAlgorithm algo) {
+ // TODO: throw IllegalArgumentException if algo is not an encryption algorithm.
+ Preconditions.checkNotNull(algo);
+ mConfig.setEncryption(algo);
+ return this;
+ }
+
+ /**
+ * Set the authentication (integrity) algorithm.
+ *
+ * <p>Authentication is mutually exclusive with authenticated encryption.
+ *
+ * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied.
+ */
+ @NonNull
+ public IpSecTransform.Builder setAuthentication(@NonNull IpSecAlgorithm algo) {
+ // TODO: throw IllegalArgumentException if algo is not an authentication algorithm.
+ Preconditions.checkNotNull(algo);
+ mConfig.setAuthentication(algo);
+ return this;
+ }
+
+ /**
+ * Set the authenticated encryption algorithm.
+ *
+ * <p>The Authenticated Encryption (AE) class of algorithms are also known as
+ * Authenticated Encryption with Associated Data (AEAD) algorithms, or Combined mode
+ * algorithms (as referred to in
+ * <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>).
+ *
+ * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+ *
+ * @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to
+ * be applied.
+ */
+ @NonNull
+ public IpSecTransform.Builder setAuthenticatedEncryption(@NonNull IpSecAlgorithm algo) {
+ Preconditions.checkNotNull(algo);
+ mConfig.setAuthenticatedEncryption(algo);
+ return this;
+ }
+
+ /**
+ * Add UDP encapsulation to an IPv4 transform.
+ *
+ * <p>This allows IPsec traffic to pass through a NAT.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc3948">RFC 3948, UDP Encapsulation of IPsec
+ * ESP Packets</a>
+ * @see <a href="https://tools.ietf.org/html/rfc7296#section-2.23">RFC 7296 section 2.23,
+ * NAT Traversal of IKEv2</a>
+ * @param localSocket a socket for sending and receiving encapsulated traffic
+ * @param remotePort the UDP port number of the remote host that will send and receive
+ * encapsulated traffic. In the case of IKEv2, this should be port 4500.
+ */
+ @NonNull
+ public IpSecTransform.Builder setIpv4Encapsulation(
+ @NonNull IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
+ Preconditions.checkNotNull(localSocket);
+ mConfig.setEncapType(ENCAP_ESPINUDP);
+ if (localSocket.getResourceId() == INVALID_RESOURCE_ID) {
+ throw new IllegalArgumentException("Invalid UdpEncapsulationSocket");
+ }
+ mConfig.setEncapSocketResourceId(localSocket.getResourceId());
+ mConfig.setEncapRemotePort(remotePort);
+ return this;
+ }
+
+ /**
+ * Build a transport mode {@link IpSecTransform}.
+ *
+ * <p>This builds and activates a transport mode transform. Note that an active transform
+ * will not affect any network traffic until it has been applied to one or more sockets.
+ *
+ * @see IpSecManager#applyTransportModeTransform
+ * @param sourceAddress the source {@code InetAddress} of traffic on sockets that will use
+ * this transform; this address must belong to the Network used by all sockets that
+ * utilize this transform; if provided, then only traffic originating from the
+ * specified source address will be processed.
+ * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
+ * traffic
+ * @throws IllegalArgumentException indicating that a particular combination of transform
+ * properties is invalid
+ * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms
+ * are active
+ * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI
+ * collides with an existing transform
+ * @throws IOException indicating other errors
+ */
+ @NonNull
+ public IpSecTransform buildTransportModeTransform(
+ @NonNull InetAddress sourceAddress,
+ @NonNull IpSecManager.SecurityParameterIndex spi)
+ throws IpSecManager.ResourceUnavailableException,
+ IpSecManager.SpiUnavailableException, IOException {
+ Preconditions.checkNotNull(sourceAddress);
+ Preconditions.checkNotNull(spi);
+ if (spi.getResourceId() == INVALID_RESOURCE_ID) {
+ throw new IllegalArgumentException("Invalid SecurityParameterIndex");
+ }
+ mConfig.setMode(MODE_TRANSPORT);
+ mConfig.setSourceAddress(sourceAddress.getHostAddress());
+ mConfig.setSpiResourceId(spi.getResourceId());
+ // FIXME: modifying a builder after calling build can change the built transform.
+ return new IpSecTransform(mContext, mConfig).activate();
+ }
+
+ /**
+ * Build and return an {@link IpSecTransform} object as a Tunnel Mode Transform. Some
+ * parameters have interdependencies that are checked at build time.
+ *
+ * @param sourceAddress the {@link InetAddress} that provides the source address for this
+ * IPsec tunnel. This is almost certainly an address belonging to the {@link Network}
+ * that will originate the traffic, which is set as the {@link #setUnderlyingNetwork}.
+ * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
+ * traffic
+ * @throws IllegalArgumentException indicating that a particular combination of transform
+ * properties is invalid.
+ * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms
+ * are active
+ * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI
+ * collides with an existing transform
+ * @throws IOException indicating other errors
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+ @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
+ public IpSecTransform buildTunnelModeTransform(
+ @NonNull InetAddress sourceAddress,
+ @NonNull IpSecManager.SecurityParameterIndex spi)
+ throws IpSecManager.ResourceUnavailableException,
+ IpSecManager.SpiUnavailableException, IOException {
+ Preconditions.checkNotNull(sourceAddress);
+ Preconditions.checkNotNull(spi);
+ if (spi.getResourceId() == INVALID_RESOURCE_ID) {
+ throw new IllegalArgumentException("Invalid SecurityParameterIndex");
+ }
+ mConfig.setMode(MODE_TUNNEL);
+ mConfig.setSourceAddress(sourceAddress.getHostAddress());
+ mConfig.setSpiResourceId(spi.getResourceId());
+ return new IpSecTransform(mContext, mConfig).activate();
+ }
+
+ /**
+ * Create a new IpSecTransform.Builder.
+ *
+ * @param context current context
+ */
+ public Builder(@NonNull Context context) {
+ Preconditions.checkNotNull(context);
+ mContext = context;
+ mConfig = new IpSecConfig();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("IpSecTransform{resourceId=")
+ .append(mResourceId)
+ .append("}")
+ .toString();
+ }
+}
diff --git a/android/net/IpSecTransformResponse.java b/android/net/IpSecTransformResponse.java
new file mode 100644
index 0000000..a384889
--- /dev/null
+++ b/android/net/IpSecTransformResponse.java
@@ -0,0 +1,73 @@
+/*
+ * 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 android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class is used to return an IpSecTransform resource Id and and corresponding status from the
+ * IpSecService to an IpSecTransform object.
+ *
+ * @hide
+ */
+public final class IpSecTransformResponse implements Parcelable {
+ private static final String TAG = "IpSecTransformResponse";
+
+ public final int resourceId;
+ public final int status;
+ // Parcelable Methods
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(status);
+ out.writeInt(resourceId);
+ }
+
+ public IpSecTransformResponse(int inStatus) {
+ if (inStatus == IpSecManager.Status.OK) {
+ throw new IllegalArgumentException("Valid status implies other args must be provided");
+ }
+ status = inStatus;
+ resourceId = IpSecManager.INVALID_RESOURCE_ID;
+ }
+
+ public IpSecTransformResponse(int inStatus, int inResourceId) {
+ status = inStatus;
+ resourceId = inResourceId;
+ }
+
+ private IpSecTransformResponse(Parcel in) {
+ status = in.readInt();
+ resourceId = in.readInt();
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<IpSecTransformResponse> CREATOR =
+ new Parcelable.Creator<IpSecTransformResponse>() {
+ public IpSecTransformResponse createFromParcel(Parcel in) {
+ return new IpSecTransformResponse(in);
+ }
+
+ public IpSecTransformResponse[] newArray(int size) {
+ return new IpSecTransformResponse[size];
+ }
+ };
+}
diff --git a/android/net/IpSecTunnelInterfaceResponse.java b/android/net/IpSecTunnelInterfaceResponse.java
new file mode 100644
index 0000000..e3411e0
--- /dev/null
+++ b/android/net/IpSecTunnelInterfaceResponse.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class is used to return an IpSecTunnelInterface resource Id and and corresponding status
+ * from the IpSecService to an IpSecTunnelInterface object.
+ *
+ * @hide
+ */
+public final class IpSecTunnelInterfaceResponse implements Parcelable {
+ private static final String TAG = "IpSecTunnelInterfaceResponse";
+
+ public final int resourceId;
+ public final String interfaceName;
+ public final int status;
+ // Parcelable Methods
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(status);
+ out.writeInt(resourceId);
+ out.writeString(interfaceName);
+ }
+
+ public IpSecTunnelInterfaceResponse(int inStatus) {
+ if (inStatus == IpSecManager.Status.OK) {
+ throw new IllegalArgumentException("Valid status implies other args must be provided");
+ }
+ status = inStatus;
+ resourceId = IpSecManager.INVALID_RESOURCE_ID;
+ interfaceName = "";
+ }
+
+ public IpSecTunnelInterfaceResponse(int inStatus, int inResourceId, String inInterfaceName) {
+ status = inStatus;
+ resourceId = inResourceId;
+ interfaceName = inInterfaceName;
+ }
+
+ private IpSecTunnelInterfaceResponse(Parcel in) {
+ status = in.readInt();
+ resourceId = in.readInt();
+ interfaceName = in.readString();
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<IpSecTunnelInterfaceResponse> CREATOR =
+ new Parcelable.Creator<IpSecTunnelInterfaceResponse>() {
+ public IpSecTunnelInterfaceResponse createFromParcel(Parcel in) {
+ return new IpSecTunnelInterfaceResponse(in);
+ }
+
+ public IpSecTunnelInterfaceResponse[] newArray(int size) {
+ return new IpSecTunnelInterfaceResponse[size];
+ }
+ };
+}
diff --git a/android/net/IpSecUdpEncapResponse.java b/android/net/IpSecUdpEncapResponse.java
new file mode 100644
index 0000000..4e7ba9b
--- /dev/null
+++ b/android/net/IpSecUdpEncapResponse.java
@@ -0,0 +1,96 @@
+/*
+ * 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 android.net;
+
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/**
+ * This class is used to return a UDP Socket and corresponding status from the IpSecService to an
+ * IpSecManager.UdpEncapsulationSocket.
+ *
+ * @hide
+ */
+public final class IpSecUdpEncapResponse implements Parcelable {
+ private static final String TAG = "IpSecUdpEncapResponse";
+
+ public final int resourceId;
+ public final int port;
+ public final int status;
+ // There is a weird asymmetry with FileDescriptor: you can write a FileDescriptor
+ // but you read a ParcelFileDescriptor. To circumvent this, when we receive a FD
+ // from the user, we immediately create a ParcelFileDescriptor DUP, which we invalidate
+ // on writeParcel() by setting the flag to do close-on-write.
+ // TODO: tests to ensure this doesn't leak
+ public final ParcelFileDescriptor fileDescriptor;
+
+ // Parcelable Methods
+
+ @Override
+ public int describeContents() {
+ return (fileDescriptor != null) ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(status);
+ out.writeInt(resourceId);
+ out.writeInt(port);
+ out.writeParcelable(fileDescriptor, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ }
+
+ public IpSecUdpEncapResponse(int inStatus) {
+ if (inStatus == IpSecManager.Status.OK) {
+ throw new IllegalArgumentException("Valid status implies other args must be provided");
+ }
+ status = inStatus;
+ resourceId = IpSecManager.INVALID_RESOURCE_ID;
+ port = -1;
+ fileDescriptor = null; // yes I know it's redundant, but readability
+ }
+
+ public IpSecUdpEncapResponse(int inStatus, int inResourceId, int inPort, FileDescriptor inFd)
+ throws IOException {
+ if (inStatus == IpSecManager.Status.OK && inFd == null) {
+ throw new IllegalArgumentException("Valid status implies FD must be non-null");
+ }
+ status = inStatus;
+ resourceId = inResourceId;
+ port = inPort;
+ fileDescriptor = (status == IpSecManager.Status.OK) ? ParcelFileDescriptor.dup(inFd) : null;
+ }
+
+ private IpSecUdpEncapResponse(Parcel in) {
+ status = in.readInt();
+ resourceId = in.readInt();
+ port = in.readInt();
+ fileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<IpSecUdpEncapResponse> CREATOR =
+ new Parcelable.Creator<IpSecUdpEncapResponse>() {
+ public IpSecUdpEncapResponse createFromParcel(Parcel in) {
+ return new IpSecUdpEncapResponse(in);
+ }
+
+ public IpSecUdpEncapResponse[] newArray(int size) {
+ return new IpSecUdpEncapResponse[size];
+ }
+ };
+}
diff --git a/android/net/KeepalivePacketData.java b/android/net/KeepalivePacketData.java
new file mode 100644
index 0000000..9b8b732
--- /dev/null
+++ b/android/net/KeepalivePacketData.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2015 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;
+
+import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
+import static android.net.SocketKeepalive.ERROR_INVALID_PORT;
+
+import android.net.SocketKeepalive.InvalidPacketException;
+import android.net.util.IpUtils;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.net.InetAddress;
+
+/**
+ * Represents the actual packets that are sent by the
+ * {@link android.net.SocketKeepalive} API.
+ *
+ * @hide
+ */
+public class KeepalivePacketData implements Parcelable {
+ private static final String TAG = "KeepalivePacketData";
+
+ /** Source IP address */
+ public final InetAddress srcAddress;
+
+ /** Destination IP address */
+ public final InetAddress dstAddress;
+
+ /** Source port */
+ public final int srcPort;
+
+ /** Destination port */
+ public final int dstPort;
+
+ /** Packet data. A raw byte string of packet data, not including the link-layer header. */
+ private final byte[] mPacket;
+
+ protected static final int IPV4_HEADER_LENGTH = 20;
+ protected static final int UDP_HEADER_LENGTH = 8;
+
+ // This should only be constructed via static factory methods, such as
+ // nattKeepalivePacket
+ protected KeepalivePacketData(InetAddress srcAddress, int srcPort,
+ InetAddress dstAddress, int dstPort, byte[] data) throws InvalidPacketException {
+ this.srcAddress = srcAddress;
+ this.dstAddress = dstAddress;
+ this.srcPort = srcPort;
+ this.dstPort = dstPort;
+ this.mPacket = data;
+
+ // Check we have two IP addresses of the same family.
+ if (srcAddress == null || dstAddress == null || !srcAddress.getClass().getName()
+ .equals(dstAddress.getClass().getName())) {
+ Log.e(TAG, "Invalid or mismatched InetAddresses in KeepalivePacketData");
+ throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
+ }
+
+ // Check the ports.
+ if (!IpUtils.isValidUdpOrTcpPort(srcPort) || !IpUtils.isValidUdpOrTcpPort(dstPort)) {
+ Log.e(TAG, "Invalid ports in KeepalivePacketData");
+ throw new InvalidPacketException(ERROR_INVALID_PORT);
+ }
+ }
+
+ public byte[] getPacket() {
+ return mPacket.clone();
+ }
+
+ /* Parcelable Implementation */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Write to parcel */
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(srcAddress.getHostAddress());
+ out.writeString(dstAddress.getHostAddress());
+ out.writeInt(srcPort);
+ out.writeInt(dstPort);
+ out.writeByteArray(mPacket);
+ }
+
+ protected KeepalivePacketData(Parcel in) {
+ srcAddress = NetworkUtils.numericToInetAddress(in.readString());
+ dstAddress = NetworkUtils.numericToInetAddress(in.readString());
+ srcPort = in.readInt();
+ dstPort = in.readInt();
+ mPacket = in.createByteArray();
+ }
+
+ /** Parcelable Creator */
+ public static final @android.annotation.NonNull Parcelable.Creator<KeepalivePacketData> CREATOR =
+ new Parcelable.Creator<KeepalivePacketData>() {
+ public KeepalivePacketData createFromParcel(Parcel in) {
+ return new KeepalivePacketData(in);
+ }
+
+ public KeepalivePacketData[] newArray(int size) {
+ return new KeepalivePacketData[size];
+ }
+ };
+
+}
diff --git a/android/net/LinkAddress.java b/android/net/LinkAddress.java
new file mode 100644
index 0000000..93dd2e4
--- /dev/null
+++ b/android/net/LinkAddress.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2010 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;
+
+import static android.system.OsConstants.IFA_F_DADFAILED;
+import static android.system.OsConstants.IFA_F_DEPRECATED;
+import static android.system.OsConstants.IFA_F_OPTIMISTIC;
+import static android.system.OsConstants.IFA_F_TENTATIVE;
+import static android.system.OsConstants.RT_SCOPE_HOST;
+import static android.system.OsConstants.RT_SCOPE_LINK;
+import static android.system.OsConstants.RT_SCOPE_SITE;
+import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Pair;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Identifies an IP address on a network link.
+ *
+ * A {@code LinkAddress} consists of:
+ * <ul>
+ * <li>An IP address and prefix length (e.g., {@code 2001:db8::1/64} or {@code 192.0.2.1/24}).
+ * The address must be unicast, as multicast addresses cannot be assigned to interfaces.
+ * <li>Address flags: A bitmask of {@code OsConstants.IFA_F_*} values representing properties
+ * of the address (e.g., {@code android.system.OsConstants.IFA_F_OPTIMISTIC}).
+ * <li>Address scope: One of the {@code OsConstants.IFA_F_*} values; defines the scope in which
+ * the address is unique (e.g.,
+ * {@code android.system.OsConstants.RT_SCOPE_LINK} or
+ * {@code android.system.OsConstants.RT_SCOPE_UNIVERSE}).
+ * </ul>
+ */
+public class LinkAddress implements Parcelable {
+ /**
+ * IPv4 or IPv6 address.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private InetAddress address;
+
+ /**
+ * Prefix length.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private int prefixLength;
+
+ /**
+ * Address flags. A bitmask of IFA_F_* values.
+ */
+ private int flags;
+
+ /**
+ * Address scope. One of the RT_SCOPE_* constants.
+ */
+ private int scope;
+
+ /**
+ * Utility function to determines the scope of a unicast address. Per RFC 4291 section 2.5 and
+ * RFC 6724 section 3.2.
+ * @hide
+ */
+ private static int scopeForUnicastAddress(InetAddress addr) {
+ if (addr.isAnyLocalAddress()) {
+ return RT_SCOPE_HOST;
+ }
+
+ if (addr.isLoopbackAddress() || addr.isLinkLocalAddress()) {
+ return RT_SCOPE_LINK;
+ }
+
+ // isSiteLocalAddress() returns true for private IPv4 addresses, but RFC 6724 section 3.2
+ // says that they are assigned global scope.
+ if (!(addr instanceof Inet4Address) && addr.isSiteLocalAddress()) {
+ return RT_SCOPE_SITE;
+ }
+
+ return RT_SCOPE_UNIVERSE;
+ }
+
+ /**
+ * Utility function to check if |address| is a Unique Local IPv6 Unicast Address
+ * (a.k.a. "ULA"; RFC 4193).
+ *
+ * Per RFC 4193 section 8, fc00::/7 identifies these addresses.
+ */
+ private boolean isIpv6ULA() {
+ if (isIpv6()) {
+ byte[] bytes = address.getAddress();
+ return ((bytes[0] & (byte)0xfe) == (byte)0xfc);
+ }
+ return false;
+ }
+
+ /**
+ * @return true if the address is IPv6.
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public boolean isIpv6() {
+ return address instanceof Inet6Address;
+ }
+
+ /**
+ * For backward compatibility.
+ * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely
+ * just yet.
+ * @return true if the address is IPv6.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public boolean isIPv6() {
+ return isIpv6();
+ }
+
+ /**
+ * @return true if the address is IPv4 or is a mapped IPv4 address.
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public boolean isIpv4() {
+ return address instanceof Inet4Address;
+ }
+
+ /**
+ * Utility function for the constructors.
+ */
+ private void init(InetAddress address, int prefixLength, int flags, int scope) {
+ if (address == null ||
+ address.isMulticastAddress() ||
+ prefixLength < 0 ||
+ (address instanceof Inet4Address && prefixLength > 32) ||
+ (prefixLength > 128)) {
+ throw new IllegalArgumentException("Bad LinkAddress params " + address +
+ "/" + prefixLength);
+ }
+ this.address = address;
+ this.prefixLength = prefixLength;
+ this.flags = flags;
+ this.scope = scope;
+ }
+
+ /**
+ * Constructs a new {@code LinkAddress} from an {@code InetAddress} and prefix length, with
+ * the specified flags and scope. Flags and scope are not checked for validity.
+ * @param address The IP address.
+ * @param prefixLength The prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6).
+ * @param flags A bitmask of {@code IFA_F_*} values representing properties of the address.
+ * @param scope An integer defining the scope in which the address is unique (e.g.,
+ * {@link OsConstants#RT_SCOPE_LINK} or {@link OsConstants#RT_SCOPE_SITE}).
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public LinkAddress(@NonNull InetAddress address, @IntRange(from = 0, to = 128) int prefixLength,
+ int flags, int scope) {
+ init(address, prefixLength, flags, scope);
+ }
+
+ /**
+ * Constructs a new {@code LinkAddress} from an {@code InetAddress} and a prefix length.
+ * The flags are set to zero and the scope is determined from the address.
+ * @param address The IP address.
+ * @param prefixLength The prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6).
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public LinkAddress(@NonNull InetAddress address,
+ @IntRange(from = 0, to = 128) int prefixLength) {
+ this(address, prefixLength, 0, 0);
+ this.scope = scopeForUnicastAddress(address);
+ }
+
+ /**
+ * Constructs a new {@code LinkAddress} from an {@code InterfaceAddress}.
+ * The flags are set to zero and the scope is determined from the address.
+ * @param interfaceAddress The interface address.
+ * @hide
+ */
+ public LinkAddress(@NonNull InterfaceAddress interfaceAddress) {
+ this(interfaceAddress.getAddress(),
+ interfaceAddress.getNetworkPrefixLength());
+ }
+
+ /**
+ * Constructs a new {@code LinkAddress} from a string such as "192.0.2.5/24" or
+ * "2001:db8::1/64". The flags are set to zero and the scope is determined from the address.
+ * @param address The string to parse.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public LinkAddress(@NonNull String address) {
+ this(address, 0, 0);
+ this.scope = scopeForUnicastAddress(this.address);
+ }
+
+ /**
+ * Constructs a new {@code LinkAddress} from a string such as "192.0.2.5/24" or
+ * "2001:db8::1/64", with the specified flags and scope.
+ * @param address The string to parse.
+ * @param flags The address flags.
+ * @param scope The address scope.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public LinkAddress(@NonNull String address, int flags, int scope) {
+ // This may throw an IllegalArgumentException; catching it is the caller's responsibility.
+ // TODO: consider rejecting mapped IPv4 addresses such as "::ffff:192.0.2.5/24".
+ Pair<InetAddress, Integer> ipAndMask = NetworkUtils.parseIpAndMask(address);
+ init(ipAndMask.first, ipAndMask.second, flags, scope);
+ }
+
+ /**
+ * Returns a string representation of this address, such as "192.0.2.1/24" or "2001:db8::1/64".
+ * The string representation does not contain the flags and scope, just the address and prefix
+ * length.
+ */
+ @Override
+ public String toString() {
+ return address.getHostAddress() + "/" + prefixLength;
+ }
+
+ /**
+ * Compares this {@code LinkAddress} instance against {@code obj}. Two addresses are equal if
+ * their address, prefix length, flags and scope are equal. Thus, for example, two addresses
+ * that have the same address and prefix length are not equal if one of them is deprecated and
+ * the other is not.
+ *
+ * @param obj the object to be tested for equality.
+ * @return {@code true} if both objects are equal, {@code false} otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof LinkAddress)) {
+ return false;
+ }
+ LinkAddress linkAddress = (LinkAddress) obj;
+ return this.address.equals(linkAddress.address) &&
+ this.prefixLength == linkAddress.prefixLength &&
+ this.flags == linkAddress.flags &&
+ this.scope == linkAddress.scope;
+ }
+
+ /**
+ * Returns a hashcode for this address.
+ */
+ @Override
+ public int hashCode() {
+ return address.hashCode() + 11 * prefixLength + 19 * flags + 43 * scope;
+ }
+
+ /**
+ * Determines whether this {@code LinkAddress} and the provided {@code LinkAddress}
+ * represent the same address. Two {@code LinkAddresses} represent the same address
+ * if they have the same IP address and prefix length, even if their properties are
+ * different.
+ *
+ * @param other the {@code LinkAddress} to compare to.
+ * @return {@code true} if both objects have the same address and prefix length, {@code false}
+ * otherwise.
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public boolean isSameAddressAs(@Nullable LinkAddress other) {
+ if (other == null) {
+ return false;
+ }
+ return address.equals(other.address) && prefixLength == other.prefixLength;
+ }
+
+ /**
+ * Returns the {@link InetAddress} of this {@code LinkAddress}.
+ */
+ public InetAddress getAddress() {
+ return address;
+ }
+
+ /**
+ * Returns the prefix length of this {@code LinkAddress}.
+ */
+ @IntRange(from = 0, to = 128)
+ public int getPrefixLength() {
+ return prefixLength;
+ }
+
+ /**
+ * Returns the prefix length of this {@code LinkAddress}.
+ * TODO: Delete all callers and remove in favour of getPrefixLength().
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @IntRange(from = 0, to = 128)
+ public int getNetworkPrefixLength() {
+ return getPrefixLength();
+ }
+
+ /**
+ * Returns the flags of this {@code LinkAddress}.
+ */
+ public int getFlags() {
+ return flags;
+ }
+
+ /**
+ * Returns the scope of this {@code LinkAddress}.
+ */
+ public int getScope() {
+ return scope;
+ }
+
+ /**
+ * Returns true if this {@code LinkAddress} is global scope and preferred.
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public boolean isGlobalPreferred() {
+ /**
+ * Note that addresses flagged as IFA_F_OPTIMISTIC are
+ * simultaneously flagged as IFA_F_TENTATIVE (when the tentative
+ * state has cleared either DAD has succeeded or failed, and both
+ * flags are cleared regardless).
+ */
+ return (scope == RT_SCOPE_UNIVERSE
+ && !isIpv6ULA()
+ && (flags & (IFA_F_DADFAILED | IFA_F_DEPRECATED)) == 0L
+ && ((flags & IFA_F_TENTATIVE) == 0L || (flags & IFA_F_OPTIMISTIC) != 0L));
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeByteArray(address.getAddress());
+ dest.writeInt(prefixLength);
+ dest.writeInt(this.flags);
+ dest.writeInt(scope);
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ */
+ public static final @android.annotation.NonNull Creator<LinkAddress> CREATOR =
+ new Creator<LinkAddress>() {
+ public LinkAddress createFromParcel(Parcel in) {
+ InetAddress address = null;
+ try {
+ address = InetAddress.getByAddress(in.createByteArray());
+ } catch (UnknownHostException e) {
+ // Nothing we can do here. When we call the constructor, we'll throw an
+ // IllegalArgumentException, because a LinkAddress can't have a null
+ // InetAddress.
+ }
+ int prefixLength = in.readInt();
+ int flags = in.readInt();
+ int scope = in.readInt();
+ return new LinkAddress(address, prefixLength, flags, scope);
+ }
+
+ public LinkAddress[] newArray(int size) {
+ return new LinkAddress[size];
+ }
+ };
+}
diff --git a/android/net/LinkProperties.java b/android/net/LinkProperties.java
new file mode 100644
index 0000000..d3f48ac
--- /dev/null
+++ b/android/net/LinkProperties.java
@@ -0,0 +1,1699 @@
+/*
+ * Copyright (C) 2010 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Objects;
+import java.util.StringJoiner;
+
+/**
+ * Describes the properties of a network link.
+ *
+ * A link represents a connection to a network.
+ * It may have multiple addresses and multiple gateways,
+ * multiple dns servers but only one http proxy and one
+ * network interface.
+ *
+ * Note that this is just a holder of data. Modifying it
+ * does not affect live networks.
+ *
+ */
+public final class LinkProperties implements Parcelable {
+ // The interface described by the network link.
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private String mIfaceName;
+ private final ArrayList<LinkAddress> mLinkAddresses = new ArrayList<>();
+ private final ArrayList<InetAddress> mDnses = new ArrayList<>();
+ // PCSCF addresses are addresses of SIP proxies that only exist for the IMS core service.
+ private final ArrayList<InetAddress> mPcscfs = new ArrayList<InetAddress>();
+ private final ArrayList<InetAddress> mValidatedPrivateDnses = new ArrayList<>();
+ private boolean mUsePrivateDns;
+ private String mPrivateDnsServerName;
+ private String mDomains;
+ private ArrayList<RouteInfo> mRoutes = new ArrayList<>();
+ private ProxyInfo mHttpProxy;
+ private int mMtu;
+ // in the format "rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max"
+ private String mTcpBufferSizes;
+ private IpPrefix mNat64Prefix;
+
+ private static final int MIN_MTU = 68;
+ private static final int MIN_MTU_V6 = 1280;
+ private static final int MAX_MTU = 10000;
+
+ // Stores the properties of links that are "stacked" above this link.
+ // Indexed by interface name to allow modification and to prevent duplicates being added.
+ private Hashtable<String, LinkProperties> mStackedLinks = new Hashtable<>();
+
+ /**
+ * @hide
+ */
+ public static class CompareResult<T> {
+ public final List<T> removed = new ArrayList<>();
+ public final List<T> added = new ArrayList<>();
+
+ public CompareResult() {}
+
+ public CompareResult(Collection<T> oldItems, Collection<T> newItems) {
+ if (oldItems != null) {
+ removed.addAll(oldItems);
+ }
+ if (newItems != null) {
+ for (T newItem : newItems) {
+ if (!removed.remove(newItem)) {
+ added.add(newItem);
+ }
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "removed=[" + TextUtils.join(",", removed)
+ + "] added=[" + TextUtils.join(",", added)
+ + "]";
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public enum ProvisioningChange {
+ @UnsupportedAppUsage
+ STILL_NOT_PROVISIONED,
+ @UnsupportedAppUsage
+ LOST_PROVISIONING,
+ @UnsupportedAppUsage
+ GAINED_PROVISIONING,
+ @UnsupportedAppUsage
+ STILL_PROVISIONED,
+ }
+
+ /**
+ * Compare the provisioning states of two LinkProperties instances.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static ProvisioningChange compareProvisioning(
+ LinkProperties before, LinkProperties after) {
+ if (before.isProvisioned() && after.isProvisioned()) {
+ // On dual-stack networks, DHCPv4 renewals can occasionally fail.
+ // When this happens, IPv6-reachable services continue to function
+ // normally but IPv4-only services (naturally) fail.
+ //
+ // When an application using an IPv4-only service reports a bad
+ // network condition to the framework, attempts to re-validate
+ // the network succeed (since we support IPv6-only networks) and
+ // nothing is changed.
+ //
+ // For users, this is confusing and unexpected behaviour, and is
+ // not necessarily easy to diagnose. Therefore, we treat changing
+ // from a dual-stack network to an IPv6-only network equivalent to
+ // a total loss of provisioning.
+ //
+ // For one such example of this, see b/18867306.
+ //
+ // Additionally, losing IPv6 provisioning can result in TCP
+ // connections getting stuck until timeouts fire and other
+ // baffling failures. Therefore, loss of either IPv4 or IPv6 on a
+ // previously dual-stack network is deemed a lost of provisioning.
+ if ((before.isIpv4Provisioned() && !after.isIpv4Provisioned())
+ || (before.isIpv6Provisioned() && !after.isIpv6Provisioned())) {
+ return ProvisioningChange.LOST_PROVISIONING;
+ }
+ return ProvisioningChange.STILL_PROVISIONED;
+ } else if (before.isProvisioned() && !after.isProvisioned()) {
+ return ProvisioningChange.LOST_PROVISIONING;
+ } else if (!before.isProvisioned() && after.isProvisioned()) {
+ return ProvisioningChange.GAINED_PROVISIONING;
+ } else { // !before.isProvisioned() && !after.isProvisioned()
+ return ProvisioningChange.STILL_NOT_PROVISIONED;
+ }
+ }
+
+ /**
+ * Constructs a new {@code LinkProperties} with default values.
+ */
+ public LinkProperties() {
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public LinkProperties(@Nullable LinkProperties source) {
+ if (source != null) {
+ mIfaceName = source.mIfaceName;
+ mLinkAddresses.addAll(source.mLinkAddresses);
+ mDnses.addAll(source.mDnses);
+ mValidatedPrivateDnses.addAll(source.mValidatedPrivateDnses);
+ mUsePrivateDns = source.mUsePrivateDns;
+ mPrivateDnsServerName = source.mPrivateDnsServerName;
+ mPcscfs.addAll(source.mPcscfs);
+ mDomains = source.mDomains;
+ mRoutes.addAll(source.mRoutes);
+ mHttpProxy = (source.mHttpProxy == null) ? null : new ProxyInfo(source.mHttpProxy);
+ for (LinkProperties l: source.mStackedLinks.values()) {
+ addStackedLink(l);
+ }
+ setMtu(source.mMtu);
+ mTcpBufferSizes = source.mTcpBufferSizes;
+ mNat64Prefix = source.mNat64Prefix;
+ }
+ }
+
+ /**
+ * Sets the interface name for this link. All {@link RouteInfo} already set for this
+ * will have their interface changed to match this new value.
+ *
+ * @param iface The name of the network interface used for this link.
+ */
+ public void setInterfaceName(@Nullable String iface) {
+ mIfaceName = iface;
+ ArrayList<RouteInfo> newRoutes = new ArrayList<>(mRoutes.size());
+ for (RouteInfo route : mRoutes) {
+ newRoutes.add(routeWithInterface(route));
+ }
+ mRoutes = newRoutes;
+ }
+
+ /**
+ * Gets the interface name for this link. May be {@code null} if not set.
+ *
+ * @return The interface name set for this link or {@code null}.
+ */
+ public @Nullable String getInterfaceName() {
+ return mIfaceName;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public @NonNull List<String> getAllInterfaceNames() {
+ List<String> interfaceNames = new ArrayList<>(mStackedLinks.size() + 1);
+ if (mIfaceName != null) interfaceNames.add(mIfaceName);
+ for (LinkProperties stacked: mStackedLinks.values()) {
+ interfaceNames.addAll(stacked.getAllInterfaceNames());
+ }
+ return interfaceNames;
+ }
+
+ /**
+ * Returns all the addresses on this link. We often think of a link having a single address,
+ * however, particularly with Ipv6 several addresses are typical. Note that the
+ * {@code LinkProperties} actually contains {@link LinkAddress} objects which also include
+ * prefix lengths for each address. This is a simplified utility alternative to
+ * {@link LinkProperties#getLinkAddresses}.
+ *
+ * @return An unmodifiable {@link List} of {@link InetAddress} for this link.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public @NonNull List<InetAddress> getAddresses() {
+ final List<InetAddress> addresses = new ArrayList<>();
+ for (LinkAddress linkAddress : mLinkAddresses) {
+ addresses.add(linkAddress.getAddress());
+ }
+ return Collections.unmodifiableList(addresses);
+ }
+
+ /**
+ * Returns all the addresses on this link and all the links stacked above it.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public @NonNull List<InetAddress> getAllAddresses() {
+ List<InetAddress> addresses = new ArrayList<>();
+ for (LinkAddress linkAddress : mLinkAddresses) {
+ addresses.add(linkAddress.getAddress());
+ }
+ for (LinkProperties stacked: mStackedLinks.values()) {
+ addresses.addAll(stacked.getAllAddresses());
+ }
+ return addresses;
+ }
+
+ private int findLinkAddressIndex(LinkAddress address) {
+ for (int i = 0; i < mLinkAddresses.size(); i++) {
+ if (mLinkAddresses.get(i).isSameAddressAs(address)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Adds a {@link LinkAddress} to this {@code LinkProperties} if a {@link LinkAddress} of the
+ * same address/prefix does not already exist. If it does exist it is replaced.
+ * @param address The {@code LinkAddress} to add.
+ * @return true if {@code address} was added or updated, false otherwise.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public boolean addLinkAddress(@NonNull LinkAddress address) {
+ if (address == null) {
+ return false;
+ }
+ int i = findLinkAddressIndex(address);
+ if (i < 0) {
+ // Address was not present. Add it.
+ mLinkAddresses.add(address);
+ return true;
+ } else if (mLinkAddresses.get(i).equals(address)) {
+ // Address was present and has same properties. Do nothing.
+ return false;
+ } else {
+ // Address was present and has different properties. Update it.
+ mLinkAddresses.set(i, address);
+ return true;
+ }
+ }
+
+ /**
+ * Removes a {@link LinkAddress} from this {@code LinkProperties}. Specifically, matches
+ * and {@link LinkAddress} with the same address and prefix.
+ *
+ * @param toRemove A {@link LinkAddress} specifying the address to remove.
+ * @return true if the address was removed, false if it did not exist.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public boolean removeLinkAddress(@NonNull LinkAddress toRemove) {
+ int i = findLinkAddressIndex(toRemove);
+ if (i >= 0) {
+ mLinkAddresses.remove(i);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns all the {@link LinkAddress} on this link. Typically a link will have
+ * one IPv4 address and one or more IPv6 addresses.
+ *
+ * @return An unmodifiable {@link List} of {@link LinkAddress} for this link.
+ */
+ public @NonNull List<LinkAddress> getLinkAddresses() {
+ return Collections.unmodifiableList(mLinkAddresses);
+ }
+
+ /**
+ * Returns all the addresses on this link and all the links stacked above it.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public List<LinkAddress> getAllLinkAddresses() {
+ List<LinkAddress> addresses = new ArrayList<>(mLinkAddresses);
+ for (LinkProperties stacked: mStackedLinks.values()) {
+ addresses.addAll(stacked.getAllLinkAddresses());
+ }
+ return addresses;
+ }
+
+ /**
+ * Replaces the {@link LinkAddress} in this {@code LinkProperties} with
+ * the given {@link Collection} of {@link LinkAddress}.
+ *
+ * @param addresses The {@link Collection} of {@link LinkAddress} to set in this
+ * object.
+ */
+ public void setLinkAddresses(@NonNull Collection<LinkAddress> addresses) {
+ mLinkAddresses.clear();
+ for (LinkAddress address: addresses) {
+ addLinkAddress(address);
+ }
+ }
+
+ /**
+ * Adds the given {@link InetAddress} to the list of DNS servers, if not present.
+ *
+ * @param dnsServer The {@link InetAddress} to add to the list of DNS servers.
+ * @return true if the DNS server was added, false if it was already present.
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public boolean addDnsServer(@NonNull InetAddress dnsServer) {
+ if (dnsServer != null && !mDnses.contains(dnsServer)) {
+ mDnses.add(dnsServer);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Removes the given {@link InetAddress} from the list of DNS servers.
+ *
+ * @param dnsServer The {@link InetAddress} to remove from the list of DNS servers.
+ * @return true if the DNS server was removed, false if it did not exist.
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public boolean removeDnsServer(@NonNull InetAddress dnsServer) {
+ return mDnses.remove(dnsServer);
+ }
+
+ /**
+ * Replaces the DNS servers in this {@code LinkProperties} with
+ * the given {@link Collection} of {@link InetAddress} objects.
+ *
+ * @param dnsServers The {@link Collection} of DNS servers to set in this object.
+ */
+ public void setDnsServers(@NonNull Collection<InetAddress> dnsServers) {
+ mDnses.clear();
+ for (InetAddress dnsServer: dnsServers) {
+ addDnsServer(dnsServer);
+ }
+ }
+
+ /**
+ * Returns all the {@link InetAddress} for DNS servers on this link.
+ *
+ * @return An unmodifiable {@link List} of {@link InetAddress} for DNS servers on
+ * this link.
+ */
+ public @NonNull List<InetAddress> getDnsServers() {
+ return Collections.unmodifiableList(mDnses);
+ }
+
+ /**
+ * Set whether private DNS is currently in use on this network.
+ *
+ * @param usePrivateDns The private DNS state.
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public void setUsePrivateDns(boolean usePrivateDns) {
+ mUsePrivateDns = usePrivateDns;
+ }
+
+ /**
+ * Returns whether private DNS is currently in use on this network. When
+ * private DNS is in use, applications must not send unencrypted DNS
+ * queries as doing so could reveal private user information. Furthermore,
+ * if private DNS is in use and {@link #getPrivateDnsServerName} is not
+ * {@code null}, DNS queries must be sent to the specified DNS server.
+ *
+ * @return {@code true} if private DNS is in use, {@code false} otherwise.
+ */
+ public boolean isPrivateDnsActive() {
+ return mUsePrivateDns;
+ }
+
+ /**
+ * Set the name of the private DNS server to which private DNS queries
+ * should be sent when in strict mode. This value should be {@code null}
+ * when private DNS is off or in opportunistic mode.
+ *
+ * @param privateDnsServerName The private DNS server name.
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public void setPrivateDnsServerName(@Nullable String privateDnsServerName) {
+ mPrivateDnsServerName = privateDnsServerName;
+ }
+
+ /**
+ * Returns the private DNS server name that is in use. If not {@code null},
+ * private DNS is in strict mode. In this mode, applications should ensure
+ * that all DNS queries are encrypted and sent to this hostname and that
+ * queries are only sent if the hostname's certificate is valid. If
+ * {@code null} and {@link #isPrivateDnsActive} is {@code true}, private
+ * DNS is in opportunistic mode, and applications should ensure that DNS
+ * queries are encrypted and sent to a DNS server returned by
+ * {@link #getDnsServers}. System DNS will handle each of these cases
+ * correctly, but applications implementing their own DNS lookups must make
+ * sure to follow these requirements.
+ *
+ * @return The private DNS server name.
+ */
+ public @Nullable String getPrivateDnsServerName() {
+ return mPrivateDnsServerName;
+ }
+
+ /**
+ * Adds the given {@link InetAddress} to the list of validated private DNS servers,
+ * if not present. This is distinct from the server name in that these are actually
+ * resolved addresses.
+ *
+ * @param dnsServer The {@link InetAddress} to add to the list of validated private DNS servers.
+ * @return true if the DNS server was added, false if it was already present.
+ * @hide
+ */
+ public boolean addValidatedPrivateDnsServer(@NonNull InetAddress dnsServer) {
+ if (dnsServer != null && !mValidatedPrivateDnses.contains(dnsServer)) {
+ mValidatedPrivateDnses.add(dnsServer);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Removes the given {@link InetAddress} from the list of validated private DNS servers.
+ *
+ * @param dnsServer The {@link InetAddress} to remove from the list of validated private DNS
+ * servers.
+ * @return true if the DNS server was removed, false if it did not exist.
+ * @hide
+ */
+ public boolean removeValidatedPrivateDnsServer(@NonNull InetAddress dnsServer) {
+ return mValidatedPrivateDnses.remove(dnsServer);
+ }
+
+ /**
+ * Replaces the validated private DNS servers in this {@code LinkProperties} with
+ * the given {@link Collection} of {@link InetAddress} objects.
+ *
+ * @param dnsServers The {@link Collection} of validated private DNS servers to set in this
+ * object.
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public void setValidatedPrivateDnsServers(@NonNull Collection<InetAddress> dnsServers) {
+ mValidatedPrivateDnses.clear();
+ for (InetAddress dnsServer: dnsServers) {
+ addValidatedPrivateDnsServer(dnsServer);
+ }
+ }
+
+ /**
+ * Returns all the {@link InetAddress} for validated private DNS servers on this link.
+ * These are resolved from the private DNS server name.
+ *
+ * @return An unmodifiable {@link List} of {@link InetAddress} for validated private
+ * DNS servers on this link.
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public @NonNull List<InetAddress> getValidatedPrivateDnsServers() {
+ return Collections.unmodifiableList(mValidatedPrivateDnses);
+ }
+
+ /**
+ * Adds the given {@link InetAddress} to the list of PCSCF servers, if not present.
+ *
+ * @param pcscfServer The {@link InetAddress} to add to the list of PCSCF servers.
+ * @return true if the PCSCF server was added, false otherwise.
+ * @hide
+ */
+ public boolean addPcscfServer(@NonNull InetAddress pcscfServer) {
+ if (pcscfServer != null && !mPcscfs.contains(pcscfServer)) {
+ mPcscfs.add(pcscfServer);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Removes the given {@link InetAddress} from the list of PCSCF servers.
+ *
+ * @param pcscfServer The {@link InetAddress} to remove from the list of PCSCF servers.
+ * @return true if the PCSCF server was removed, false otherwise.
+ * @hide
+ */
+ public boolean removePcscfServer(@NonNull InetAddress pcscfServer) {
+ return mPcscfs.remove(pcscfServer);
+ }
+
+ /**
+ * Replaces the PCSCF servers in this {@code LinkProperties} with
+ * the given {@link Collection} of {@link InetAddress} objects.
+ *
+ * @param pcscfServers The {@link Collection} of PCSCF servers to set in this object.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public void setPcscfServers(@NonNull Collection<InetAddress> pcscfServers) {
+ mPcscfs.clear();
+ for (InetAddress pcscfServer: pcscfServers) {
+ addPcscfServer(pcscfServer);
+ }
+ }
+
+ /**
+ * Returns all the {@link InetAddress} for PCSCF servers on this link.
+ *
+ * @return An unmodifiable {@link List} of {@link InetAddress} for PCSCF servers on
+ * this link.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public @NonNull List<InetAddress> getPcscfServers() {
+ return Collections.unmodifiableList(mPcscfs);
+ }
+
+ /**
+ * Sets the DNS domain search path used on this link.
+ *
+ * @param domains A {@link String} listing in priority order the comma separated
+ * domains to search when resolving host names on this link.
+ */
+ public void setDomains(@Nullable String domains) {
+ mDomains = domains;
+ }
+
+ /**
+ * Get the DNS domains search path set for this link. May be {@code null} if not set.
+ *
+ * @return A {@link String} containing the comma separated domains to search when resolving host
+ * names on this link or {@code null}.
+ */
+ public @Nullable String getDomains() {
+ return mDomains;
+ }
+
+ /**
+ * Sets the Maximum Transmission Unit size to use on this link. This should not be used
+ * unless the system default (1500) is incorrect. Values less than 68 or greater than
+ * 10000 will be ignored.
+ *
+ * @param mtu The MTU to use for this link.
+ */
+ public void setMtu(int mtu) {
+ mMtu = mtu;
+ }
+
+ /**
+ * Gets any non-default MTU size set for this link. Note that if the default is being used
+ * this will return 0.
+ *
+ * @return The mtu value set for this link.
+ */
+ public int getMtu() {
+ return mMtu;
+ }
+
+ /**
+ * Sets the tcp buffers sizes to be used when this link is the system default.
+ * Should be of the form "rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max".
+ *
+ * @param tcpBufferSizes The tcp buffers sizes to use.
+ *
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public void setTcpBufferSizes(@Nullable String tcpBufferSizes) {
+ mTcpBufferSizes = tcpBufferSizes;
+ }
+
+ /**
+ * Gets the tcp buffer sizes. May be {@code null} if not set.
+ *
+ * @return the tcp buffer sizes to use when this link is the system default or {@code null}.
+ *
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public @Nullable String getTcpBufferSizes() {
+ return mTcpBufferSizes;
+ }
+
+ private RouteInfo routeWithInterface(RouteInfo route) {
+ return new RouteInfo(
+ route.getDestination(),
+ route.getGateway(),
+ mIfaceName,
+ route.getType());
+ }
+
+ /**
+ * Adds a {@link RouteInfo} to this {@code LinkProperties}, if not present. If the
+ * {@link RouteInfo} had an interface name set and that differs from the interface set for this
+ * {@code LinkProperties} an {@link IllegalArgumentException} will be thrown. The proper
+ * course is to add either un-named or properly named {@link RouteInfo}.
+ *
+ * @param route A {@link RouteInfo} to add to this object.
+ * @return {@code false} if the route was already present, {@code true} if it was added.
+ */
+ public boolean addRoute(@NonNull RouteInfo route) {
+ String routeIface = route.getInterface();
+ if (routeIface != null && !routeIface.equals(mIfaceName)) {
+ throw new IllegalArgumentException(
+ "Route added with non-matching interface: " + routeIface
+ + " vs. " + mIfaceName);
+ }
+ route = routeWithInterface(route);
+ if (!mRoutes.contains(route)) {
+ mRoutes.add(route);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Removes a {@link RouteInfo} from this {@code LinkProperties}, if present. The route must
+ * specify an interface and the interface must match the interface of this
+ * {@code LinkProperties}, or it will not be removed.
+ *
+ * @return {@code true} if the route was removed, {@code false} if it was not present.
+ *
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public boolean removeRoute(@NonNull RouteInfo route) {
+ return Objects.equals(mIfaceName, route.getInterface()) && mRoutes.remove(route);
+ }
+
+ /**
+ * Returns all the {@link RouteInfo} set on this link.
+ *
+ * @return An unmodifiable {@link List} of {@link RouteInfo} for this link.
+ */
+ public @NonNull List<RouteInfo> getRoutes() {
+ return Collections.unmodifiableList(mRoutes);
+ }
+
+ /**
+ * Make sure this LinkProperties instance contains routes that cover the local subnet
+ * of its link addresses. Add any route that is missing.
+ * @hide
+ */
+ public void ensureDirectlyConnectedRoutes() {
+ for (LinkAddress addr : mLinkAddresses) {
+ addRoute(new RouteInfo(addr, null, mIfaceName));
+ }
+ }
+
+ /**
+ * Returns all the routes on this link and all the links stacked above it.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public @NonNull List<RouteInfo> getAllRoutes() {
+ List<RouteInfo> routes = new ArrayList<>(mRoutes);
+ for (LinkProperties stacked: mStackedLinks.values()) {
+ routes.addAll(stacked.getAllRoutes());
+ }
+ return routes;
+ }
+
+ /**
+ * Sets the recommended {@link ProxyInfo} to use on this link, or {@code null} for none.
+ * Note that Http Proxies are only a hint - the system recommends their use, but it does
+ * not enforce it and applications may ignore them.
+ *
+ * @param proxy A {@link ProxyInfo} defining the HTTP Proxy to use on this link.
+ */
+ public void setHttpProxy(@Nullable ProxyInfo proxy) {
+ mHttpProxy = proxy;
+ }
+
+ /**
+ * Gets the recommended {@link ProxyInfo} (or {@code null}) set on this link.
+ *
+ * @return The {@link ProxyInfo} set on this link or {@code null}.
+ */
+ public @Nullable ProxyInfo getHttpProxy() {
+ return mHttpProxy;
+ }
+
+ /**
+ * Returns the NAT64 prefix in use on this link, if any.
+ *
+ * @return the NAT64 prefix or {@code null}.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public @Nullable IpPrefix getNat64Prefix() {
+ return mNat64Prefix;
+ }
+
+ /**
+ * Sets the NAT64 prefix in use on this link.
+ *
+ * Currently, only 96-bit prefixes (i.e., where the 32-bit IPv4 address is at the end of the
+ * 128-bit IPv6 address) are supported or {@code null} for no prefix.
+ *
+ * @param prefix the NAT64 prefix.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public void setNat64Prefix(@Nullable IpPrefix prefix) {
+ if (prefix != null && prefix.getPrefixLength() != 96) {
+ throw new IllegalArgumentException("Only 96-bit prefixes are supported: " + prefix);
+ }
+ mNat64Prefix = prefix; // IpPrefix objects are immutable.
+ }
+
+ /**
+ * Adds a stacked link.
+ *
+ * If there is already a stacked link with the same interface name as link,
+ * that link is replaced with link. Otherwise, link is added to the list
+ * of stacked links.
+ *
+ * @param link The link to add.
+ * @return true if the link was stacked, false otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public boolean addStackedLink(@NonNull LinkProperties link) {
+ if (link.getInterfaceName() != null) {
+ mStackedLinks.put(link.getInterfaceName(), link);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Removes a stacked link.
+ *
+ * If there is a stacked link with the given interface name, it is
+ * removed. Otherwise, nothing changes.
+ *
+ * @param iface The interface name of the link to remove.
+ * @return true if the link was removed, false otherwise.
+ * @hide
+ */
+ public boolean removeStackedLink(@NonNull String iface) {
+ LinkProperties removed = mStackedLinks.remove(iface);
+ return removed != null;
+ }
+
+ /**
+ * Returns all the links stacked on top of this link.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public @NonNull List<LinkProperties> getStackedLinks() {
+ if (mStackedLinks.isEmpty()) {
+ return Collections.emptyList();
+ }
+ final List<LinkProperties> stacked = new ArrayList<>();
+ for (LinkProperties link : mStackedLinks.values()) {
+ stacked.add(new LinkProperties(link));
+ }
+ return Collections.unmodifiableList(stacked);
+ }
+
+ /**
+ * Clears this object to its initial state.
+ */
+ public void clear() {
+ mIfaceName = null;
+ mLinkAddresses.clear();
+ mDnses.clear();
+ mUsePrivateDns = false;
+ mPrivateDnsServerName = null;
+ mPcscfs.clear();
+ mDomains = null;
+ mRoutes.clear();
+ mHttpProxy = null;
+ mStackedLinks.clear();
+ mMtu = 0;
+ mTcpBufferSizes = null;
+ mNat64Prefix = null;
+ }
+
+ /**
+ * Implement the Parcelable interface
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ // Space as a separator, so no need for spaces at start/end of the individual fragments.
+ final StringJoiner resultJoiner = new StringJoiner(" ", "{", "}");
+
+ if (mIfaceName != null) {
+ resultJoiner.add("InterfaceName:");
+ resultJoiner.add(mIfaceName);
+ }
+
+ resultJoiner.add("LinkAddresses: [");
+ if (!mLinkAddresses.isEmpty()) {
+ resultJoiner.add(TextUtils.join(",", mLinkAddresses));
+ }
+ resultJoiner.add("]");
+
+ resultJoiner.add("DnsAddresses: [");
+ if (!mDnses.isEmpty()) {
+ resultJoiner.add(TextUtils.join(",", mDnses));
+ }
+ resultJoiner.add("]");
+
+ if (mUsePrivateDns) {
+ resultJoiner.add("UsePrivateDns: true");
+ }
+
+ if (mPrivateDnsServerName != null) {
+ resultJoiner.add("PrivateDnsServerName:");
+ resultJoiner.add(mPrivateDnsServerName);
+ }
+
+ if (!mPcscfs.isEmpty()) {
+ resultJoiner.add("PcscfAddresses: [");
+ resultJoiner.add(TextUtils.join(",", mPcscfs));
+ resultJoiner.add("]");
+ }
+
+ if (!mValidatedPrivateDnses.isEmpty()) {
+ final StringJoiner validatedPrivateDnsesJoiner =
+ new StringJoiner(",", "ValidatedPrivateDnsAddresses: [", "]");
+ for (final InetAddress addr : mValidatedPrivateDnses) {
+ validatedPrivateDnsesJoiner.add(addr.getHostAddress());
+ }
+ resultJoiner.add(validatedPrivateDnsesJoiner.toString());
+ }
+
+ resultJoiner.add("Domains:");
+ resultJoiner.add(mDomains);
+
+ resultJoiner.add("MTU:");
+ resultJoiner.add(Integer.toString(mMtu));
+
+ if (mTcpBufferSizes != null) {
+ resultJoiner.add("TcpBufferSizes:");
+ resultJoiner.add(mTcpBufferSizes);
+ }
+
+ resultJoiner.add("Routes: [");
+ if (!mRoutes.isEmpty()) {
+ resultJoiner.add(TextUtils.join(",", mRoutes));
+ }
+ resultJoiner.add("]");
+
+ if (mHttpProxy != null) {
+ resultJoiner.add("HttpProxy:");
+ resultJoiner.add(mHttpProxy.toString());
+ }
+
+ if (mNat64Prefix != null) {
+ resultJoiner.add("Nat64Prefix:");
+ resultJoiner.add(mNat64Prefix.toString());
+ }
+
+ final Collection<LinkProperties> stackedLinksValues = mStackedLinks.values();
+ if (!stackedLinksValues.isEmpty()) {
+ final StringJoiner stackedLinksJoiner = new StringJoiner(",", "Stacked: [", "]");
+ for (final LinkProperties lp : stackedLinksValues) {
+ stackedLinksJoiner.add("[ " + lp + " ]");
+ }
+ resultJoiner.add(stackedLinksJoiner.toString());
+ }
+
+ return resultJoiner.toString();
+ }
+
+ /**
+ * Returns true if this link has an IPv4 address.
+ *
+ * @return {@code true} if there is an IPv4 address, {@code false} otherwise.
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public boolean hasIpv4Address() {
+ for (LinkAddress address : mLinkAddresses) {
+ if (address.getAddress() instanceof Inet4Address) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * For backward compatibility.
+ * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely
+ * just yet.
+ * @return {@code true} if there is an IPv4 address, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public boolean hasIPv4Address() {
+ return hasIpv4Address();
+ }
+
+ /**
+ * Returns true if this link or any of its stacked interfaces has an IPv4 address.
+ *
+ * @return {@code true} if there is an IPv4 address, {@code false} otherwise.
+ */
+ private boolean hasIpv4AddressOnInterface(String iface) {
+ // mIfaceName can be null.
+ return (Objects.equals(iface, mIfaceName) && hasIpv4Address())
+ || (iface != null && mStackedLinks.containsKey(iface)
+ && mStackedLinks.get(iface).hasIpv4Address());
+ }
+
+ /**
+ * Returns true if this link has a global preferred IPv6 address.
+ *
+ * @return {@code true} if there is a global preferred IPv6 address, {@code false} otherwise.
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public boolean hasGlobalIpv6Address() {
+ for (LinkAddress address : mLinkAddresses) {
+ if (address.getAddress() instanceof Inet6Address && address.isGlobalPreferred()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * For backward compatibility.
+ * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely
+ * just yet.
+ * @return {@code true} if there is a global preferred IPv6 address, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public boolean hasGlobalIPv6Address() {
+ return hasGlobalIpv6Address();
+ }
+
+ /**
+ * Returns true if this link has an IPv4 default route.
+ *
+ * @return {@code true} if there is an IPv4 default route, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public boolean hasIpv4DefaultRoute() {
+ for (RouteInfo r : mRoutes) {
+ if (r.isIPv4Default()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * For backward compatibility.
+ * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely
+ * just yet.
+ * @return {@code true} if there is an IPv4 default route, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public boolean hasIPv4DefaultRoute() {
+ return hasIpv4DefaultRoute();
+ }
+
+ /**
+ * Returns true if this link has an IPv6 default route.
+ *
+ * @return {@code true} if there is an IPv6 default route, {@code false} otherwise.
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public boolean hasIpv6DefaultRoute() {
+ for (RouteInfo r : mRoutes) {
+ if (r.isIPv6Default()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * For backward compatibility.
+ * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely
+ * just yet.
+ * @return {@code true} if there is an IPv6 default route, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public boolean hasIPv6DefaultRoute() {
+ return hasIpv6DefaultRoute();
+ }
+
+ /**
+ * Returns true if this link has an IPv4 DNS server.
+ *
+ * @return {@code true} if there is an IPv4 DNS server, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public boolean hasIpv4DnsServer() {
+ for (InetAddress ia : mDnses) {
+ if (ia instanceof Inet4Address) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * For backward compatibility.
+ * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely
+ * just yet.
+ * @return {@code true} if there is an IPv4 DNS server, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public boolean hasIPv4DnsServer() {
+ return hasIpv4DnsServer();
+ }
+
+ /**
+ * Returns true if this link has an IPv6 DNS server.
+ *
+ * @return {@code true} if there is an IPv6 DNS server, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public boolean hasIpv6DnsServer() {
+ for (InetAddress ia : mDnses) {
+ if (ia instanceof Inet6Address) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * For backward compatibility.
+ * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely
+ * just yet.
+ * @return {@code true} if there is an IPv6 DNS server, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public boolean hasIPv6DnsServer() {
+ return hasIpv6DnsServer();
+ }
+
+ /**
+ * Returns true if this link has an IPv4 PCSCF server.
+ *
+ * @return {@code true} if there is an IPv4 PCSCF server, {@code false} otherwise.
+ * @hide
+ */
+ public boolean hasIpv4PcscfServer() {
+ for (InetAddress ia : mPcscfs) {
+ if (ia instanceof Inet4Address) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if this link has an IPv6 PCSCF server.
+ *
+ * @return {@code true} if there is an IPv6 PCSCF server, {@code false} otherwise.
+ * @hide
+ */
+ public boolean hasIpv6PcscfServer() {
+ for (InetAddress ia : mPcscfs) {
+ if (ia instanceof Inet6Address) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if this link is provisioned for global IPv4 connectivity.
+ * This requires an IP address, default route, and DNS server.
+ *
+ * @return {@code true} if the link is provisioned, {@code false} otherwise.
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public boolean isIpv4Provisioned() {
+ return (hasIpv4Address()
+ && hasIpv4DefaultRoute()
+ && hasIpv4DnsServer());
+ }
+
+ /**
+ * Returns true if this link is provisioned for global IPv6 connectivity.
+ * This requires an IP address, default route, and DNS server.
+ *
+ * @return {@code true} if the link is provisioned, {@code false} otherwise.
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public boolean isIpv6Provisioned() {
+ return (hasGlobalIpv6Address()
+ && hasIpv6DefaultRoute()
+ && hasIpv6DnsServer());
+ }
+
+ /**
+ * For backward compatibility.
+ * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely
+ * just yet.
+ * @return {@code true} if the link is provisioned, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public boolean isIPv6Provisioned() {
+ return isIpv6Provisioned();
+ }
+
+
+ /**
+ * Returns true if this link is provisioned for global connectivity,
+ * for at least one Internet Protocol family.
+ *
+ * @return {@code true} if the link is provisioned, {@code false} otherwise.
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public boolean isProvisioned() {
+ return (isIpv4Provisioned() || isIpv6Provisioned());
+ }
+
+ /**
+ * Evaluate whether the {@link InetAddress} is considered reachable.
+ *
+ * @return {@code true} if the given {@link InetAddress} is considered reachable,
+ * {@code false} otherwise.
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public boolean isReachable(@NonNull InetAddress ip) {
+ final List<RouteInfo> allRoutes = getAllRoutes();
+ // If we don't have a route to this IP address, it's not reachable.
+ final RouteInfo bestRoute = RouteInfo.selectBestRoute(allRoutes, ip);
+ if (bestRoute == null) {
+ return false;
+ }
+
+ // TODO: better source address evaluation for destination addresses.
+
+ if (ip instanceof Inet4Address) {
+ // For IPv4, it suffices for now to simply have any address.
+ return hasIpv4AddressOnInterface(bestRoute.getInterface());
+ } else if (ip instanceof Inet6Address) {
+ if (ip.isLinkLocalAddress()) {
+ // For now, just make sure link-local destinations have
+ // scopedIds set, since transmits will generally fail otherwise.
+ // TODO: verify it matches the ifindex of one of the interfaces.
+ return (((Inet6Address)ip).getScopeId() != 0);
+ } else {
+ // For non-link-local destinations check that either the best route
+ // is directly connected or that some global preferred address exists.
+ // TODO: reconsider all cases (disconnected ULA networks, ...).
+ return (!bestRoute.hasGateway() || hasGlobalIpv6Address());
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Compares this {@code LinkProperties} interface name against the target
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public boolean isIdenticalInterfaceName(@NonNull LinkProperties target) {
+ return TextUtils.equals(getInterfaceName(), target.getInterfaceName());
+ }
+
+ /**
+ * Compares this {@code LinkProperties} interface addresses against the target
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public boolean isIdenticalAddresses(@NonNull LinkProperties target) {
+ Collection<InetAddress> targetAddresses = target.getAddresses();
+ Collection<InetAddress> sourceAddresses = getAddresses();
+ return (sourceAddresses.size() == targetAddresses.size()) ?
+ sourceAddresses.containsAll(targetAddresses) : false;
+ }
+
+ /**
+ * Compares this {@code LinkProperties} DNS addresses against the target
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public boolean isIdenticalDnses(@NonNull LinkProperties target) {
+ Collection<InetAddress> targetDnses = target.getDnsServers();
+ String targetDomains = target.getDomains();
+ if (mDomains == null) {
+ if (targetDomains != null) return false;
+ } else {
+ if (!mDomains.equals(targetDomains)) return false;
+ }
+ return (mDnses.size() == targetDnses.size()) ?
+ mDnses.containsAll(targetDnses) : false;
+ }
+
+ /**
+ * Compares this {@code LinkProperties} private DNS settings against the
+ * target.
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ public boolean isIdenticalPrivateDns(@NonNull LinkProperties target) {
+ return (isPrivateDnsActive() == target.isPrivateDnsActive()
+ && TextUtils.equals(getPrivateDnsServerName(),
+ target.getPrivateDnsServerName()));
+ }
+
+ /**
+ * Compares this {@code LinkProperties} validated private DNS addresses against
+ * the target
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ public boolean isIdenticalValidatedPrivateDnses(@NonNull LinkProperties target) {
+ Collection<InetAddress> targetDnses = target.getValidatedPrivateDnsServers();
+ return (mValidatedPrivateDnses.size() == targetDnses.size())
+ ? mValidatedPrivateDnses.containsAll(targetDnses) : false;
+ }
+
+ /**
+ * Compares this {@code LinkProperties} PCSCF addresses against the target
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ public boolean isIdenticalPcscfs(@NonNull LinkProperties target) {
+ Collection<InetAddress> targetPcscfs = target.getPcscfServers();
+ return (mPcscfs.size() == targetPcscfs.size()) ?
+ mPcscfs.containsAll(targetPcscfs) : false;
+ }
+
+ /**
+ * Compares this {@code LinkProperties} Routes against the target
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public boolean isIdenticalRoutes(@NonNull LinkProperties target) {
+ Collection<RouteInfo> targetRoutes = target.getRoutes();
+ return (mRoutes.size() == targetRoutes.size()) ?
+ mRoutes.containsAll(targetRoutes) : false;
+ }
+
+ /**
+ * Compares this {@code LinkProperties} HttpProxy against the target
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public boolean isIdenticalHttpProxy(@NonNull LinkProperties target) {
+ return getHttpProxy() == null ? target.getHttpProxy() == null :
+ getHttpProxy().equals(target.getHttpProxy());
+ }
+
+ /**
+ * Compares this {@code LinkProperties} stacked links against the target
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public boolean isIdenticalStackedLinks(@NonNull LinkProperties target) {
+ if (!mStackedLinks.keySet().equals(target.mStackedLinks.keySet())) {
+ return false;
+ }
+ for (LinkProperties stacked : mStackedLinks.values()) {
+ // Hashtable values can never be null.
+ String iface = stacked.getInterfaceName();
+ if (!stacked.equals(target.mStackedLinks.get(iface))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Compares this {@code LinkProperties} MTU against the target
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ public boolean isIdenticalMtu(@NonNull LinkProperties target) {
+ return getMtu() == target.getMtu();
+ }
+
+ /**
+ * Compares this {@code LinkProperties} Tcp buffer sizes against the target.
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ public boolean isIdenticalTcpBufferSizes(@NonNull LinkProperties target) {
+ return Objects.equals(mTcpBufferSizes, target.mTcpBufferSizes);
+ }
+
+ /**
+ * Compares this {@code LinkProperties} NAT64 prefix against the target.
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ public boolean isIdenticalNat64Prefix(@NonNull LinkProperties target) {
+ return Objects.equals(mNat64Prefix, target.mNat64Prefix);
+ }
+
+ /**
+ * Compares this {@code LinkProperties} instance against the target
+ * LinkProperties in {@code obj}. Two LinkPropertieses are equal if
+ * all their fields are equal in values.
+ *
+ * For collection fields, such as mDnses, containsAll() is used to check
+ * if two collections contains the same elements, independent of order.
+ * There are two thoughts regarding containsAll()
+ * 1. Duplicated elements. eg, (A, B, B) and (A, A, B) are equal.
+ * 2. Worst case performance is O(n^2).
+ *
+ * @param obj the object to be tested for equality.
+ * @return {@code true} if both objects are equal, {@code false} otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+
+ if (!(obj instanceof LinkProperties)) return false;
+
+ LinkProperties target = (LinkProperties) obj;
+ /*
+ * This method does not check that stacked interfaces are equal, because
+ * stacked interfaces are not so much a property of the link as a
+ * description of connections between links.
+ */
+ return isIdenticalInterfaceName(target)
+ && isIdenticalAddresses(target)
+ && isIdenticalDnses(target)
+ && isIdenticalPrivateDns(target)
+ && isIdenticalValidatedPrivateDnses(target)
+ && isIdenticalPcscfs(target)
+ && isIdenticalRoutes(target)
+ && isIdenticalHttpProxy(target)
+ && isIdenticalStackedLinks(target)
+ && isIdenticalMtu(target)
+ && isIdenticalTcpBufferSizes(target)
+ && isIdenticalNat64Prefix(target);
+ }
+
+ /**
+ * Compares the addresses in this LinkProperties with another
+ * LinkProperties, examining only addresses on the base link.
+ *
+ * @param target a LinkProperties with the new list of addresses
+ * @return the differences between the addresses.
+ * @hide
+ */
+ public @NonNull CompareResult<LinkAddress> compareAddresses(@Nullable LinkProperties target) {
+ /*
+ * Duplicate the LinkAddresses into removed, we will be removing
+ * address which are common between mLinkAddresses and target
+ * leaving the addresses that are different. And address which
+ * are in target but not in mLinkAddresses are placed in the
+ * addedAddresses.
+ */
+ return new CompareResult<>(mLinkAddresses,
+ target != null ? target.getLinkAddresses() : null);
+ }
+
+ /**
+ * Compares the DNS addresses in this LinkProperties with another
+ * LinkProperties, examining only DNS addresses on the base link.
+ *
+ * @param target a LinkProperties with the new list of dns addresses
+ * @return the differences between the DNS addresses.
+ * @hide
+ */
+ public @NonNull CompareResult<InetAddress> compareDnses(@Nullable LinkProperties target) {
+ /*
+ * Duplicate the InetAddresses into removed, we will be removing
+ * dns address which are common between mDnses and target
+ * leaving the addresses that are different. And dns address which
+ * are in target but not in mDnses are placed in the
+ * addedAddresses.
+ */
+ return new CompareResult<>(mDnses, target != null ? target.getDnsServers() : null);
+ }
+
+ /**
+ * Compares the validated private DNS addresses in this LinkProperties with another
+ * LinkProperties.
+ *
+ * @param target a LinkProperties with the new list of validated private dns addresses
+ * @return the differences between the DNS addresses.
+ * @hide
+ */
+ public @NonNull CompareResult<InetAddress> compareValidatedPrivateDnses(
+ @Nullable LinkProperties target) {
+ return new CompareResult<>(mValidatedPrivateDnses,
+ target != null ? target.getValidatedPrivateDnsServers() : null);
+ }
+
+ /**
+ * Compares all routes in this LinkProperties with another LinkProperties,
+ * examining both the the base link and all stacked links.
+ *
+ * @param target a LinkProperties with the new list of routes
+ * @return the differences between the routes.
+ * @hide
+ */
+ public @NonNull CompareResult<RouteInfo> compareAllRoutes(@Nullable LinkProperties target) {
+ /*
+ * Duplicate the RouteInfos into removed, we will be removing
+ * routes which are common between mRoutes and target
+ * leaving the routes that are different. And route address which
+ * are in target but not in mRoutes are placed in added.
+ */
+ return new CompareResult<>(getAllRoutes(), target != null ? target.getAllRoutes() : null);
+ }
+
+ /**
+ * Compares all interface names in this LinkProperties with another
+ * LinkProperties, examining both the the base link and all stacked links.
+ *
+ * @param target a LinkProperties with the new list of interface names
+ * @return the differences between the interface names.
+ * @hide
+ */
+ public @NonNull CompareResult<String> compareAllInterfaceNames(
+ @Nullable LinkProperties target) {
+ /*
+ * Duplicate the interface names into removed, we will be removing
+ * interface names which are common between this and target
+ * leaving the interface names that are different. And interface names which
+ * are in target but not in this are placed in added.
+ */
+ return new CompareResult<>(getAllInterfaceNames(),
+ target != null ? target.getAllInterfaceNames() : null);
+ }
+
+
+ /**
+ * Generate hashcode based on significant fields
+ *
+ * Equal objects must produce the same hash code, while unequal objects
+ * may have the same hash codes.
+ */
+ @Override
+ public int hashCode() {
+ return ((null == mIfaceName) ? 0 : mIfaceName.hashCode()
+ + mLinkAddresses.size() * 31
+ + mDnses.size() * 37
+ + mValidatedPrivateDnses.size() * 61
+ + ((null == mDomains) ? 0 : mDomains.hashCode())
+ + mRoutes.size() * 41
+ + ((null == mHttpProxy) ? 0 : mHttpProxy.hashCode())
+ + mStackedLinks.hashCode() * 47)
+ + mMtu * 51
+ + ((null == mTcpBufferSizes) ? 0 : mTcpBufferSizes.hashCode())
+ + (mUsePrivateDns ? 57 : 0)
+ + mPcscfs.size() * 67
+ + ((null == mPrivateDnsServerName) ? 0 : mPrivateDnsServerName.hashCode())
+ + Objects.hash(mNat64Prefix);
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(getInterfaceName());
+ dest.writeInt(mLinkAddresses.size());
+ for (LinkAddress linkAddress : mLinkAddresses) {
+ dest.writeParcelable(linkAddress, flags);
+ }
+
+ dest.writeInt(mDnses.size());
+ for (InetAddress d : mDnses) {
+ dest.writeByteArray(d.getAddress());
+ }
+ dest.writeInt(mValidatedPrivateDnses.size());
+ for (InetAddress d : mValidatedPrivateDnses) {
+ dest.writeByteArray(d.getAddress());
+ }
+ dest.writeBoolean(mUsePrivateDns);
+ dest.writeString(mPrivateDnsServerName);
+ dest.writeInt(mPcscfs.size());
+ for (InetAddress d : mPcscfs) {
+ dest.writeByteArray(d.getAddress());
+ }
+ dest.writeString(mDomains);
+ dest.writeInt(mMtu);
+ dest.writeString(mTcpBufferSizes);
+ dest.writeInt(mRoutes.size());
+ for (RouteInfo route : mRoutes) {
+ dest.writeParcelable(route, flags);
+ }
+
+ if (mHttpProxy != null) {
+ dest.writeByte((byte)1);
+ dest.writeParcelable(mHttpProxy, flags);
+ } else {
+ dest.writeByte((byte)0);
+ }
+ dest.writeParcelable(mNat64Prefix, 0);
+
+ ArrayList<LinkProperties> stackedLinks = new ArrayList<>(mStackedLinks.values());
+ dest.writeList(stackedLinks);
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ */
+ public static final @android.annotation.NonNull Creator<LinkProperties> CREATOR =
+ new Creator<LinkProperties>() {
+ public LinkProperties createFromParcel(Parcel in) {
+ LinkProperties netProp = new LinkProperties();
+
+ String iface = in.readString();
+ if (iface != null) {
+ netProp.setInterfaceName(iface);
+ }
+ int addressCount = in.readInt();
+ for (int i = 0; i < addressCount; i++) {
+ netProp.addLinkAddress(in.readParcelable(null));
+ }
+ addressCount = in.readInt();
+ for (int i = 0; i < addressCount; i++) {
+ try {
+ netProp.addDnsServer(InetAddress.getByAddress(in.createByteArray()));
+ } catch (UnknownHostException e) { }
+ }
+ addressCount = in.readInt();
+ for (int i = 0; i < addressCount; i++) {
+ try {
+ netProp.addValidatedPrivateDnsServer(
+ InetAddress.getByAddress(in.createByteArray()));
+ } catch (UnknownHostException e) { }
+ }
+ netProp.setUsePrivateDns(in.readBoolean());
+ netProp.setPrivateDnsServerName(in.readString());
+ addressCount = in.readInt();
+ for (int i = 0; i < addressCount; i++) {
+ try {
+ netProp.addPcscfServer(InetAddress.getByAddress(in.createByteArray()));
+ } catch (UnknownHostException e) { }
+ }
+ netProp.setDomains(in.readString());
+ netProp.setMtu(in.readInt());
+ netProp.setTcpBufferSizes(in.readString());
+ addressCount = in.readInt();
+ for (int i = 0; i < addressCount; i++) {
+ netProp.addRoute(in.readParcelable(null));
+ }
+ if (in.readByte() == 1) {
+ netProp.setHttpProxy(in.readParcelable(null));
+ }
+ netProp.setNat64Prefix(in.readParcelable(null));
+ ArrayList<LinkProperties> stackedLinks = new ArrayList<LinkProperties>();
+ in.readList(stackedLinks, LinkProperties.class.getClassLoader());
+ for (LinkProperties stackedLink: stackedLinks) {
+ netProp.addStackedLink(stackedLink);
+ }
+ return netProp;
+ }
+
+ public LinkProperties[] newArray(int size) {
+ return new LinkProperties[size];
+ }
+ };
+
+ /**
+ * Check the valid MTU range based on IPv4 or IPv6.
+ * @hide
+ */
+ public static boolean isValidMtu(int mtu, boolean ipv6) {
+ if (ipv6) {
+ return mtu >= MIN_MTU_V6 && mtu <= MAX_MTU;
+ } else {
+ return mtu >= MIN_MTU && mtu <= MAX_MTU;
+ }
+ }
+}
diff --git a/android/net/LinkQualityInfo.java b/android/net/LinkQualityInfo.java
new file mode 100644
index 0000000..3c5d474
--- /dev/null
+++ b/android/net/LinkQualityInfo.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2013 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;
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Class that represents useful attributes of generic network links
+ * such as the upload/download throughput or packet error rate.
+ * Generally speaking, you should be dealing with instances of
+ * LinkQualityInfo subclasses, such as {@link android.net.#WifiLinkQualityInfo}
+ * or {@link android.net.#MobileLinkQualityInfo} which provide additional
+ * information.
+ * @hide
+ */
+public class LinkQualityInfo implements Parcelable {
+
+ /**
+ * Represents a value that you can use to test if an integer field is set to a good value
+ */
+ public static final int UNKNOWN_INT = Integer.MAX_VALUE;
+
+ /**
+ * Represents a value that you can use to test if a long field is set to a good value
+ */
+ public static final long UNKNOWN_LONG = Long.MAX_VALUE;
+
+ public static final int NORMALIZED_MIN_SIGNAL_STRENGTH = 0;
+
+ public static final int NORMALIZED_MAX_SIGNAL_STRENGTH = 99;
+
+ public static final int NORMALIZED_SIGNAL_STRENGTH_RANGE =
+ NORMALIZED_MAX_SIGNAL_STRENGTH - NORMALIZED_MIN_SIGNAL_STRENGTH + 1;
+
+ /* Network type as defined by ConnectivityManager */
+ private int mNetworkType = ConnectivityManager.TYPE_NONE;
+
+ private int mNormalizedSignalStrength = UNKNOWN_INT;
+
+ private long mPacketCount = UNKNOWN_LONG;
+ private long mPacketErrorCount = UNKNOWN_LONG;
+ private int mTheoreticalTxBandwidth = UNKNOWN_INT;
+ private int mTheoreticalRxBandwidth = UNKNOWN_INT;
+ private int mTheoreticalLatency = UNKNOWN_INT;
+
+ /* Timestamp when last sample was made available */
+ private long mLastDataSampleTime = UNKNOWN_LONG;
+
+ /* Sample duration in millisecond */
+ private int mDataSampleDuration = UNKNOWN_INT;
+
+ public LinkQualityInfo() {
+
+ }
+
+ /**
+ * Implement the Parcelable interface
+ * @hide
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ */
+
+ protected static final int OBJECT_TYPE_LINK_QUALITY_INFO = 1;
+ protected static final int OBJECT_TYPE_WIFI_LINK_QUALITY_INFO = 2;
+ protected static final int OBJECT_TYPE_MOBILE_LINK_QUALITY_INFO = 3;
+
+ /**
+ * @hide
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ writeToParcel(dest, flags, OBJECT_TYPE_LINK_QUALITY_INFO);
+ }
+
+ /**
+ * @hide
+ */
+ public void writeToParcel(Parcel dest, int flags, int objectType) {
+ dest.writeInt(objectType);
+ dest.writeInt(mNetworkType);
+ dest.writeInt(mNormalizedSignalStrength);
+ dest.writeLong(mPacketCount);
+ dest.writeLong(mPacketErrorCount);
+ dest.writeInt(mTheoreticalTxBandwidth);
+ dest.writeInt(mTheoreticalRxBandwidth);
+ dest.writeInt(mTheoreticalLatency);
+ dest.writeLong(mLastDataSampleTime);
+ dest.writeInt(mDataSampleDuration);
+ }
+
+ /**
+ * @hide
+ */
+ public static final @android.annotation.NonNull Creator<LinkQualityInfo> CREATOR =
+ new Creator<LinkQualityInfo>() {
+ public LinkQualityInfo createFromParcel(Parcel in) {
+ int objectType = in.readInt();
+ if (objectType == OBJECT_TYPE_LINK_QUALITY_INFO) {
+ LinkQualityInfo li = new LinkQualityInfo();
+ li.initializeFromParcel(in);
+ return li;
+ } else if (objectType == OBJECT_TYPE_WIFI_LINK_QUALITY_INFO) {
+ return WifiLinkQualityInfo.createFromParcelBody(in);
+ } else if (objectType == OBJECT_TYPE_MOBILE_LINK_QUALITY_INFO) {
+ return MobileLinkQualityInfo.createFromParcelBody(in);
+ } else {
+ return null;
+ }
+ }
+
+ public LinkQualityInfo[] newArray(int size) {
+ return new LinkQualityInfo[size];
+ }
+ };
+
+ /**
+ * @hide
+ */
+ protected void initializeFromParcel(Parcel in) {
+ mNetworkType = in.readInt();
+ mNormalizedSignalStrength = in.readInt();
+ mPacketCount = in.readLong();
+ mPacketErrorCount = in.readLong();
+ mTheoreticalTxBandwidth = in.readInt();
+ mTheoreticalRxBandwidth = in.readInt();
+ mTheoreticalLatency = in.readInt();
+ mLastDataSampleTime = in.readLong();
+ mDataSampleDuration = in.readInt();
+ }
+
+ /**
+ * returns the type of network this link is connected to
+ * @return network type as defined by {@link android.net.ConnectivityManager} or
+ * {@link android.net.LinkQualityInfo#UNKNOWN_INT}
+ */
+ public int getNetworkType() {
+ return mNetworkType;
+ }
+
+ /**
+ * @hide
+ */
+ public void setNetworkType(int networkType) {
+ mNetworkType = networkType;
+ }
+
+ /**
+ * returns the signal strength normalized across multiple types of networks
+ * @return an integer value from 0 - 99 or {@link android.net.LinkQualityInfo#UNKNOWN_INT}
+ */
+ public int getNormalizedSignalStrength() {
+ return mNormalizedSignalStrength;
+ }
+
+ /**
+ * @hide
+ */
+ public void setNormalizedSignalStrength(int normalizedSignalStrength) {
+ mNormalizedSignalStrength = normalizedSignalStrength;
+ }
+
+ /**
+ * returns the total number of packets sent or received in sample duration
+ * @return number of packets or {@link android.net.LinkQualityInfo#UNKNOWN_LONG}
+ */
+ public long getPacketCount() {
+ return mPacketCount;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setPacketCount(long packetCount) {
+ mPacketCount = packetCount;
+ }
+
+ /**
+ * returns the total number of packets errors encountered in sample duration
+ * @return number of errors or {@link android.net.LinkQualityInfo#UNKNOWN_LONG}
+ */
+ public long getPacketErrorCount() {
+ return mPacketErrorCount;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setPacketErrorCount(long packetErrorCount) {
+ mPacketErrorCount = packetErrorCount;
+ }
+
+ /**
+ * returns the theoretical upload bandwidth of this network
+ * @return bandwidth in Kbps or {@link android.net.LinkQualityInfo#UNKNOWN_INT}
+ */
+ public int getTheoreticalTxBandwidth() {
+ return mTheoreticalTxBandwidth;
+ }
+
+ /**
+ * @hide
+ */
+ public void setTheoreticalTxBandwidth(int theoreticalTxBandwidth) {
+ mTheoreticalTxBandwidth = theoreticalTxBandwidth;
+ }
+
+ /**
+ * returns the theoretical download bandwidth of this network
+ * @return bandwidth in Kbps or {@link android.net.LinkQualityInfo#UNKNOWN_INT}
+ */
+ public int getTheoreticalRxBandwidth() {
+ return mTheoreticalRxBandwidth;
+ }
+
+ /**
+ * @hide
+ */
+ public void setTheoreticalRxBandwidth(int theoreticalRxBandwidth) {
+ mTheoreticalRxBandwidth = theoreticalRxBandwidth;
+ }
+
+ /**
+ * returns the theoretical latency of this network
+ * @return latency in milliseconds or {@link android.net.LinkQualityInfo#UNKNOWN_INT}
+ */
+ public int getTheoreticalLatency() {
+ return mTheoreticalLatency;
+ }
+
+ /**
+ * @hide
+ */
+ public void setTheoreticalLatency(int theoreticalLatency) {
+ mTheoreticalLatency = theoreticalLatency;
+ }
+
+ /**
+ * returns the time stamp of the last sample
+ * @return milliseconds elapsed since start and sample time or
+ * {@link android.net.LinkQualityInfo#UNKNOWN_LONG}
+ */
+ public long getLastDataSampleTime() {
+ return mLastDataSampleTime;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setLastDataSampleTime(long lastDataSampleTime) {
+ mLastDataSampleTime = lastDataSampleTime;
+ }
+
+ /**
+ * returns the sample duration used
+ * @return duration in milliseconds or {@link android.net.LinkQualityInfo#UNKNOWN_INT}
+ */
+ public int getDataSampleDuration() {
+ return mDataSampleDuration;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setDataSampleDuration(int dataSampleDuration) {
+ mDataSampleDuration = dataSampleDuration;
+ }
+}
diff --git a/android/net/LocalServerSocket.java b/android/net/LocalServerSocket.java
new file mode 100644
index 0000000..d1f49d2
--- /dev/null
+++ b/android/net/LocalServerSocket.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2007 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;
+
+import java.io.Closeable;
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/**
+ * Non-standard class for creating an inbound UNIX-domain socket
+ * in the Linux abstract namespace.
+ */
+public class LocalServerSocket implements Closeable {
+ private final LocalSocketImpl impl;
+ private final LocalSocketAddress localAddress;
+
+ /** 50 seems a bit much, but it's what was here */
+ private static final int LISTEN_BACKLOG = 50;
+
+ /**
+ * Creates a new server socket listening at specified name.
+ * On the Android platform, the name is created in the Linux
+ * abstract namespace (instead of on the filesystem).
+ *
+ * @param name address for socket
+ * @throws IOException
+ */
+ public LocalServerSocket(String name) throws IOException
+ {
+ impl = new LocalSocketImpl();
+
+ impl.create(LocalSocket.SOCKET_STREAM);
+
+ localAddress = new LocalSocketAddress(name);
+ impl.bind(localAddress);
+
+ impl.listen(LISTEN_BACKLOG);
+ }
+
+ /**
+ * Create a LocalServerSocket from a file descriptor that's already
+ * been created and bound. listen() will be called immediately on it.
+ * Used for cases where file descriptors are passed in via environment
+ * variables
+ *
+ * @param fd bound file descriptor
+ * @throws IOException
+ */
+ public LocalServerSocket(FileDescriptor fd) throws IOException
+ {
+ impl = new LocalSocketImpl(fd);
+ impl.listen(LISTEN_BACKLOG);
+ localAddress = impl.getSockAddress();
+ }
+
+ /**
+ * Obtains the socket's local address
+ *
+ * @return local address
+ */
+ public LocalSocketAddress getLocalSocketAddress()
+ {
+ return localAddress;
+ }
+
+ /**
+ * Accepts a new connection to the socket. Blocks until a new
+ * connection arrives.
+ *
+ * @return a socket representing the new connection.
+ * @throws IOException
+ */
+ public LocalSocket accept() throws IOException
+ {
+ LocalSocketImpl acceptedImpl = new LocalSocketImpl();
+
+ impl.accept(acceptedImpl);
+
+ return LocalSocket.createLocalSocketForAccept(acceptedImpl);
+ }
+
+ /**
+ * Returns file descriptor or null if not yet open/already closed
+ *
+ * @return fd or null
+ */
+ public FileDescriptor getFileDescriptor() {
+ return impl.getFileDescriptor();
+ }
+
+ /**
+ * Closes server socket.
+ *
+ * @throws IOException
+ */
+ @Override public void close() throws IOException
+ {
+ impl.close();
+ }
+}
diff --git a/android/net/LocalSocket.java b/android/net/LocalSocket.java
new file mode 100644
index 0000000..6a2031b
--- /dev/null
+++ b/android/net/LocalSocket.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2007 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;
+
+import android.annotation.UnsupportedAppUsage;
+import java.io.Closeable;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.SocketOptions;
+
+/**
+ * Creates a (non-server) socket in the UNIX-domain namespace. The interface
+ * here is not entirely unlike that of java.net.Socket. This class and the streams
+ * returned from it may be used from multiple threads.
+ */
+public class LocalSocket implements Closeable {
+
+ @UnsupportedAppUsage
+ private final LocalSocketImpl impl;
+ /** false if impl.create() needs to be called */
+ private volatile boolean implCreated;
+ private LocalSocketAddress localAddress;
+ private boolean isBound;
+ private boolean isConnected;
+ private final int sockType;
+
+ /** unknown socket type (used for constructor with existing file descriptor) */
+ /* package */ static final int SOCKET_UNKNOWN = 0;
+ /** Datagram socket type */
+ public static final int SOCKET_DGRAM = 1;
+ /** Stream socket type */
+ public static final int SOCKET_STREAM = 2;
+ /** Sequential packet socket type */
+ public static final int SOCKET_SEQPACKET = 3;
+
+ /**
+ * Creates a AF_LOCAL/UNIX domain stream socket.
+ */
+ public LocalSocket() {
+ this(SOCKET_STREAM);
+ }
+
+ /**
+ * Creates a AF_LOCAL/UNIX domain stream socket with given socket type
+ *
+ * @param sockType either {@link #SOCKET_DGRAM}, {@link #SOCKET_STREAM}
+ * or {@link #SOCKET_SEQPACKET}
+ */
+ public LocalSocket(int sockType) {
+ this(new LocalSocketImpl(), sockType);
+ }
+
+ private LocalSocket(LocalSocketImpl impl, int sockType) {
+ this.impl = impl;
+ this.sockType = sockType;
+ this.isConnected = false;
+ this.isBound = false;
+ }
+
+ /**
+ * Creates a LocalSocket instances using the FileDescriptor for an already-connected
+ * AF_LOCAL/UNIX domain stream socket. Note: the FileDescriptor must be closed by the caller:
+ * closing the LocalSocket will not close it.
+ *
+ * @hide - used by BluetoothSocket.
+ */
+ public static LocalSocket createConnectedLocalSocket(FileDescriptor fd) {
+ return createConnectedLocalSocket(new LocalSocketImpl(fd), SOCKET_UNKNOWN);
+ }
+
+ /**
+ * for use with LocalServerSocket.accept()
+ */
+ static LocalSocket createLocalSocketForAccept(LocalSocketImpl impl) {
+ return createConnectedLocalSocket(impl, SOCKET_UNKNOWN);
+ }
+
+ /**
+ * Creates a LocalSocket from an existing LocalSocketImpl that is already connected.
+ */
+ private static LocalSocket createConnectedLocalSocket(LocalSocketImpl impl, int sockType) {
+ LocalSocket socket = new LocalSocket(impl, sockType);
+ socket.isConnected = true;
+ socket.isBound = true;
+ socket.implCreated = true;
+ return socket;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return super.toString() + " impl:" + impl;
+ }
+
+ /**
+ * It's difficult to discern from the spec when impl.create() should be
+ * called, but it seems like a reasonable rule is "as soon as possible,
+ * but not in a context where IOException cannot be thrown"
+ *
+ * @throws IOException from SocketImpl.create()
+ */
+ private void implCreateIfNeeded() throws IOException {
+ if (!implCreated) {
+ synchronized (this) {
+ if (!implCreated) {
+ try {
+ impl.create(sockType);
+ } finally {
+ implCreated = true;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Connects this socket to an endpoint. May only be called on an instance
+ * that has not yet been connected.
+ *
+ * @param endpoint endpoint address
+ * @throws IOException if socket is in invalid state or the address does
+ * not exist.
+ */
+ public void connect(LocalSocketAddress endpoint) throws IOException {
+ synchronized (this) {
+ if (isConnected) {
+ throw new IOException("already connected");
+ }
+
+ implCreateIfNeeded();
+ impl.connect(endpoint, 0);
+ isConnected = true;
+ isBound = true;
+ }
+ }
+
+ /**
+ * Binds this socket to an endpoint name. May only be called on an instance
+ * that has not yet been bound.
+ *
+ * @param bindpoint endpoint address
+ * @throws IOException
+ */
+ public void bind(LocalSocketAddress bindpoint) throws IOException {
+ implCreateIfNeeded();
+
+ synchronized (this) {
+ if (isBound) {
+ throw new IOException("already bound");
+ }
+
+ localAddress = bindpoint;
+ impl.bind(localAddress);
+ isBound = true;
+ }
+ }
+
+ /**
+ * Retrieves the name that this socket is bound to, if any.
+ *
+ * @return Local address or null if anonymous
+ */
+ public LocalSocketAddress getLocalSocketAddress() {
+ return localAddress;
+ }
+
+ /**
+ * Retrieves the input stream for this instance.
+ *
+ * @return input stream
+ * @throws IOException if socket has been closed or cannot be created.
+ */
+ public InputStream getInputStream() throws IOException {
+ implCreateIfNeeded();
+ return impl.getInputStream();
+ }
+
+ /**
+ * Retrieves the output stream for this instance.
+ *
+ * @return output stream
+ * @throws IOException if socket has been closed or cannot be created.
+ */
+ public OutputStream getOutputStream() throws IOException {
+ implCreateIfNeeded();
+ return impl.getOutputStream();
+ }
+
+ /**
+ * Closes the socket.
+ *
+ * @throws IOException
+ */
+ @Override
+ public void close() throws IOException {
+ implCreateIfNeeded();
+ impl.close();
+ }
+
+ /**
+ * Shuts down the input side of the socket.
+ *
+ * @throws IOException
+ */
+ public void shutdownInput() throws IOException {
+ implCreateIfNeeded();
+ impl.shutdownInput();
+ }
+
+ /**
+ * Shuts down the output side of the socket.
+ *
+ * @throws IOException
+ */
+ public void shutdownOutput() throws IOException {
+ implCreateIfNeeded();
+ impl.shutdownOutput();
+ }
+
+ public void setReceiveBufferSize(int size) throws IOException {
+ impl.setOption(SocketOptions.SO_RCVBUF, Integer.valueOf(size));
+ }
+
+ public int getReceiveBufferSize() throws IOException {
+ return ((Integer) impl.getOption(SocketOptions.SO_RCVBUF)).intValue();
+ }
+
+ public void setSoTimeout(int n) throws IOException {
+ impl.setOption(SocketOptions.SO_TIMEOUT, Integer.valueOf(n));
+ }
+
+ public int getSoTimeout() throws IOException {
+ return ((Integer) impl.getOption(SocketOptions.SO_TIMEOUT)).intValue();
+ }
+
+ public void setSendBufferSize(int n) throws IOException {
+ impl.setOption(SocketOptions.SO_SNDBUF, Integer.valueOf(n));
+ }
+
+ public int getSendBufferSize() throws IOException {
+ return ((Integer) impl.getOption(SocketOptions.SO_SNDBUF)).intValue();
+ }
+
+ //???SEC
+ public LocalSocketAddress getRemoteSocketAddress() {
+ throw new UnsupportedOperationException();
+ }
+
+ //???SEC
+ public synchronized boolean isConnected() {
+ return isConnected;
+ }
+
+ //???SEC
+ public boolean isClosed() {
+ throw new UnsupportedOperationException();
+ }
+
+ //???SEC
+ public synchronized boolean isBound() {
+ return isBound;
+ }
+
+ //???SEC
+ public boolean isOutputShutdown() {
+ throw new UnsupportedOperationException();
+ }
+
+ //???SEC
+ public boolean isInputShutdown() {
+ throw new UnsupportedOperationException();
+ }
+
+ //???SEC
+ public void connect(LocalSocketAddress endpoint, int timeout)
+ throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Enqueues a set of file descriptors to send to the peer. The queue
+ * is one deep. The file descriptors will be sent with the next write
+ * of normal data, and will be delivered in a single ancillary message.
+ * See "man 7 unix" SCM_RIGHTS on a desktop Linux machine.
+ *
+ * @param fds non-null; file descriptors to send.
+ */
+ public void setFileDescriptorsForSend(FileDescriptor[] fds) {
+ impl.setFileDescriptorsForSend(fds);
+ }
+
+ /**
+ * Retrieves a set of file descriptors that a peer has sent through
+ * an ancillary message. This method retrieves the most recent set sent,
+ * and then returns null until a new set arrives.
+ * File descriptors may only be passed along with regular data, so this
+ * method can only return a non-null after a read operation.
+ *
+ * @return null or file descriptor array
+ * @throws IOException
+ */
+ public FileDescriptor[] getAncillaryFileDescriptors() throws IOException {
+ return impl.getAncillaryFileDescriptors();
+ }
+
+ /**
+ * Retrieves the credentials of this socket's peer. Only valid on
+ * connected sockets.
+ *
+ * @return non-null; peer credentials
+ * @throws IOException
+ */
+ public Credentials getPeerCredentials() throws IOException {
+ return impl.getPeerCredentials();
+ }
+
+ /**
+ * Returns file descriptor or null if not yet open/already closed
+ *
+ * @return fd or null
+ */
+ public FileDescriptor getFileDescriptor() {
+ return impl.getFileDescriptor();
+ }
+}
diff --git a/android/net/LocalSocketAddress.java b/android/net/LocalSocketAddress.java
new file mode 100644
index 0000000..8265b85
--- /dev/null
+++ b/android/net/LocalSocketAddress.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2007 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;
+
+/**
+ * A UNIX-domain (AF_LOCAL) socket address. For use with
+ * android.net.LocalSocket and android.net.LocalServerSocket.
+ *
+ * On the Android system, these names refer to names in the Linux
+ * abstract (non-filesystem) UNIX domain namespace.
+ */
+public class LocalSocketAddress
+{
+ /**
+ * The namespace that this address exists in. See also
+ * include/cutils/sockets.h ANDROID_SOCKET_NAMESPACE_*
+ */
+ public enum Namespace {
+ /** A socket in the Linux abstract namespace */
+ ABSTRACT(0),
+ /**
+ * A socket in the Android reserved namespace in /dev/socket.
+ * Only the init process may create a socket here.
+ */
+ RESERVED(1),
+ /**
+ * A socket named with a normal filesystem path.
+ */
+ FILESYSTEM(2);
+
+ /** The id matches with a #define in include/cutils/sockets.h */
+ private int id;
+ Namespace (int id) {
+ this.id = id;
+ }
+
+ /**
+ * @return int constant shared with native code
+ */
+ /*package*/ int getId() {
+ return id;
+ }
+ }
+
+ private final String name;
+ private final Namespace namespace;
+
+ /**
+ * Creates an instance with a given name.
+ *
+ * @param name non-null name
+ * @param namespace namespace the name should be created in.
+ */
+ public LocalSocketAddress(String name, Namespace namespace) {
+ this.name = name;
+ this.namespace = namespace;
+ }
+
+ /**
+ * Creates an instance with a given name in the {@link Namespace#ABSTRACT}
+ * namespace
+ *
+ * @param name non-null name
+ */
+ public LocalSocketAddress(String name) {
+ this(name,Namespace.ABSTRACT);
+ }
+
+ /**
+ * Retrieves the string name of this address
+ * @return string name
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * Returns the namespace used by this address.
+ *
+ * @return non-null a namespace
+ */
+ public Namespace getNamespace() {
+ return namespace;
+ }
+}
diff --git a/android/net/LocalSocketImpl.java b/android/net/LocalSocketImpl.java
new file mode 100644
index 0000000..fe7632c
--- /dev/null
+++ b/android/net/LocalSocketImpl.java
@@ -0,0 +1,622 @@
+/*
+ * Copyright (C) 2007 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;
+
+import android.annotation.UnsupportedAppUsage;
+import android.system.ErrnoException;
+import android.system.Int32Ref;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructLinger;
+import android.system.StructTimeval;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.SocketOptions;
+
+/**
+ * Socket implementation used for android.net.LocalSocket and
+ * android.net.LocalServerSocket. Supports only AF_LOCAL sockets.
+ */
+class LocalSocketImpl
+{
+ private SocketInputStream fis;
+ private SocketOutputStream fos;
+ private Object readMonitor = new Object();
+ private Object writeMonitor = new Object();
+
+ /** null if closed or not yet created */
+ private FileDescriptor fd;
+ /** whether fd is created internally */
+ private boolean mFdCreatedInternally;
+
+ // These fields are accessed by native code;
+ /** file descriptor array received during a previous read */
+ @UnsupportedAppUsage
+ FileDescriptor[] inboundFileDescriptors;
+ /** file descriptor array that should be written during next write */
+ @UnsupportedAppUsage
+ FileDescriptor[] outboundFileDescriptors;
+
+ /**
+ * An input stream for local sockets. Needed because we may
+ * need to read ancillary data.
+ */
+ class SocketInputStream extends InputStream {
+ /** {@inheritDoc} */
+ @Override
+ public int available() throws IOException {
+ FileDescriptor myFd = fd;
+ if (myFd == null) throw new IOException("socket closed");
+
+ Int32Ref avail = new Int32Ref(0);
+ try {
+ Os.ioctlInt(myFd, OsConstants.FIONREAD, avail);
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ }
+ return avail.value;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void close() throws IOException {
+ LocalSocketImpl.this.close();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int read() throws IOException {
+ int ret;
+ synchronized (readMonitor) {
+ FileDescriptor myFd = fd;
+ if (myFd == null) throw new IOException("socket closed");
+
+ ret = read_native(myFd);
+ return ret;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ synchronized (readMonitor) {
+ FileDescriptor myFd = fd;
+ if (myFd == null) throw new IOException("socket closed");
+
+ if (off < 0 || len < 0 || (off + len) > b.length ) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+
+ int ret = readba_native(b, off, len, myFd);
+
+ return ret;
+ }
+ }
+ }
+
+ /**
+ * An output stream for local sockets. Needed because we may
+ * need to read ancillary data.
+ */
+ class SocketOutputStream extends OutputStream {
+ /** {@inheritDoc} */
+ @Override
+ public void close() throws IOException {
+ LocalSocketImpl.this.close();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void write (byte[] b) throws IOException {
+ write(b, 0, b.length);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void write (byte[] b, int off, int len) throws IOException {
+ synchronized (writeMonitor) {
+ FileDescriptor myFd = fd;
+ if (myFd == null) throw new IOException("socket closed");
+
+ if (off < 0 || len < 0 || (off + len) > b.length ) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ writeba_native(b, off, len, myFd);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void write (int b) throws IOException {
+ synchronized (writeMonitor) {
+ FileDescriptor myFd = fd;
+ if (myFd == null) throw new IOException("socket closed");
+ write_native(b, myFd);
+ }
+ }
+
+ /**
+ * Wait until the data in sending queue is emptied. A polling version
+ * for flush implementation.
+ * @throws IOException
+ * if an i/o error occurs.
+ */
+ @Override
+ public void flush() throws IOException {
+ FileDescriptor myFd = fd;
+ if (myFd == null) throw new IOException("socket closed");
+
+ // Loop until the output buffer is empty.
+ Int32Ref pending = new Int32Ref(0);
+ while (true) {
+ try {
+ // See linux/net/unix/af_unix.c
+ Os.ioctlInt(myFd, OsConstants.TIOCOUTQ, pending);
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ }
+
+ if (pending.value <= 0) {
+ // The output buffer is empty.
+ break;
+ }
+
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException ie) {
+ break;
+ }
+ }
+ }
+ }
+
+ private native int read_native(FileDescriptor fd) throws IOException;
+ private native int readba_native(byte[] b, int off, int len,
+ FileDescriptor fd) throws IOException;
+ private native void writeba_native(byte[] b, int off, int len,
+ FileDescriptor fd) throws IOException;
+ private native void write_native(int b, FileDescriptor fd)
+ throws IOException;
+ private native void connectLocal(FileDescriptor fd, String name,
+ int namespace) throws IOException;
+ private native void bindLocal(FileDescriptor fd, String name, int namespace)
+ throws IOException;
+ private native Credentials getPeerCredentials_native(
+ FileDescriptor fd) throws IOException;
+
+ /**
+ * Create a new instance.
+ */
+ @UnsupportedAppUsage
+ /*package*/ LocalSocketImpl()
+ {
+ }
+
+ /**
+ * Create a new instance from a file descriptor representing
+ * a bound socket. The state of the file descriptor is not checked here
+ * but the caller can verify socket state by calling listen().
+ *
+ * @param fd non-null; bound file descriptor
+ */
+ /*package*/ LocalSocketImpl(FileDescriptor fd)
+ {
+ this.fd = fd;
+ }
+
+ public String toString() {
+ return super.toString() + " fd:" + fd;
+ }
+
+ /**
+ * Creates a socket in the underlying OS.
+ *
+ * @param sockType either {@link LocalSocket#SOCKET_DGRAM}, {@link LocalSocket#SOCKET_STREAM}
+ * or {@link LocalSocket#SOCKET_SEQPACKET}
+ * @throws IOException
+ */
+ public void create(int sockType) throws IOException {
+ if (fd != null) {
+ throw new IOException("LocalSocketImpl already has an fd");
+ }
+
+ int osType;
+ switch (sockType) {
+ case LocalSocket.SOCKET_DGRAM:
+ osType = OsConstants.SOCK_DGRAM;
+ break;
+ case LocalSocket.SOCKET_STREAM:
+ osType = OsConstants.SOCK_STREAM;
+ break;
+ case LocalSocket.SOCKET_SEQPACKET:
+ osType = OsConstants.SOCK_SEQPACKET;
+ break;
+ default:
+ throw new IllegalStateException("unknown sockType");
+ }
+ try {
+ fd = Os.socket(OsConstants.AF_UNIX, osType, 0);
+ mFdCreatedInternally = true;
+ } catch (ErrnoException e) {
+ e.rethrowAsIOException();
+ }
+ }
+
+ /**
+ * Closes the socket.
+ *
+ * @throws IOException
+ */
+ public void close() throws IOException {
+ synchronized (LocalSocketImpl.this) {
+ if ((fd == null) || (mFdCreatedInternally == false)) {
+ fd = null;
+ return;
+ }
+ try {
+ Os.close(fd);
+ } catch (ErrnoException e) {
+ e.rethrowAsIOException();
+ }
+ fd = null;
+ }
+ }
+
+ /** note timeout presently ignored */
+ protected void connect(LocalSocketAddress address, int timeout)
+ throws IOException
+ {
+ if (fd == null) {
+ throw new IOException("socket not created");
+ }
+
+ connectLocal(fd, address.getName(), address.getNamespace().getId());
+ }
+
+ /**
+ * Binds this socket to an endpoint name. May only be called on an instance
+ * that has not yet been bound.
+ *
+ * @param endpoint endpoint address
+ * @throws IOException
+ */
+ public void bind(LocalSocketAddress endpoint) throws IOException
+ {
+ if (fd == null) {
+ throw new IOException("socket not created");
+ }
+
+ bindLocal(fd, endpoint.getName(), endpoint.getNamespace().getId());
+ }
+
+ protected void listen(int backlog) throws IOException
+ {
+ if (fd == null) {
+ throw new IOException("socket not created");
+ }
+ try {
+ Os.listen(fd, backlog);
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ }
+ }
+
+ /**
+ * Accepts a new connection to the socket. Blocks until a new
+ * connection arrives.
+ *
+ * @param s a socket that will be used to represent the new connection.
+ * @throws IOException
+ */
+ protected void accept(LocalSocketImpl s) throws IOException {
+ if (fd == null) {
+ throw new IOException("socket not created");
+ }
+
+ try {
+ s.fd = Os.accept(fd, null /* address */);
+ s.mFdCreatedInternally = true;
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ }
+ }
+
+ /**
+ * Retrieves the input stream for this instance.
+ *
+ * @return input stream
+ * @throws IOException if socket has been closed or cannot be created.
+ */
+ protected InputStream getInputStream() throws IOException
+ {
+ if (fd == null) {
+ throw new IOException("socket not created");
+ }
+
+ synchronized (this) {
+ if (fis == null) {
+ fis = new SocketInputStream();
+ }
+
+ return fis;
+ }
+ }
+
+ /**
+ * Retrieves the output stream for this instance.
+ *
+ * @return output stream
+ * @throws IOException if socket has been closed or cannot be created.
+ */
+ protected OutputStream getOutputStream() throws IOException
+ {
+ if (fd == null) {
+ throw new IOException("socket not created");
+ }
+
+ synchronized (this) {
+ if (fos == null) {
+ fos = new SocketOutputStream();
+ }
+
+ return fos;
+ }
+ }
+
+ /**
+ * Returns the number of bytes available for reading without blocking.
+ *
+ * @return >= 0 count bytes available
+ * @throws IOException
+ */
+ protected int available() throws IOException
+ {
+ return getInputStream().available();
+ }
+
+ /**
+ * Shuts down the input side of the socket.
+ *
+ * @throws IOException
+ */
+ protected void shutdownInput() throws IOException
+ {
+ if (fd == null) {
+ throw new IOException("socket not created");
+ }
+
+ try {
+ Os.shutdown(fd, OsConstants.SHUT_RD);
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ }
+ }
+
+ /**
+ * Shuts down the output side of the socket.
+ *
+ * @throws IOException
+ */
+ protected void shutdownOutput() throws IOException
+ {
+ if (fd == null) {
+ throw new IOException("socket not created");
+ }
+
+ try {
+ Os.shutdown(fd, OsConstants.SHUT_WR);
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ }
+ }
+
+ protected FileDescriptor getFileDescriptor()
+ {
+ return fd;
+ }
+
+ protected boolean supportsUrgentData()
+ {
+ return false;
+ }
+
+ protected void sendUrgentData(int data) throws IOException
+ {
+ throw new RuntimeException ("not impled");
+ }
+
+ public Object getOption(int optID) throws IOException
+ {
+ if (fd == null) {
+ throw new IOException("socket not created");
+ }
+
+ try {
+ Object toReturn;
+ switch (optID) {
+ case SocketOptions.SO_TIMEOUT:
+ StructTimeval timeval = Os.getsockoptTimeval(fd, OsConstants.SOL_SOCKET,
+ OsConstants.SO_SNDTIMEO);
+ toReturn = (int) timeval.toMillis();
+ break;
+ case SocketOptions.SO_RCVBUF:
+ case SocketOptions.SO_SNDBUF:
+ case SocketOptions.SO_REUSEADDR:
+ int osOpt = javaSoToOsOpt(optID);
+ toReturn = Os.getsockoptInt(fd, OsConstants.SOL_SOCKET, osOpt);
+ break;
+ case SocketOptions.SO_LINGER:
+ StructLinger linger=
+ Os.getsockoptLinger(fd, OsConstants.SOL_SOCKET, OsConstants.SO_LINGER);
+ if (!linger.isOn()) {
+ toReturn = -1;
+ } else {
+ toReturn = linger.l_linger;
+ }
+ break;
+ case SocketOptions.TCP_NODELAY:
+ toReturn = Os.getsockoptInt(fd, OsConstants.IPPROTO_TCP,
+ OsConstants.TCP_NODELAY);
+ break;
+ default:
+ throw new IOException("Unknown option: " + optID);
+ }
+ return toReturn;
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ }
+ }
+
+ public void setOption(int optID, Object value)
+ throws IOException {
+
+ if (fd == null) {
+ throw new IOException("socket not created");
+ }
+
+ /*
+ * Boolean.FALSE is used to disable some options, so it
+ * is important to distinguish between FALSE and unset.
+ * We define it here that -1 is unset, 0 is FALSE, and 1
+ * is TRUE.
+ */
+ int boolValue = -1;
+ int intValue = 0;
+ if (value instanceof Integer) {
+ intValue = (Integer)value;
+ } else if (value instanceof Boolean) {
+ boolValue = ((Boolean) value)? 1 : 0;
+ } else {
+ throw new IOException("bad value: " + value);
+ }
+
+ try {
+ switch (optID) {
+ case SocketOptions.SO_LINGER:
+ StructLinger linger = new StructLinger(boolValue, intValue);
+ Os.setsockoptLinger(fd, OsConstants.SOL_SOCKET, OsConstants.SO_LINGER, linger);
+ break;
+ case SocketOptions.SO_TIMEOUT:
+ // The option must set both send and receive timeouts.
+ // Note: The incoming timeout value is in milliseconds.
+ StructTimeval timeval = StructTimeval.fromMillis(intValue);
+ Os.setsockoptTimeval(fd, OsConstants.SOL_SOCKET, OsConstants.SO_RCVTIMEO,
+ timeval);
+ Os.setsockoptTimeval(fd, OsConstants.SOL_SOCKET, OsConstants.SO_SNDTIMEO,
+ timeval);
+ break;
+ case SocketOptions.SO_RCVBUF:
+ case SocketOptions.SO_SNDBUF:
+ case SocketOptions.SO_REUSEADDR:
+ int osOpt = javaSoToOsOpt(optID);
+ Os.setsockoptInt(fd, OsConstants.SOL_SOCKET, osOpt, intValue);
+ break;
+ case SocketOptions.TCP_NODELAY:
+ Os.setsockoptInt(fd, OsConstants.IPPROTO_TCP, OsConstants.TCP_NODELAY,
+ intValue);
+ break;
+ default:
+ throw new IOException("Unknown option: " + optID);
+ }
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ }
+ }
+
+ /**
+ * Enqueues a set of file descriptors to send to the peer. The queue
+ * is one deep. The file descriptors will be sent with the next write
+ * of normal data, and will be delivered in a single ancillary message.
+ * See "man 7 unix" SCM_RIGHTS on a desktop Linux machine.
+ *
+ * @param fds non-null; file descriptors to send.
+ * @throws IOException
+ */
+ public void setFileDescriptorsForSend(FileDescriptor[] fds) {
+ synchronized(writeMonitor) {
+ outboundFileDescriptors = fds;
+ }
+ }
+
+ /**
+ * Retrieves a set of file descriptors that a peer has sent through
+ * an ancillary message. This method retrieves the most recent set sent,
+ * and then returns null until a new set arrives.
+ * File descriptors may only be passed along with regular data, so this
+ * method can only return a non-null after a read operation.
+ *
+ * @return null or file descriptor array
+ * @throws IOException
+ */
+ public FileDescriptor[] getAncillaryFileDescriptors() throws IOException {
+ synchronized(readMonitor) {
+ FileDescriptor[] result = inboundFileDescriptors;
+
+ inboundFileDescriptors = null;
+ return result;
+ }
+ }
+
+ /**
+ * Retrieves the credentials of this socket's peer. Only valid on
+ * connected sockets.
+ *
+ * @return non-null; peer credentials
+ * @throws IOException
+ */
+ public Credentials getPeerCredentials() throws IOException {
+ return getPeerCredentials_native(fd);
+ }
+
+ /**
+ * Retrieves the socket name from the OS.
+ *
+ * @return non-null; socket name
+ * @throws IOException on failure
+ */
+ public LocalSocketAddress getSockAddress() throws IOException {
+ // This method has never been implemented.
+ return null;
+ }
+
+ @Override
+ protected void finalize() throws IOException {
+ close();
+ }
+
+ private static int javaSoToOsOpt(int optID) {
+ switch (optID) {
+ case SocketOptions.SO_SNDBUF:
+ return OsConstants.SO_SNDBUF;
+ case SocketOptions.SO_RCVBUF:
+ return OsConstants.SO_RCVBUF;
+ case SocketOptions.SO_REUSEADDR:
+ return OsConstants.SO_REUSEADDR;
+ default:
+ throw new UnsupportedOperationException("Unknown option: " + optID);
+ }
+ }
+}
diff --git a/android/net/MacAddress.java b/android/net/MacAddress.java
new file mode 100644
index 0000000..aa8e010
--- /dev/null
+++ b/android/net/MacAddress.java
@@ -0,0 +1,446 @@
+/*
+ * Copyright 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 android.net;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.BitUtils;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.Inet6Address;
+import java.net.UnknownHostException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.Random;
+
+/**
+ * Representation of a MAC address.
+ *
+ * This class only supports 48 bits long addresses and does not support 64 bits long addresses.
+ * Instances of this class are immutable.
+ */
+public final class MacAddress implements Parcelable {
+
+ private static final int ETHER_ADDR_LEN = 6;
+ private static final byte[] ETHER_ADDR_BROADCAST = addr(0xff, 0xff, 0xff, 0xff, 0xff, 0xff);
+
+ /**
+ * The MacAddress representing the unique broadcast MAC address.
+ */
+ public static final MacAddress BROADCAST_ADDRESS = MacAddress.fromBytes(ETHER_ADDR_BROADCAST);
+
+ /**
+ * The MacAddress zero MAC address.
+ *
+ * <p>Not publicly exposed or treated specially since the OUI 00:00:00 is registered.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static final MacAddress ALL_ZEROS_ADDRESS = new MacAddress(0);
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_UNKNOWN,
+ TYPE_UNICAST,
+ TYPE_MULTICAST,
+ TYPE_BROADCAST,
+ })
+ public @interface MacAddressType { }
+
+ /** @hide Indicates a MAC address of unknown type. */
+ public static final int TYPE_UNKNOWN = 0;
+ /** Indicates a MAC address is a unicast address. */
+ public static final int TYPE_UNICAST = 1;
+ /** Indicates a MAC address is a multicast address. */
+ public static final int TYPE_MULTICAST = 2;
+ /** Indicates a MAC address is the broadcast address. */
+ public static final int TYPE_BROADCAST = 3;
+
+ private static final long VALID_LONG_MASK = (1L << 48) - 1;
+ private static final long LOCALLY_ASSIGNED_MASK = MacAddress.fromString("2:0:0:0:0:0").mAddr;
+ private static final long MULTICAST_MASK = MacAddress.fromString("1:0:0:0:0:0").mAddr;
+ private static final long OUI_MASK = MacAddress.fromString("ff:ff:ff:0:0:0").mAddr;
+ private static final long NIC_MASK = MacAddress.fromString("0:0:0:ff:ff:ff").mAddr;
+ private static final MacAddress BASE_GOOGLE_MAC = MacAddress.fromString("da:a1:19:0:0:0");
+
+ // Internal representation of the MAC address as a single 8 byte long.
+ // The encoding scheme sets the two most significant bytes to 0. The 6 bytes of the
+ // MAC address are encoded in the 6 least significant bytes of the long, where the first
+ // byte of the array is mapped to the 3rd highest logical byte of the long, the second
+ // byte of the array is mapped to the 4th highest logical byte of the long, and so on.
+ private final long mAddr;
+
+ private MacAddress(long addr) {
+ mAddr = (VALID_LONG_MASK & addr);
+ }
+
+ /**
+ * Returns the type of this address.
+ *
+ * @return the int constant representing the MAC address type of this MacAddress.
+ */
+ public @MacAddressType int getAddressType() {
+ if (equals(BROADCAST_ADDRESS)) {
+ return TYPE_BROADCAST;
+ }
+ if (isMulticastAddress()) {
+ return TYPE_MULTICAST;
+ }
+ return TYPE_UNICAST;
+ }
+
+ /**
+ * @return true if this MacAddress is a multicast address.
+ * @hide
+ */
+ public boolean isMulticastAddress() {
+ return (mAddr & MULTICAST_MASK) != 0;
+ }
+
+ /**
+ * @return true if this MacAddress is a locally assigned address.
+ */
+ public boolean isLocallyAssigned() {
+ return (mAddr & LOCALLY_ASSIGNED_MASK) != 0;
+ }
+
+ /**
+ * @return a byte array representation of this MacAddress.
+ */
+ public @NonNull byte[] toByteArray() {
+ return byteAddrFromLongAddr(mAddr);
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return stringAddrFromLongAddr(mAddr);
+ }
+
+ /**
+ * @return a String representation of the OUI part of this MacAddress made of 3 hexadecimal
+ * numbers in [0,ff] joined by ':' characters.
+ */
+ public @NonNull String toOuiString() {
+ return String.format(
+ "%02x:%02x:%02x", (mAddr >> 40) & 0xff, (mAddr >> 32) & 0xff, (mAddr >> 24) & 0xff);
+ }
+
+ @Override
+ public int hashCode() {
+ return (int) ((mAddr >> 32) ^ mAddr);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return (o instanceof MacAddress) && ((MacAddress) o).mAddr == mAddr;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(mAddr);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<MacAddress> CREATOR =
+ new Parcelable.Creator<MacAddress>() {
+ public MacAddress createFromParcel(Parcel in) {
+ return new MacAddress(in.readLong());
+ }
+
+ public MacAddress[] newArray(int size) {
+ return new MacAddress[size];
+ }
+ };
+
+ /**
+ * Returns true if the given byte array is an valid MAC address.
+ * A valid byte array representation for a MacAddress is a non-null array of length 6.
+ *
+ * @param addr a byte array.
+ * @return true if the given byte array is not null and has the length of a MAC address.
+ *
+ * @hide
+ */
+ public static boolean isMacAddress(byte[] addr) {
+ return addr != null && addr.length == ETHER_ADDR_LEN;
+ }
+
+ /**
+ * Returns the MAC address type of the MAC address represented by the given byte array,
+ * or null if the given byte array does not represent a MAC address.
+ * A valid byte array representation for a MacAddress is a non-null array of length 6.
+ *
+ * @param addr a byte array representing a MAC address.
+ * @return the int constant representing the MAC address type of the MAC address represented
+ * by the given byte array, or type UNKNOWN if the byte array is not a valid MAC address.
+ *
+ * @hide
+ */
+ public static int macAddressType(byte[] addr) {
+ if (!isMacAddress(addr)) {
+ return TYPE_UNKNOWN;
+ }
+ return MacAddress.fromBytes(addr).getAddressType();
+ }
+
+ /**
+ * Converts a String representation of a MAC address to a byte array representation.
+ * A valid String representation for a MacAddress is a series of 6 values in the
+ * range [0,ff] printed in hexadecimal and joined by ':' characters.
+ *
+ * @param addr a String representation of a MAC address.
+ * @return the byte representation of the MAC address.
+ * @throws IllegalArgumentException if the given String is not a valid representation.
+ *
+ * @hide
+ */
+ public static @NonNull byte[] byteAddrFromStringAddr(String addr) {
+ Preconditions.checkNotNull(addr);
+ String[] parts = addr.split(":");
+ if (parts.length != ETHER_ADDR_LEN) {
+ throw new IllegalArgumentException(addr + " was not a valid MAC address");
+ }
+ byte[] bytes = new byte[ETHER_ADDR_LEN];
+ for (int i = 0; i < ETHER_ADDR_LEN; i++) {
+ int x = Integer.valueOf(parts[i], 16);
+ if (x < 0 || 0xff < x) {
+ throw new IllegalArgumentException(addr + "was not a valid MAC address");
+ }
+ bytes[i] = (byte) x;
+ }
+ return bytes;
+ }
+
+ /**
+ * Converts a byte array representation of a MAC address to a String representation made
+ * of 6 hexadecimal numbers in [0,ff] joined by ':' characters.
+ * A valid byte array representation for a MacAddress is a non-null array of length 6.
+ *
+ * @param addr a byte array representation of a MAC address.
+ * @return the String representation of the MAC address.
+ * @throws IllegalArgumentException if the given byte array is not a valid representation.
+ *
+ * @hide
+ */
+ public static @NonNull String stringAddrFromByteAddr(byte[] addr) {
+ if (!isMacAddress(addr)) {
+ return null;
+ }
+ return String.format("%02x:%02x:%02x:%02x:%02x:%02x",
+ addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
+ }
+
+ private static byte[] byteAddrFromLongAddr(long addr) {
+ byte[] bytes = new byte[ETHER_ADDR_LEN];
+ int index = ETHER_ADDR_LEN;
+ while (index-- > 0) {
+ bytes[index] = (byte) addr;
+ addr = addr >> 8;
+ }
+ return bytes;
+ }
+
+ private static long longAddrFromByteAddr(byte[] addr) {
+ Preconditions.checkNotNull(addr);
+ if (!isMacAddress(addr)) {
+ throw new IllegalArgumentException(
+ Arrays.toString(addr) + " was not a valid MAC address");
+ }
+ long longAddr = 0;
+ for (byte b : addr) {
+ longAddr = (longAddr << 8) + BitUtils.uint8(b);
+ }
+ return longAddr;
+ }
+
+ // Internal conversion function equivalent to longAddrFromByteAddr(byteAddrFromStringAddr(addr))
+ // that avoids the allocation of an intermediary byte[].
+ private static long longAddrFromStringAddr(String addr) {
+ Preconditions.checkNotNull(addr);
+ String[] parts = addr.split(":");
+ if (parts.length != ETHER_ADDR_LEN) {
+ throw new IllegalArgumentException(addr + " was not a valid MAC address");
+ }
+ long longAddr = 0;
+ for (int i = 0; i < parts.length; i++) {
+ int x = Integer.valueOf(parts[i], 16);
+ if (x < 0 || 0xff < x) {
+ throw new IllegalArgumentException(addr + "was not a valid MAC address");
+ }
+ longAddr = x + (longAddr << 8);
+ }
+ return longAddr;
+ }
+
+ // Internal conversion function equivalent to stringAddrFromByteAddr(byteAddrFromLongAddr(addr))
+ // that avoids the allocation of an intermediary byte[].
+ private static @NonNull String stringAddrFromLongAddr(long addr) {
+ return String.format("%02x:%02x:%02x:%02x:%02x:%02x",
+ (addr >> 40) & 0xff,
+ (addr >> 32) & 0xff,
+ (addr >> 24) & 0xff,
+ (addr >> 16) & 0xff,
+ (addr >> 8) & 0xff,
+ addr & 0xff);
+ }
+
+ /**
+ * Creates a MacAddress from the given String representation. A valid String representation
+ * for a MacAddress is a series of 6 values in the range [0,ff] printed in hexadecimal
+ * and joined by ':' characters.
+ *
+ * @param addr a String representation of a MAC address.
+ * @return the MacAddress corresponding to the given String representation.
+ * @throws IllegalArgumentException if the given String is not a valid representation.
+ */
+ public static @NonNull MacAddress fromString(@NonNull String addr) {
+ return new MacAddress(longAddrFromStringAddr(addr));
+ }
+
+ /**
+ * Creates a MacAddress from the given byte array representation.
+ * A valid byte array representation for a MacAddress is a non-null array of length 6.
+ *
+ * @param addr a byte array representation of a MAC address.
+ * @return the MacAddress corresponding to the given byte array representation.
+ * @throws IllegalArgumentException if the given byte array is not a valid representation.
+ */
+ public static @NonNull MacAddress fromBytes(@NonNull byte[] addr) {
+ return new MacAddress(longAddrFromByteAddr(addr));
+ }
+
+ /**
+ * Returns a generated MAC address whose 24 least significant bits constituting the
+ * NIC part of the address are randomly selected and has Google OUI base.
+ *
+ * The locally assigned bit is always set to 1. The multicast bit is always set to 0.
+ *
+ * @return a random locally assigned, unicast MacAddress with Google OUI.
+ *
+ * @hide
+ */
+ public static @NonNull MacAddress createRandomUnicastAddressWithGoogleBase() {
+ return createRandomUnicastAddress(BASE_GOOGLE_MAC, new SecureRandom());
+ }
+
+ /**
+ * Returns a generated MAC address whose 46 bits, excluding the locally assigned bit and the
+ * unicast bit, are randomly selected.
+ *
+ * The locally assigned bit is always set to 1. The multicast bit is always set to 0.
+ *
+ * @return a random locally assigned, unicast MacAddress.
+ *
+ * @hide
+ */
+ public static @NonNull MacAddress createRandomUnicastAddress() {
+ SecureRandom r = new SecureRandom();
+ long addr = r.nextLong() & VALID_LONG_MASK;
+ addr |= LOCALLY_ASSIGNED_MASK;
+ addr &= ~MULTICAST_MASK;
+ return new MacAddress(addr);
+ }
+
+ /**
+ * Returns a randomly generated MAC address using the given Random object and the same
+ * OUI values as the given MacAddress.
+ *
+ * The locally assigned bit is always set to 1. The multicast bit is always set to 0.
+ *
+ * @param base a base MacAddress whose OUI is used for generating the random address.
+ * @param r a standard Java Random object used for generating the random address.
+ * @return a random locally assigned MacAddress.
+ *
+ * @hide
+ */
+ public static @NonNull MacAddress createRandomUnicastAddress(MacAddress base, Random r) {
+ long addr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong());
+ addr |= LOCALLY_ASSIGNED_MASK;
+ addr &= ~MULTICAST_MASK;
+ return new MacAddress(addr);
+ }
+
+ // Convenience function for working around the lack of byte literals.
+ private static byte[] addr(int... in) {
+ if (in.length != ETHER_ADDR_LEN) {
+ throw new IllegalArgumentException(Arrays.toString(in)
+ + " was not an array with length equal to " + ETHER_ADDR_LEN);
+ }
+ byte[] out = new byte[ETHER_ADDR_LEN];
+ for (int i = 0; i < ETHER_ADDR_LEN; i++) {
+ out[i] = (byte) in[i];
+ }
+ return out;
+ }
+
+ /**
+ * Checks if this MAC Address matches the provided range.
+ *
+ * @param baseAddress MacAddress representing the base address to compare with.
+ * @param mask MacAddress representing the mask to use during comparison.
+ * @return true if this MAC Address matches the given range.
+ *
+ * @hide
+ */
+ public boolean matches(@NonNull MacAddress baseAddress, @NonNull MacAddress mask) {
+ Preconditions.checkNotNull(baseAddress);
+ Preconditions.checkNotNull(mask);
+ return (mAddr & mask.mAddr) == (baseAddress.mAddr & mask.mAddr);
+ }
+
+ /**
+ * Create a link-local Inet6Address from the MAC address. The EUI-48 MAC address is converted
+ * to an EUI-64 MAC address per RFC 4291. The resulting EUI-64 is used to construct a link-local
+ * IPv6 address per RFC 4862.
+ *
+ * @return A link-local Inet6Address constructed from the MAC address.
+ * @hide
+ */
+ public @Nullable Inet6Address getLinkLocalIpv6FromEui48Mac() {
+ byte[] macEui48Bytes = toByteArray();
+ byte[] addr = new byte[16];
+
+ addr[0] = (byte) 0xfe;
+ addr[1] = (byte) 0x80;
+ addr[8] = (byte) (macEui48Bytes[0] ^ (byte) 0x02); // flip the link-local bit
+ addr[9] = macEui48Bytes[1];
+ addr[10] = macEui48Bytes[2];
+ addr[11] = (byte) 0xff;
+ addr[12] = (byte) 0xfe;
+ addr[13] = macEui48Bytes[3];
+ addr[14] = macEui48Bytes[4];
+ addr[15] = macEui48Bytes[5];
+
+ try {
+ return Inet6Address.getByAddress(null, addr, 0);
+ } catch (UnknownHostException e) {
+ return null;
+ }
+ }
+}
diff --git a/android/net/MailTo.java b/android/net/MailTo.java
new file mode 100644
index 0000000..dadb6d9
--- /dev/null
+++ b/android/net/MailTo.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ *
+ * MailTo URL parser
+ *
+ * This class parses a mailto scheme URL and then can be queried for
+ * the parsed parameters. This implements RFC 2368.
+ *
+ */
+public class MailTo {
+
+ static public final String MAILTO_SCHEME = "mailto:";
+
+ // All the parsed content is added to the headers.
+ private HashMap<String, String> mHeaders;
+
+ // Well known headers
+ static private final String TO = "to";
+ static private final String BODY = "body";
+ static private final String CC = "cc";
+ static private final String SUBJECT = "subject";
+
+
+ /**
+ * Test to see if the given string is a mailto URL
+ * @param url string to be tested
+ * @return true if the string is a mailto URL
+ */
+ public static boolean isMailTo(String url) {
+ if (url != null && url.startsWith(MAILTO_SCHEME)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Parse and decode a mailto scheme string. This parser implements
+ * RFC 2368. The returned object can be queried for the parsed parameters.
+ * @param url String containing a mailto URL
+ * @return MailTo object
+ * @exception ParseException if the scheme is not a mailto URL
+ */
+ public static MailTo parse(String url) throws ParseException {
+ if (url == null) {
+ throw new NullPointerException();
+ }
+ if (!isMailTo(url)) {
+ throw new ParseException("Not a mailto scheme");
+ }
+ // Strip the scheme as the Uri parser can't cope with it.
+ String noScheme = url.substring(MAILTO_SCHEME.length());
+ Uri email = Uri.parse(noScheme);
+ MailTo m = new MailTo();
+
+ // Parse out the query parameters
+ String query = email.getQuery();
+ if (query != null ) {
+ String[] queries = query.split("&");
+ for (String q : queries) {
+ String[] nameval = q.split("=");
+ if (nameval.length == 0) {
+ continue;
+ }
+ // insert the headers with the name in lowercase so that
+ // we can easily find common headers
+ m.mHeaders.put(Uri.decode(nameval[0]).toLowerCase(Locale.ROOT),
+ nameval.length > 1 ? Uri.decode(nameval[1]) : null);
+ }
+ }
+
+ // Address can be specified in both the headers and just after the
+ // mailto line. Join the two together.
+ String address = email.getPath();
+ if (address != null) {
+ String addr = m.getTo();
+ if (addr != null) {
+ address += ", " + addr;
+ }
+ m.mHeaders.put(TO, address);
+ }
+
+ return m;
+ }
+
+ /**
+ * Retrieve the To address line from the parsed mailto URL. This could be
+ * several email address that are comma-space delimited.
+ * If no To line was specified, then null is return
+ * @return comma delimited email addresses or null
+ */
+ public String getTo() {
+ return mHeaders.get(TO);
+ }
+
+ /**
+ * Retrieve the CC address line from the parsed mailto URL. This could be
+ * several email address that are comma-space delimited.
+ * If no CC line was specified, then null is return
+ * @return comma delimited email addresses or null
+ */
+ public String getCc() {
+ return mHeaders.get(CC);
+ }
+
+ /**
+ * Retrieve the subject line from the parsed mailto URL.
+ * If no subject line was specified, then null is return
+ * @return subject or null
+ */
+ public String getSubject() {
+ return mHeaders.get(SUBJECT);
+ }
+
+ /**
+ * Retrieve the body line from the parsed mailto URL.
+ * If no body line was specified, then null is return
+ * @return body or null
+ */
+ public String getBody() {
+ return mHeaders.get(BODY);
+ }
+
+ /**
+ * Retrieve all the parsed email headers from the mailto URL
+ * @return map containing all parsed values
+ */
+ public Map<String, String> getHeaders() {
+ return mHeaders;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(MAILTO_SCHEME);
+ sb.append('?');
+ for (Map.Entry<String,String> header : mHeaders.entrySet()) {
+ sb.append(Uri.encode(header.getKey()));
+ sb.append('=');
+ sb.append(Uri.encode(header.getValue()));
+ sb.append('&');
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Private constructor. The only way to build a Mailto object is through
+ * the parse() method.
+ */
+ private MailTo() {
+ mHeaders = new HashMap<String, String>();
+ }
+}
diff --git a/android/net/MatchAllNetworkSpecifier.java b/android/net/MatchAllNetworkSpecifier.java
new file mode 100644
index 0000000..ab4f627
--- /dev/null
+++ b/android/net/MatchAllNetworkSpecifier.java
@@ -0,0 +1,80 @@
+/*
+ * 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 android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * MatchAllNetworkSpecifier is a marker class used by NetworkFactory classes to indicate
+ * that they accept (match) any network specifier in requests.
+ *
+ * The class must never be used as part of a network request (those semantics aren't specified).
+ *
+ * @hide
+ */
+public final class MatchAllNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+ /**
+ * Utility method which verifies that the ns argument is not a MatchAllNetworkSpecifier and
+ * throws an IllegalArgumentException if it is.
+ */
+ public static void checkNotMatchAllNetworkSpecifier(NetworkSpecifier ns) {
+ if (ns instanceof MatchAllNetworkSpecifier) {
+ throw new IllegalArgumentException("A MatchAllNetworkSpecifier is not permitted");
+ }
+ }
+
+ public boolean satisfiedBy(NetworkSpecifier other) {
+ /*
+ * The method is called by a NetworkRequest to see if it is satisfied by a proposed
+ * network (e.g. as offered by a network factory). Since MatchAllNetweorkSpecifier must
+ * not be used in network requests this method should never be called.
+ */
+ throw new IllegalStateException(
+ "MatchAllNetworkSpecifier must not be used in NetworkRequests");
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof MatchAllNetworkSpecifier;
+ }
+
+ @Override
+ public int hashCode() {
+ return 0;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Nothing to write.
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<MatchAllNetworkSpecifier> CREATOR =
+ new Parcelable.Creator<MatchAllNetworkSpecifier>() {
+ public MatchAllNetworkSpecifier createFromParcel(Parcel in) {
+ return new MatchAllNetworkSpecifier();
+ }
+ public MatchAllNetworkSpecifier[] newArray(int size) {
+ return new MatchAllNetworkSpecifier[size];
+ }
+ };
+}
diff --git a/android/net/MobileLinkQualityInfo.java b/android/net/MobileLinkQualityInfo.java
new file mode 100644
index 0000000..06c739d
--- /dev/null
+++ b/android/net/MobileLinkQualityInfo.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2013 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;
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+
+/**
+ * Class that represents useful attributes of mobile network links
+ * such as the upload/download throughput or error rate etc.
+ * @hide
+ */
+public class MobileLinkQualityInfo extends LinkQualityInfo {
+ // Represents TelephonyManager.NetworkType
+ private int mMobileNetworkType = UNKNOWN_INT;
+ private int mRssi = UNKNOWN_INT;
+ private int mGsmErrorRate = UNKNOWN_INT;
+ private int mCdmaDbm = UNKNOWN_INT;
+ private int mCdmaEcio = UNKNOWN_INT;
+ private int mEvdoDbm = UNKNOWN_INT;
+ private int mEvdoEcio = UNKNOWN_INT;
+ private int mEvdoSnr = UNKNOWN_INT;
+ private int mLteSignalStrength = UNKNOWN_INT;
+ private int mLteRsrp = UNKNOWN_INT;
+ private int mLteRsrq = UNKNOWN_INT;
+ private int mLteRssnr = UNKNOWN_INT;
+ private int mLteCqi = UNKNOWN_INT;
+
+ /**
+ * Implement the Parcelable interface.
+ * @hide
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags, OBJECT_TYPE_MOBILE_LINK_QUALITY_INFO);
+
+ dest.writeInt(mMobileNetworkType);
+ dest.writeInt(mRssi);
+ dest.writeInt(mGsmErrorRate);
+ dest.writeInt(mCdmaDbm);
+ dest.writeInt(mCdmaEcio);
+ dest.writeInt(mEvdoDbm);
+ dest.writeInt(mEvdoEcio);
+ dest.writeInt(mEvdoSnr);
+ dest.writeInt(mLteSignalStrength);
+ dest.writeInt(mLteRsrp);
+ dest.writeInt(mLteRsrq);
+ dest.writeInt(mLteRssnr);
+ dest.writeInt(mLteCqi);
+ }
+
+ /* Un-parceling helper */
+ /**
+ * @hide
+ */
+ public static MobileLinkQualityInfo createFromParcelBody(Parcel in) {
+
+ MobileLinkQualityInfo li = new MobileLinkQualityInfo();
+
+ li.initializeFromParcel(in);
+
+ li.mMobileNetworkType = in.readInt();
+ li.mRssi = in.readInt();
+ li.mGsmErrorRate = in.readInt();
+ li.mCdmaDbm = in.readInt();
+ li.mCdmaEcio = in.readInt();
+ li.mEvdoDbm = in.readInt();
+ li.mEvdoEcio = in.readInt();
+ li.mEvdoSnr = in.readInt();
+ li.mLteSignalStrength = in.readInt();
+ li.mLteRsrp = in.readInt();
+ li.mLteRsrq = in.readInt();
+ li.mLteRssnr = in.readInt();
+ li.mLteCqi = in.readInt();
+
+ return li;
+ }
+
+ /**
+ * returns mobile network type as defined by {@link android.telephony.TelephonyManager}
+ * @return network type or {@link android.net.LinkQualityInfo#UNKNOWN_INT}
+ */
+ @UnsupportedAppUsage
+ public int getMobileNetworkType() {
+ return mMobileNetworkType;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setMobileNetworkType(int mobileNetworkType) {
+ mMobileNetworkType = mobileNetworkType;
+ }
+
+ /**
+ * returns signal strength for GSM networks
+ * @return signal strength in db or {@link android.net.LinkQualityInfo#UNKNOWN_INT}
+ */
+ public int getRssi() {
+ return mRssi;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setRssi(int Rssi) {
+ mRssi = Rssi;
+ }
+
+ /**
+ * returns error rates for GSM networks
+ * @return error rate or {@link android.net.LinkQualityInfo#UNKNOWN_INT}
+ */
+ public int getGsmErrorRate() {
+ return mGsmErrorRate;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setGsmErrorRate(int gsmErrorRate) {
+ mGsmErrorRate = gsmErrorRate;
+ }
+
+ /**
+ * returns signal strength for CDMA networks
+ * @return signal strength in db or {@link android.net.LinkQualityInfo#UNKNOWN_INT}
+ */
+ public int getCdmaDbm() {
+ return mCdmaDbm;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setCdmaDbm(int cdmaDbm) {
+ mCdmaDbm = cdmaDbm;
+ }
+
+ /**
+ * returns signal to noise ratio for CDMA networks
+ * @return signal to noise ratio in db or {@link android.net.LinkQualityInfo#UNKNOWN_INT}
+ */
+ public int getCdmaEcio() {
+ return mCdmaEcio;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setCdmaEcio(int cdmaEcio) {
+ mCdmaEcio = cdmaEcio;
+ }
+
+ /**
+ * returns signal strength for EVDO networks
+ * @return signal strength in db or {@link android.net.LinkQualityInfo#UNKNOWN_INT}
+ */
+ public int getEvdoDbm() {
+ return mEvdoDbm;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setEvdoDbm(int evdoDbm) {
+ mEvdoDbm = evdoDbm;
+ }
+
+ /**
+ * returns signal to noise ratio for EVDO spectrum
+ * @return signal to noise ration in db or {@link android.net.LinkQualityInfo#UNKNOWN_INT}
+ */
+ public int getEvdoEcio() {
+ return mEvdoEcio;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setEvdoEcio(int evdoEcio) {
+ mEvdoEcio = evdoEcio;
+ }
+
+ /**
+ * returns end-to-end signal to noise ratio for EVDO networks
+ * @return signal to noise ration in db or {@link android.net.LinkQualityInfo#UNKNOWN_INT}
+ */
+ public int getEvdoSnr() {
+ return mEvdoSnr;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setEvdoSnr(int evdoSnr) {
+ mEvdoSnr = evdoSnr;
+ }
+
+ /**
+ * returns signal strength for LTE network
+ * @return signal strength in db or {@link android.net.LinkQualityInfo#UNKNOWN_INT}
+ */
+ public int getLteSignalStrength() {
+ return mLteSignalStrength;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setLteSignalStrength(int lteSignalStrength) {
+ mLteSignalStrength = lteSignalStrength;
+ }
+
+ /**
+ * returns RSRP (Reference Signal Received Power) for LTE network
+ * @return RSRP in db or {@link android.net.LinkQualityInfo#UNKNOWN_INT}
+ */
+ public int getLteRsrp() {
+ return mLteRsrp;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setLteRsrp(int lteRsrp) {
+ mLteRsrp = lteRsrp;
+ }
+
+ /**
+ * returns RSRQ (Reference Signal Received Quality) for LTE network
+ * @return RSRQ ??? or {@link android.net.LinkQualityInfo#UNKNOWN_INT}
+ */
+ public int getLteRsrq() {
+ return mLteRsrq;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setLteRsrq(int lteRsrq) {
+ mLteRsrq = lteRsrq;
+ }
+
+ /**
+ * returns signal to noise ratio for LTE networks
+ * @return signal to noise ration in db or {@link android.net.LinkQualityInfo#UNKNOWN_INT}
+ */
+ public int getLteRssnr() {
+ return mLteRssnr;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setLteRssnr(int lteRssnr) {
+ mLteRssnr = lteRssnr;
+ }
+
+ /**
+ * returns channel quality indicator for LTE networks
+ * @return CQI or {@link android.net.LinkQualityInfo#UNKNOWN_INT}
+ */
+ public int getLteCqi() {
+ return mLteCqi;
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setLteCqi(int lteCqi) {
+ mLteCqi = lteCqi;
+ }
+}
diff --git a/android/net/NattKeepalivePacketData.java b/android/net/NattKeepalivePacketData.java
new file mode 100644
index 0000000..a77c244
--- /dev/null
+++ b/android/net/NattKeepalivePacketData.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
+import static android.net.SocketKeepalive.ERROR_INVALID_PORT;
+
+import android.net.SocketKeepalive.InvalidPacketException;
+import android.net.util.IpUtils;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.system.OsConstants;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/** @hide */
+public final class NattKeepalivePacketData extends KeepalivePacketData implements Parcelable {
+ // This should only be constructed via static factory methods, such as
+ // nattKeepalivePacket
+ private NattKeepalivePacketData(InetAddress srcAddress, int srcPort,
+ InetAddress dstAddress, int dstPort, byte[] data) throws
+ InvalidPacketException {
+ super(srcAddress, srcPort, dstAddress, dstPort, data);
+ }
+
+ /**
+ * Factory method to create Nat-T keepalive packet structure.
+ */
+ public static NattKeepalivePacketData nattKeepalivePacket(
+ InetAddress srcAddress, int srcPort, InetAddress dstAddress, int dstPort)
+ throws InvalidPacketException {
+
+ if (!(srcAddress instanceof Inet4Address) || !(dstAddress instanceof Inet4Address)) {
+ throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
+ }
+
+ if (dstPort != NattSocketKeepalive.NATT_PORT) {
+ throw new InvalidPacketException(ERROR_INVALID_PORT);
+ }
+
+ int length = IPV4_HEADER_LENGTH + UDP_HEADER_LENGTH + 1;
+ ByteBuffer buf = ByteBuffer.allocate(length);
+ buf.order(ByteOrder.BIG_ENDIAN);
+ buf.putShort((short) 0x4500); // IP version and TOS
+ buf.putShort((short) length);
+ buf.putInt(0); // ID, flags, offset
+ buf.put((byte) 64); // TTL
+ buf.put((byte) OsConstants.IPPROTO_UDP);
+ int ipChecksumOffset = buf.position();
+ buf.putShort((short) 0); // IP checksum
+ buf.put(srcAddress.getAddress());
+ buf.put(dstAddress.getAddress());
+ buf.putShort((short) srcPort);
+ buf.putShort((short) dstPort);
+ buf.putShort((short) (length - 20)); // UDP length
+ int udpChecksumOffset = buf.position();
+ buf.putShort((short) 0); // UDP checksum
+ buf.put((byte) 0xff); // NAT-T keepalive
+ buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0));
+ buf.putShort(udpChecksumOffset, IpUtils.udpChecksum(buf, 0, IPV4_HEADER_LENGTH));
+
+ return new NattKeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array());
+ }
+
+ /** Parcelable Implementation */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Write to parcel */
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(srcAddress.getHostAddress());
+ out.writeString(dstAddress.getHostAddress());
+ out.writeInt(srcPort);
+ out.writeInt(dstPort);
+ }
+
+ /** Parcelable Creator */
+ public static final Parcelable.Creator<NattKeepalivePacketData> CREATOR =
+ new Parcelable.Creator<NattKeepalivePacketData>() {
+ public NattKeepalivePacketData createFromParcel(Parcel in) {
+ final InetAddress srcAddress =
+ InetAddresses.parseNumericAddress(in.readString());
+ final InetAddress dstAddress =
+ InetAddresses.parseNumericAddress(in.readString());
+ final int srcPort = in.readInt();
+ final int dstPort = in.readInt();
+ try {
+ return NattKeepalivePacketData.nattKeepalivePacket(srcAddress, srcPort,
+ dstAddress, dstPort);
+ } catch (InvalidPacketException e) {
+ throw new IllegalArgumentException(
+ "Invalid NAT-T keepalive data: " + e.error);
+ }
+ }
+
+ public NattKeepalivePacketData[] newArray(int size) {
+ return new NattKeepalivePacketData[size];
+ }
+ };
+}
diff --git a/android/net/NattSocketKeepalive.java b/android/net/NattSocketKeepalive.java
new file mode 100644
index 0000000..b0ce0c7
--- /dev/null
+++ b/android/net/NattSocketKeepalive.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.net.InetAddress;
+import java.util.concurrent.Executor;
+
+/** @hide */
+public final class NattSocketKeepalive extends SocketKeepalive {
+ /** The NAT-T destination port for IPsec */
+ public static final int NATT_PORT = 4500;
+
+ @NonNull private final InetAddress mSource;
+ @NonNull private final InetAddress mDestination;
+ private final int mResourceId;
+
+ NattSocketKeepalive(@NonNull IConnectivityManager service,
+ @NonNull Network network,
+ @NonNull ParcelFileDescriptor pfd,
+ int resourceId,
+ @NonNull InetAddress source,
+ @NonNull InetAddress destination,
+ @NonNull Executor executor,
+ @NonNull Callback callback) {
+ super(service, network, pfd, executor, callback);
+ mSource = source;
+ mDestination = destination;
+ mResourceId = resourceId;
+ }
+
+ @Override
+ void startImpl(int intervalSec) {
+ mExecutor.execute(() -> {
+ try {
+ mService.startNattKeepaliveWithFd(mNetwork, mPfd.getFileDescriptor(), mResourceId,
+ intervalSec, mCallback,
+ mSource.getHostAddress(), mDestination.getHostAddress());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error starting socket keepalive: ", e);
+ throw e.rethrowFromSystemServer();
+ }
+ });
+ }
+
+ @Override
+ void stopImpl() {
+ mExecutor.execute(() -> {
+ try {
+ if (mSlot != null) {
+ mService.stopKeepalive(mNetwork, mSlot);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error stopping socket keepalive: ", e);
+ throw e.rethrowFromSystemServer();
+ }
+ });
+ }
+}
diff --git a/android/net/Network.java b/android/net/Network.java
new file mode 100644
index 0000000..3f56def
--- /dev/null
+++ b/android/net/Network.java
@@ -0,0 +1,510 @@
+/*
+ * Copyright (C) 2014 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;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.okhttp.internalandroidapi.Dns;
+import com.android.okhttp.internalandroidapi.HttpURLConnectionFactory;
+
+import libcore.io.IoUtils;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+import javax.net.SocketFactory;
+
+/**
+ * Identifies a {@code Network}. This is supplied to applications via
+ * {@link ConnectivityManager.NetworkCallback} in response to the active
+ * {@link ConnectivityManager#requestNetwork} or passive
+ * {@link ConnectivityManager#registerNetworkCallback} calls.
+ * It is used to direct traffic to the given {@code Network}, either on a {@link Socket} basis
+ * through a targeted {@link SocketFactory} or process-wide via
+ * {@link ConnectivityManager#bindProcessToNetwork}.
+ */
+public class Network implements Parcelable {
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public final int netId;
+
+ // Objects used to perform per-network operations such as getSocketFactory
+ // and openConnection, and a lock to protect access to them.
+ private volatile NetworkBoundSocketFactory mNetworkBoundSocketFactory = null;
+ // mLock should be used to control write access to mUrlConnectionFactory.
+ // maybeInitUrlConnectionFactory() must be called prior to reading this field.
+ private volatile HttpURLConnectionFactory mUrlConnectionFactory;
+ private final Object mLock = new Object();
+
+ // Default connection pool values. These are evaluated at startup, just
+ // like the OkHttp code. Also like the OkHttp code, we will throw parse
+ // exceptions at class loading time if the properties are set but are not
+ // valid integers.
+ private static final boolean httpKeepAlive =
+ Boolean.parseBoolean(System.getProperty("http.keepAlive", "true"));
+ private static final int httpMaxConnections =
+ httpKeepAlive ? Integer.parseInt(System.getProperty("http.maxConnections", "5")) : 0;
+ private static final long httpKeepAliveDurationMs =
+ Long.parseLong(System.getProperty("http.keepAliveDuration", "300000")); // 5 minutes.
+ // Value used to obfuscate network handle longs.
+ // The HANDLE_MAGIC value MUST be kept in sync with the corresponding
+ // value in the native/android/net.c NDK implementation.
+ private static final long HANDLE_MAGIC = 0xcafed00dL;
+ private static final int HANDLE_MAGIC_SIZE = 32;
+
+ // A boolean to control how getAllByName()/getByName() behaves in the face
+ // of Private DNS.
+ //
+ // When true, these calls will request that DNS resolution bypass any
+ // Private DNS that might otherwise apply. Use of this feature is restricted
+ // and permission checks are made by netd (attempts to bypass Private DNS
+ // without appropriate permission are silently turned into vanilla DNS
+ // requests). This only affects DNS queries made using this network object.
+ //
+ // It it not parceled to receivers because (a) it can be set or cleared at
+ // anytime and (b) receivers should be explicit about attempts to bypass
+ // Private DNS so that the intent of the code is easily determined and
+ // code search audits are possible.
+ private final transient boolean mPrivateDnsBypass;
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public Network(int netId) {
+ this(netId, false);
+ }
+
+ /**
+ * @hide
+ */
+ public Network(int netId, boolean privateDnsBypass) {
+ this.netId = netId;
+ this.mPrivateDnsBypass = privateDnsBypass;
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public Network(@NonNull Network that) {
+ this(that.netId, that.mPrivateDnsBypass);
+ }
+
+ /**
+ * Operates the same as {@code InetAddress.getAllByName} except that host
+ * resolution is done on this network.
+ *
+ * @param host the hostname or literal IP string to be resolved.
+ * @return the array of addresses associated with the specified host.
+ * @throws UnknownHostException if the address lookup fails.
+ */
+ public InetAddress[] getAllByName(String host) throws UnknownHostException {
+ return InetAddress.getAllByNameOnNet(host, getNetIdForResolv());
+ }
+
+ /**
+ * Operates the same as {@code InetAddress.getByName} except that host
+ * resolution is done on this network.
+ *
+ * @param host the hostname to be resolved to an address or {@code null}.
+ * @return the {@code InetAddress} instance representing the host.
+ * @throws UnknownHostException
+ * if the address lookup fails.
+ */
+ public InetAddress getByName(String host) throws UnknownHostException {
+ return InetAddress.getByNameOnNet(host, getNetIdForResolv());
+ }
+
+ /**
+ * Obtain a Network object for which Private DNS is to be bypassed when attempting
+ * to use {@link #getAllByName(String)}/{@link #getByName(String)} methods on the given
+ * instance for hostname resolution.
+ *
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public @NonNull Network getPrivateDnsBypassingCopy() {
+ return new Network(netId, true);
+ }
+
+ /**
+ * Returns a netid marked with the Private DNS bypass flag.
+ *
+ * This flag must be kept in sync with the NETID_USE_LOCAL_NAMESERVERS flag
+ * in system/netd/include/NetdClient.h.
+ *
+ * @hide
+ */
+ public int getNetIdForResolv() {
+ return mPrivateDnsBypass
+ ? (int) (0x80000000L | (long) netId) // Non-portable DNS resolution flag.
+ : netId;
+ }
+
+ /**
+ * A {@code SocketFactory} that produces {@code Socket}'s bound to this network.
+ */
+ private class NetworkBoundSocketFactory extends SocketFactory {
+ private Socket connectToHost(String host, int port, SocketAddress localAddress)
+ throws IOException {
+ // Lookup addresses only on this Network.
+ InetAddress[] hostAddresses = getAllByName(host);
+ // Try all addresses.
+ for (int i = 0; i < hostAddresses.length; i++) {
+ try {
+ Socket socket = createSocket();
+ boolean failed = true;
+ try {
+ if (localAddress != null) socket.bind(localAddress);
+ socket.connect(new InetSocketAddress(hostAddresses[i], port));
+ failed = false;
+ return socket;
+ } finally {
+ if (failed) IoUtils.closeQuietly(socket);
+ }
+ } catch (IOException e) {
+ if (i == (hostAddresses.length - 1)) throw e;
+ }
+ }
+ throw new UnknownHostException(host);
+ }
+
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
+ throws IOException {
+ return connectToHost(host, port, new InetSocketAddress(localHost, localPort));
+ }
+
+ @Override
+ public Socket createSocket(InetAddress address, int port, InetAddress localAddress,
+ int localPort) throws IOException {
+ Socket socket = createSocket();
+ boolean failed = true;
+ try {
+ socket.bind(new InetSocketAddress(localAddress, localPort));
+ socket.connect(new InetSocketAddress(address, port));
+ failed = false;
+ } finally {
+ if (failed) IoUtils.closeQuietly(socket);
+ }
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket(InetAddress host, int port) throws IOException {
+ Socket socket = createSocket();
+ boolean failed = true;
+ try {
+ socket.connect(new InetSocketAddress(host, port));
+ failed = false;
+ } finally {
+ if (failed) IoUtils.closeQuietly(socket);
+ }
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException {
+ return connectToHost(host, port, null);
+ }
+
+ @Override
+ public Socket createSocket() throws IOException {
+ Socket socket = new Socket();
+ boolean failed = true;
+ try {
+ bindSocket(socket);
+ failed = false;
+ } finally {
+ if (failed) IoUtils.closeQuietly(socket);
+ }
+ return socket;
+ }
+ }
+
+ /**
+ * Returns a {@link SocketFactory} bound to this network. Any {@link Socket} created by
+ * this factory will have its traffic sent over this {@code Network}. Note that if this
+ * {@code Network} ever disconnects, this factory and any {@link Socket} it produced in the
+ * past or future will cease to work.
+ *
+ * @return a {@link SocketFactory} which produces {@link Socket} instances bound to this
+ * {@code Network}.
+ */
+ public SocketFactory getSocketFactory() {
+ if (mNetworkBoundSocketFactory == null) {
+ synchronized (mLock) {
+ if (mNetworkBoundSocketFactory == null) {
+ mNetworkBoundSocketFactory = new NetworkBoundSocketFactory();
+ }
+ }
+ }
+ return mNetworkBoundSocketFactory;
+ }
+
+ // TODO: This creates a connection pool and host resolver for
+ // every Network object, instead of one for every NetId. This is
+ // suboptimal, because an app could potentially have more than one
+ // Network object for the same NetId, causing increased memory footprint
+ // and performance penalties due to lack of connection reuse (connection
+ // setup time, congestion window growth time, etc.).
+ //
+ // Instead, investigate only having one connection pool and host resolver
+ // for every NetId, perhaps by using a static HashMap of NetIds to
+ // connection pools and host resolvers. The tricky part is deciding when
+ // to remove a map entry; a WeakHashMap shouldn't be used because whether
+ // a Network is referenced doesn't correlate with whether a new Network
+ // will be instantiated in the near future with the same NetID. A good
+ // solution would involve purging empty (or when all connections are timed
+ // out) ConnectionPools.
+ private void maybeInitUrlConnectionFactory() {
+ synchronized (mLock) {
+ if (mUrlConnectionFactory == null) {
+ // Set configuration on the HttpURLConnectionFactory that will be good for all
+ // connections created by this Network. Configuration that might vary is left
+ // until openConnection() and passed as arguments.
+ Dns dnsLookup = hostname -> Arrays.asList(Network.this.getAllByName(hostname));
+ HttpURLConnectionFactory urlConnectionFactory = new HttpURLConnectionFactory();
+ urlConnectionFactory.setDns(dnsLookup); // Let traffic go via dnsLookup
+ // A private connection pool just for this Network.
+ urlConnectionFactory.setNewConnectionPool(httpMaxConnections,
+ httpKeepAliveDurationMs, TimeUnit.MILLISECONDS);
+ mUrlConnectionFactory = urlConnectionFactory;
+ }
+ }
+ }
+
+ /**
+ * Opens the specified {@link URL} on this {@code Network}, such that all traffic will be sent
+ * on this Network. The URL protocol must be {@code HTTP} or {@code HTTPS}.
+ *
+ * @return a {@code URLConnection} to the resource referred to by this URL.
+ * @throws MalformedURLException if the URL protocol is not HTTP or HTTPS.
+ * @throws IOException if an error occurs while opening the connection.
+ * @see java.net.URL#openConnection()
+ */
+ public URLConnection openConnection(URL url) throws IOException {
+ final ConnectivityManager cm = ConnectivityManager.getInstanceOrNull();
+ if (cm == null) {
+ throw new IOException("No ConnectivityManager yet constructed, please construct one");
+ }
+ // TODO: Should this be optimized to avoid fetching the global proxy for every request?
+ final ProxyInfo proxyInfo = cm.getProxyForNetwork(this);
+ final java.net.Proxy proxy;
+ if (proxyInfo != null) {
+ proxy = proxyInfo.makeProxy();
+ } else {
+ proxy = java.net.Proxy.NO_PROXY;
+ }
+ return openConnection(url, proxy);
+ }
+
+ /**
+ * Opens the specified {@link URL} on this {@code Network}, such that all traffic will be sent
+ * on this Network. The URL protocol must be {@code HTTP} or {@code HTTPS}.
+ *
+ * @param proxy the proxy through which the connection will be established.
+ * @return a {@code URLConnection} to the resource referred to by this URL.
+ * @throws MalformedURLException if the URL protocol is not HTTP or HTTPS.
+ * @throws IllegalArgumentException if the argument proxy is null.
+ * @throws IOException if an error occurs while opening the connection.
+ * @see java.net.URL#openConnection()
+ */
+ public URLConnection openConnection(URL url, java.net.Proxy proxy) throws IOException {
+ if (proxy == null) throw new IllegalArgumentException("proxy is null");
+ maybeInitUrlConnectionFactory();
+ SocketFactory socketFactory = getSocketFactory();
+ return mUrlConnectionFactory.openConnection(url, socketFactory, proxy);
+ }
+
+ /**
+ * Binds the specified {@link DatagramSocket} to this {@code Network}. All data traffic on the
+ * socket will be sent on this {@code Network}, irrespective of any process-wide network binding
+ * set by {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be
+ * connected.
+ */
+ public void bindSocket(DatagramSocket socket) throws IOException {
+ // Query a property of the underlying socket to ensure that the socket's file descriptor
+ // exists, is available to bind to a network and is not closed.
+ socket.getReuseAddress();
+ bindSocket(socket.getFileDescriptor$());
+ }
+
+ /**
+ * Binds the specified {@link Socket} to this {@code Network}. All data traffic on the socket
+ * will be sent on this {@code Network}, irrespective of any process-wide network binding set by
+ * {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be connected.
+ */
+ public void bindSocket(Socket socket) throws IOException {
+ // Query a property of the underlying socket to ensure that the socket's file descriptor
+ // exists, is available to bind to a network and is not closed.
+ socket.getReuseAddress();
+ bindSocket(socket.getFileDescriptor$());
+ }
+
+ /**
+ * Binds the specified {@link FileDescriptor} to this {@code Network}. All data traffic on the
+ * socket represented by this file descriptor will be sent on this {@code Network},
+ * irrespective of any process-wide network binding set by
+ * {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be connected.
+ */
+ public void bindSocket(FileDescriptor fd) throws IOException {
+ try {
+ final SocketAddress peer = Os.getpeername(fd);
+ final InetAddress inetPeer = ((InetSocketAddress) peer).getAddress();
+ if (!inetPeer.isAnyLocalAddress()) {
+ // Apparently, the kernel doesn't update a connected UDP socket's
+ // routing upon mark changes.
+ throw new SocketException("Socket is connected");
+ }
+ } catch (ErrnoException e) {
+ // getpeername() failed.
+ if (e.errno != OsConstants.ENOTCONN) {
+ throw e.rethrowAsSocketException();
+ }
+ } catch (ClassCastException e) {
+ // Wasn't an InetSocketAddress.
+ throw new SocketException("Only AF_INET/AF_INET6 sockets supported");
+ }
+
+ final int err = NetworkUtils.bindSocketToNetwork(fd.getInt$(), netId);
+ if (err != 0) {
+ // bindSocketToNetwork returns negative errno.
+ throw new ErrnoException("Binding socket to network " + netId, -err)
+ .rethrowAsSocketException();
+ }
+ }
+
+ /**
+ * Returns a {@link Network} object given a handle returned from {@link #getNetworkHandle}.
+ *
+ * @param networkHandle a handle returned from {@link #getNetworkHandle}.
+ * @return A {@link Network} object derived from {@code networkHandle}.
+ */
+ public static Network fromNetworkHandle(long networkHandle) {
+ if (networkHandle == 0) {
+ throw new IllegalArgumentException(
+ "Network.fromNetworkHandle refusing to instantiate NETID_UNSET Network.");
+ }
+ if ((networkHandle & ((1L << HANDLE_MAGIC_SIZE) - 1)) != HANDLE_MAGIC
+ || networkHandle < 0) {
+ throw new IllegalArgumentException(
+ "Value passed to fromNetworkHandle() is not a network handle.");
+ }
+ return new Network((int) (networkHandle >> HANDLE_MAGIC_SIZE));
+ }
+
+ /**
+ * Returns a handle representing this {@code Network}, for use with the NDK API.
+ */
+ public long getNetworkHandle() {
+ // The network handle is explicitly not the same as the netId.
+ //
+ // The netId is an implementation detail which might be changed in the
+ // future, or which alone (i.e. in the absence of some additional
+ // context) might not be sufficient to fully identify a Network.
+ //
+ // As such, the intention is to prevent accidental misuse of the API
+ // that might result if a developer assumed that handles and netIds
+ // were identical and passing a netId to a call expecting a handle
+ // "just worked". Such accidental misuse, if widely deployed, might
+ // prevent future changes to the semantics of the netId field or
+ // inhibit the expansion of state required for Network objects.
+ //
+ // This extra layer of indirection might be seen as paranoia, and might
+ // never end up being necessary, but the added complexity is trivial.
+ // At some future date it may be desirable to realign the handle with
+ // Multiple Provisioning Domains API recommendations, as made by the
+ // IETF mif working group.
+ if (netId == 0) {
+ return 0L; // make this zero condition obvious for debugging
+ }
+ return (((long) netId) << HANDLE_MAGIC_SIZE) | HANDLE_MAGIC;
+ }
+
+ // implement the Parcelable interface
+ public int describeContents() {
+ return 0;
+ }
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(netId);
+ }
+
+ public static final @android.annotation.NonNull Creator<Network> CREATOR =
+ new Creator<Network>() {
+ public Network createFromParcel(Parcel in) {
+ int netId = in.readInt();
+
+ return new Network(netId);
+ }
+
+ public Network[] newArray(int size) {
+ return new Network[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Network)) return false;
+ Network other = (Network)obj;
+ return this.netId == other.netId;
+ }
+
+ @Override
+ public int hashCode() {
+ return netId * 11;
+ }
+
+ @Override
+ public String toString() {
+ return Integer.toString(netId);
+ }
+
+ /** @hide */
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(NetworkProto.NET_ID, netId);
+ proto.end(token);
+ }
+}
diff --git a/android/net/NetworkAgent.java b/android/net/NetworkAgent.java
new file mode 100644
index 0000000..9cd4f53
--- /dev/null
+++ b/android/net/NetworkAgent.java
@@ -0,0 +1,560 @@
+/*
+ * Copyright (C) 2014 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;
+
+import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.util.Log;
+
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
+
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A Utility class for handling for communicating between bearer-specific
+ * code and ConnectivityService.
+ *
+ * A bearer may have more than one NetworkAgent if it can simultaneously
+ * support separate networks (IMS / Internet / MMS Apns on cellular, or
+ * perhaps connections with different SSID or P2P for Wi-Fi).
+ *
+ * @hide
+ */
+public abstract class NetworkAgent extends Handler {
+ // Guaranteed to be valid (not NETID_UNSET), otherwise registerNetworkAgent() would have thrown
+ // an exception.
+ public final int netId;
+
+ private volatile AsyncChannel mAsyncChannel;
+ private final String LOG_TAG;
+ private static final boolean DBG = true;
+ private static final boolean VDBG = false;
+ private final Context mContext;
+ private final ArrayList<Message>mPreConnectedQueue = new ArrayList<Message>();
+ private volatile long mLastBwRefreshTime = 0;
+ private static final long BW_REFRESH_MIN_WIN_MS = 500;
+ private boolean mPollLceScheduled = false;
+ private AtomicBoolean mPollLcePending = new AtomicBoolean(false);
+ public final int mFactorySerialNumber;
+
+ private static final int BASE = Protocol.BASE_NETWORK_AGENT;
+
+ /**
+ * Sent by ConnectivityService to the NetworkAgent to inform it of
+ * suspected connectivity problems on its network. The NetworkAgent
+ * should take steps to verify and correct connectivity.
+ */
+ public static final int CMD_SUSPECT_BAD = BASE;
+
+ /**
+ * Sent by the NetworkAgent (note the EVENT vs CMD prefix) to
+ * ConnectivityService to pass the current NetworkInfo (connection state).
+ * Sent when the NetworkInfo changes, mainly due to change of state.
+ * obj = NetworkInfo
+ */
+ public static final int EVENT_NETWORK_INFO_CHANGED = BASE + 1;
+
+ /**
+ * Sent by the NetworkAgent to ConnectivityService to pass the current
+ * NetworkCapabilties.
+ * obj = NetworkCapabilities
+ */
+ public static final int EVENT_NETWORK_CAPABILITIES_CHANGED = BASE + 2;
+
+ /**
+ * Sent by the NetworkAgent to ConnectivityService to pass the current
+ * NetworkProperties.
+ * obj = NetworkProperties
+ */
+ public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 3;
+
+ /* centralize place where base network score, and network score scaling, will be
+ * stored, so as we can consistently compare apple and oranges, or wifi, ethernet and LTE
+ */
+ public static final int WIFI_BASE_SCORE = 60;
+
+ /**
+ * Sent by the NetworkAgent to ConnectivityService to pass the current
+ * network score.
+ * obj = network score Integer
+ */
+ public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4;
+
+ /**
+ * Sent by ConnectivityService to the NetworkAgent to inform the agent of the
+ * networks status - whether we could use the network or could not, due to
+ * either a bad network configuration (no internet link) or captive portal.
+ *
+ * arg1 = either {@code VALID_NETWORK} or {@code INVALID_NETWORK}
+ * obj = Bundle containing map from {@code REDIRECT_URL_KEY} to {@code String}
+ * representing URL that Internet probe was redirect to, if it was redirected,
+ * or mapping to {@code null} otherwise.
+ */
+ public static final int CMD_REPORT_NETWORK_STATUS = BASE + 7;
+
+ public static final int VALID_NETWORK = 1;
+ public static final int INVALID_NETWORK = 2;
+
+ public static String REDIRECT_URL_KEY = "redirect URL";
+
+ /**
+ * Sent by the NetworkAgent to ConnectivityService to indicate this network was
+ * explicitly selected. This should be sent before the NetworkInfo is marked
+ * CONNECTED so it can be given special treatment at that time.
+ *
+ * obj = boolean indicating whether to use this network even if unvalidated
+ */
+ public static final int EVENT_SET_EXPLICITLY_SELECTED = BASE + 8;
+
+ /**
+ * Sent by ConnectivityService to the NetworkAgent to inform the agent of
+ * whether the network should in the future be used even if not validated.
+ * This decision is made by the user, but it is the network transport's
+ * responsibility to remember it.
+ *
+ * arg1 = 1 if true, 0 if false
+ */
+ public static final int CMD_SAVE_ACCEPT_UNVALIDATED = BASE + 9;
+
+ /**
+ * Sent by ConnectivityService to the NetworkAgent to inform the agent to pull
+ * the underlying network connection for updated bandwidth information.
+ */
+ public static final int CMD_REQUEST_BANDWIDTH_UPDATE = BASE + 10;
+
+ /**
+ * Sent by ConnectivityService to the NetworkAgent to request that the specified packet be sent
+ * periodically on the given interval.
+ *
+ * arg1 = the slot number of the keepalive to start
+ * arg2 = interval in seconds
+ * obj = KeepalivePacketData object describing the data to be sent
+ *
+ * Also used internally by ConnectivityService / KeepaliveTracker, with different semantics.
+ */
+ public static final int CMD_START_SOCKET_KEEPALIVE = BASE + 11;
+
+ /**
+ * Requests that the specified keepalive packet be stopped.
+ *
+ * arg1 = slot number of the keepalive to stop.
+ *
+ * Also used internally by ConnectivityService / KeepaliveTracker, with different semantics.
+ */
+ public static final int CMD_STOP_SOCKET_KEEPALIVE = BASE + 12;
+
+ /**
+ * Sent by the NetworkAgent to ConnectivityService to provide status on a socket keepalive
+ * request. This may either be the reply to a CMD_START_SOCKET_KEEPALIVE, or an asynchronous
+ * error notification.
+ *
+ * This is also sent by KeepaliveTracker to the app's {@link SocketKeepalive},
+ * so that the app's {@link SocketKeepalive.Callback} methods can be called.
+ *
+ * arg1 = slot number of the keepalive
+ * arg2 = error code
+ */
+ public static final int EVENT_SOCKET_KEEPALIVE = BASE + 13;
+
+ // TODO: move the above 2 constants down so they are in order once merge conflicts are resolved
+ /**
+ * Sent by the KeepaliveTracker to NetworkAgent to add a packet filter.
+ *
+ * For TCP keepalive offloads, keepalive packets are sent by the firmware. However, because the
+ * remote site will send ACK packets in response to the keepalive packets, the firmware also
+ * needs to be configured to properly filter the ACKs to prevent the system from waking up.
+ * This does not happen with UDP, so this message is TCP-specific.
+ * arg1 = slot number of the keepalive to filter for.
+ * obj = the keepalive packet to send repeatedly.
+ */
+ public static final int CMD_ADD_KEEPALIVE_PACKET_FILTER = BASE + 16;
+
+ /**
+ * Sent by the KeepaliveTracker to NetworkAgent to remove a packet filter. See
+ * {@link #CMD_ADD_KEEPALIVE_PACKET_FILTER}.
+ * arg1 = slot number of the keepalive packet filter to remove.
+ */
+ public static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER = BASE + 17;
+
+ /**
+ * Sent by ConnectivityService to inform this network transport of signal strength thresholds
+ * that when crossed should trigger a system wakeup and a NetworkCapabilities update.
+ *
+ * obj = int[] describing signal strength thresholds.
+ */
+ public static final int CMD_SET_SIGNAL_STRENGTH_THRESHOLDS = BASE + 14;
+
+ /**
+ * Sent by ConnectivityService to the NeworkAgent to inform the agent to avoid
+ * automatically reconnecting to this network (e.g. via autojoin). Happens
+ * when user selects "No" option on the "Stay connected?" dialog box.
+ */
+ public static final int CMD_PREVENT_AUTOMATIC_RECONNECT = BASE + 15;
+
+ // TODO : remove these two constructors. They are a stopgap measure to help sheperding a number
+ // of dependent changes that would conflict throughout the automerger graph. Having these
+ // temporarily helps with the process of going through with all these dependent changes across
+ // the entire tree.
+ public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
+ NetworkCapabilities nc, LinkProperties lp, int score) {
+ this(looper, context, logTag, ni, nc, lp, score, null, NetworkFactory.SerialNumber.NONE);
+ }
+ public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
+ NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc) {
+ this(looper, context, logTag, ni, nc, lp, score, misc, NetworkFactory.SerialNumber.NONE);
+ }
+
+ public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
+ NetworkCapabilities nc, LinkProperties lp, int score, int factorySerialNumber) {
+ this(looper, context, logTag, ni, nc, lp, score, null, factorySerialNumber);
+ }
+
+ public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
+ NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc,
+ int factorySerialNumber) {
+ super(looper);
+ LOG_TAG = logTag;
+ mContext = context;
+ mFactorySerialNumber = factorySerialNumber;
+ if (ni == null || nc == null || lp == null) {
+ throw new IllegalArgumentException();
+ }
+
+ if (VDBG) log("Registering NetworkAgent");
+ ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ netId = cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni),
+ new LinkProperties(lp), new NetworkCapabilities(nc), score, misc,
+ factorySerialNumber);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
+ if (mAsyncChannel != null) {
+ log("Received new connection while already connected!");
+ } else {
+ if (VDBG) log("NetworkAgent fully connected");
+ AsyncChannel ac = new AsyncChannel();
+ ac.connected(null, this, msg.replyTo);
+ ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+ AsyncChannel.STATUS_SUCCESSFUL);
+ synchronized (mPreConnectedQueue) {
+ mAsyncChannel = ac;
+ for (Message m : mPreConnectedQueue) {
+ ac.sendMessage(m);
+ }
+ mPreConnectedQueue.clear();
+ }
+ }
+ break;
+ }
+ case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
+ if (VDBG) log("CMD_CHANNEL_DISCONNECT");
+ if (mAsyncChannel != null) mAsyncChannel.disconnect();
+ break;
+ }
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
+ if (DBG) log("NetworkAgent channel lost");
+ // let the client know CS is done with us.
+ unwanted();
+ synchronized (mPreConnectedQueue) {
+ mAsyncChannel = null;
+ }
+ break;
+ }
+ case CMD_SUSPECT_BAD: {
+ log("Unhandled Message " + msg);
+ break;
+ }
+ case CMD_REQUEST_BANDWIDTH_UPDATE: {
+ long currentTimeMs = System.currentTimeMillis();
+ if (VDBG) {
+ log("CMD_REQUEST_BANDWIDTH_UPDATE request received.");
+ }
+ if (currentTimeMs >= (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) {
+ mPollLceScheduled = false;
+ if (mPollLcePending.getAndSet(true) == false) {
+ pollLceData();
+ }
+ } else {
+ // deliver the request at a later time rather than discard it completely.
+ if (!mPollLceScheduled) {
+ long waitTime = mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS -
+ currentTimeMs + 1;
+ mPollLceScheduled = sendEmptyMessageDelayed(
+ CMD_REQUEST_BANDWIDTH_UPDATE, waitTime);
+ }
+ }
+ break;
+ }
+ case CMD_REPORT_NETWORK_STATUS: {
+ String redirectUrl = ((Bundle)msg.obj).getString(REDIRECT_URL_KEY);
+ if (VDBG) {
+ log("CMD_REPORT_NETWORK_STATUS(" +
+ (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ") + redirectUrl);
+ }
+ networkStatus(msg.arg1, redirectUrl);
+ break;
+ }
+ case CMD_SAVE_ACCEPT_UNVALIDATED: {
+ saveAcceptUnvalidated(msg.arg1 != 0);
+ break;
+ }
+ case CMD_START_SOCKET_KEEPALIVE: {
+ startSocketKeepalive(msg);
+ break;
+ }
+ case CMD_STOP_SOCKET_KEEPALIVE: {
+ stopSocketKeepalive(msg);
+ break;
+ }
+
+ case CMD_SET_SIGNAL_STRENGTH_THRESHOLDS: {
+ ArrayList<Integer> thresholds =
+ ((Bundle) msg.obj).getIntegerArrayList("thresholds");
+ // TODO: Change signal strength thresholds API to use an ArrayList<Integer>
+ // rather than convert to int[].
+ int[] intThresholds = new int[(thresholds != null) ? thresholds.size() : 0];
+ for (int i = 0; i < intThresholds.length; i++) {
+ intThresholds[i] = thresholds.get(i);
+ }
+ setSignalStrengthThresholds(intThresholds);
+ break;
+ }
+ case CMD_PREVENT_AUTOMATIC_RECONNECT: {
+ preventAutomaticReconnect();
+ break;
+ }
+ case CMD_ADD_KEEPALIVE_PACKET_FILTER: {
+ addKeepalivePacketFilter(msg);
+ break;
+ }
+ case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: {
+ removeKeepalivePacketFilter(msg);
+ break;
+ }
+ }
+ }
+
+ private void queueOrSendMessage(int what, Object obj) {
+ queueOrSendMessage(what, 0, 0, obj);
+ }
+
+ private void queueOrSendMessage(int what, int arg1, int arg2) {
+ queueOrSendMessage(what, arg1, arg2, null);
+ }
+
+ private void queueOrSendMessage(int what, int arg1, int arg2, Object obj) {
+ Message msg = Message.obtain();
+ msg.what = what;
+ msg.arg1 = arg1;
+ msg.arg2 = arg2;
+ msg.obj = obj;
+ queueOrSendMessage(msg);
+ }
+
+ private void queueOrSendMessage(Message msg) {
+ synchronized (mPreConnectedQueue) {
+ if (mAsyncChannel != null) {
+ mAsyncChannel.sendMessage(msg);
+ } else {
+ mPreConnectedQueue.add(msg);
+ }
+ }
+ }
+
+ /**
+ * Called by the bearer code when it has new LinkProperties data.
+ */
+ public void sendLinkProperties(LinkProperties linkProperties) {
+ queueOrSendMessage(EVENT_NETWORK_PROPERTIES_CHANGED, new LinkProperties(linkProperties));
+ }
+
+ /**
+ * Called by the bearer code when it has new NetworkInfo data.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public void sendNetworkInfo(NetworkInfo networkInfo) {
+ queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, new NetworkInfo(networkInfo));
+ }
+
+ /**
+ * Called by the bearer code when it has new NetworkCapabilities data.
+ */
+ public void sendNetworkCapabilities(NetworkCapabilities networkCapabilities) {
+ mPollLcePending.set(false);
+ mLastBwRefreshTime = System.currentTimeMillis();
+ queueOrSendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED,
+ new NetworkCapabilities(networkCapabilities));
+ }
+
+ /**
+ * Called by the bearer code when it has a new score for this network.
+ */
+ public void sendNetworkScore(int score) {
+ if (score < 0) {
+ throw new IllegalArgumentException("Score must be >= 0");
+ }
+ queueOrSendMessage(EVENT_NETWORK_SCORE_CHANGED, score, 0);
+ }
+
+ /**
+ * Called by the bearer to indicate this network was manually selected by the user.
+ * This should be called before the NetworkInfo is marked CONNECTED so that this
+ * Network can be given special treatment at that time. If {@code acceptUnvalidated} is
+ * {@code true}, then the system will switch to this network. If it is {@code false} and the
+ * network cannot be validated, the system will ask the user whether to switch to this network.
+ * If the user confirms and selects "don't ask again", then the system will call
+ * {@link #saveAcceptUnvalidated} to persist the user's choice. Thus, if the transport ever
+ * calls this method with {@code acceptUnvalidated} set to {@code false}, it must also implement
+ * {@link #saveAcceptUnvalidated} to respect the user's choice.
+ */
+ public void explicitlySelected(boolean acceptUnvalidated) {
+ explicitlySelected(true /* explicitlySelected */, acceptUnvalidated);
+ }
+
+ /**
+ * Called by the bearer to indicate this network was manually selected by the user.
+ * This should be called before the NetworkInfo is marked CONNECTED so that this
+ * Network can be given special treatment at that time. If {@code acceptUnvalidated} is
+ * {@code true}, then the system will switch to this network. If it is {@code false} and the
+ * network cannot be validated, the system will ask the user whether to switch to this network.
+ * If the user confirms and selects "don't ask again", then the system will call
+ * {@link #saveAcceptUnvalidated} to persist the user's choice. Thus, if the transport ever
+ * calls this method with {@code acceptUnvalidated} set to {@code false}, it must also implement
+ * {@link #saveAcceptUnvalidated} to respect the user's choice.
+ */
+ public void explicitlySelected(boolean explicitlySelected, boolean acceptUnvalidated) {
+ queueOrSendMessage(EVENT_SET_EXPLICITLY_SELECTED,
+ explicitlySelected ? 1 : 0,
+ acceptUnvalidated ? 1 : 0);
+ }
+
+ /**
+ * Called when ConnectivityService has indicated they no longer want this network.
+ * The parent factory should (previously) have received indication of the change
+ * as well, either canceling NetworkRequests or altering their score such that this
+ * network won't be immediately requested again.
+ */
+ abstract protected void unwanted();
+
+ /**
+ * Called when ConnectivityService request a bandwidth update. The parent factory
+ * shall try to overwrite this method and produce a bandwidth update if capable.
+ */
+ protected void pollLceData() {
+ }
+
+ /**
+ * Called when the system determines the usefulness of this network.
+ *
+ * Networks claiming internet connectivity will have their internet
+ * connectivity verified.
+ *
+ * Currently there are two possible values:
+ * {@code VALID_NETWORK} if the system is happy with the connection,
+ * {@code INVALID_NETWORK} if the system is not happy.
+ * TODO - add indications of captive portal-ness and related success/failure,
+ * ie, CAPTIVE_SUCCESS_NETWORK, CAPTIVE_NETWORK for successful login and detection
+ *
+ * This may be called multiple times as the network status changes and may
+ * generate false negatives if we lose ip connectivity before the link is torn down.
+ *
+ * @param status one of {@code VALID_NETWORK} or {@code INVALID_NETWORK}.
+ * @param redirectUrl If the Internet probe was redirected, this is the destination it was
+ * redirected to, otherwise {@code null}.
+ */
+ protected void networkStatus(int status, String redirectUrl) {
+ }
+
+ /**
+ * Called when the user asks to remember the choice to use this network even if unvalidated.
+ * The transport is responsible for remembering the choice, and the next time the user connects
+ * to the network, should explicitlySelected with {@code acceptUnvalidated} set to {@code true}.
+ * This method will only be called if {@link #explicitlySelected} was called with
+ * {@code acceptUnvalidated} set to {@code false}.
+ */
+ protected void saveAcceptUnvalidated(boolean accept) {
+ }
+
+ /**
+ * Requests that the network hardware send the specified packet at the specified interval.
+ */
+ protected void startSocketKeepalive(Message msg) {
+ onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED);
+ }
+
+ /**
+ * Requests that the network hardware send the specified packet at the specified interval.
+ */
+ protected void stopSocketKeepalive(Message msg) {
+ onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED);
+ }
+
+ /**
+ * Called by the network when a socket keepalive event occurs.
+ */
+ public void onSocketKeepaliveEvent(int slot, int reason) {
+ queueOrSendMessage(EVENT_SOCKET_KEEPALIVE, slot, reason);
+ }
+
+ /**
+ * Called by ConnectivityService to add specific packet filter to network hardware to block
+ * ACKs matching the sent keepalive packets. Implementations that support this feature must
+ * override this method.
+ */
+ protected void addKeepalivePacketFilter(Message msg) {
+ }
+
+ /**
+ * Called by ConnectivityService to remove a packet filter installed with
+ * {@link #addKeepalivePacketFilter(Message)}. Implementations that support this feature
+ * must override this method.
+ */
+ protected void removeKeepalivePacketFilter(Message msg) {
+ }
+
+ /**
+ * Called by ConnectivityService to inform this network transport of signal strength thresholds
+ * that when crossed should trigger a system wakeup and a NetworkCapabilities update.
+ */
+ protected void setSignalStrengthThresholds(int[] thresholds) {
+ }
+
+ /**
+ * Called when the user asks to not stay connected to this network because it was found to not
+ * provide Internet access. Usually followed by call to {@code unwanted}. The transport is
+ * responsible for making sure the device does not automatically reconnect to the same network
+ * after the {@code unwanted} call.
+ */
+ protected void preventAutomaticReconnect() {
+ }
+
+ protected void log(String s) {
+ Log.d(LOG_TAG, "NetworkAgent: " + s);
+ }
+}
diff --git a/android/net/NetworkAgentHelper.java b/android/net/NetworkAgentHelper.java
new file mode 100644
index 0000000..7ea7665
--- /dev/null
+++ b/android/net/NetworkAgentHelper.java
@@ -0,0 +1,16 @@
+package android.net;
+
+import android.annotation.NonNull;
+
+/**
+ * Wrapper around {@link android.net.NetworkAgent} to help test coverage
+ *
+ * {@link NetworkAgent} will call non-public method unwanted() when the
+ * agent should be disabled.
+ */
+public class NetworkAgentHelper {
+ public static void callUnwanted(@NonNull NetworkAgent networkAgent) {
+ System.out.println("NetworkAgentHelper Faking unwanted() call from connectivity manager");
+ networkAgent.unwanted();
+ }
+}
diff --git a/android/net/NetworkBadging.java b/android/net/NetworkBadging.java
new file mode 100644
index 0000000..cd9ea88
--- /dev/null
+++ b/android/net/NetworkBadging.java
@@ -0,0 +1,96 @@
+/*
+ * 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 android.net;
+
+import android.annotation.DrawableRes;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.graphics.drawable.Drawable;
+import android.net.wifi.WifiManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Utility methods for working with network badging.
+ *
+ * @removed
+ *
+ */
+@Deprecated
+public class NetworkBadging {
+
+ @IntDef({BADGING_NONE, BADGING_SD, BADGING_HD, BADGING_4K})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Badging {}
+
+ public static final int BADGING_NONE = 0;
+ public static final int BADGING_SD = 10;
+ public static final int BADGING_HD = 20;
+ public static final int BADGING_4K = 30;
+
+ private NetworkBadging() {}
+
+ /**
+ * Returns a Wi-Fi icon for a network with a given signal level and badging value.
+ *
+ * @param signalLevel The level returned by {@link WifiManager#calculateSignalLevel(int, int)}
+ * for a network. Must be between 0 and {@link WifiManager#RSSI_LEVELS}-1.
+ * @param badging {@see NetworkBadging#Badging}, retrieved from
+ * {@link ScoredNetwork#calculateBadge(int)}.
+ * @param theme The theme for the current application, may be null.
+ * @return Drawable for the given icon
+ * @throws IllegalArgumentException if {@code signalLevel} is out of range or {@code badging}
+ * is an invalid value
+ */
+ @NonNull public static Drawable getWifiIcon(
+ @IntRange(from=0, to=4) int signalLevel, @Badging int badging, @Nullable Theme theme) {
+ return Resources.getSystem().getDrawable(getWifiSignalResource(signalLevel), theme);
+ }
+
+ /**
+ * Returns the wifi signal resource id for the given signal level.
+ *
+ * <p>This wifi signal resource is a wifi icon to be displayed by itself when there is no badge.
+ *
+ * @param signalLevel The level returned by {@link WifiManager#calculateSignalLevel(int, int)}
+ * for a network. Must be between 0 and {@link WifiManager#RSSI_LEVELS}-1.
+ * @return the @DrawableRes for the icon
+ * @throws IllegalArgumentException for an invalid signal level
+ * @hide
+ */
+ @DrawableRes private static int getWifiSignalResource(int signalLevel) {
+ switch (signalLevel) {
+ case 0:
+ return com.android.internal.R.drawable.ic_wifi_signal_0;
+ case 1:
+ return com.android.internal.R.drawable.ic_wifi_signal_1;
+ case 2:
+ return com.android.internal.R.drawable.ic_wifi_signal_2;
+ case 3:
+ return com.android.internal.R.drawable.ic_wifi_signal_3;
+ case 4:
+ return com.android.internal.R.drawable.ic_wifi_signal_4;
+ default:
+ throw new IllegalArgumentException("Invalid signal level: " + signalLevel);
+ }
+ }
+}
diff --git a/android/net/NetworkCapabilities.java b/android/net/NetworkCapabilities.java
new file mode 100644
index 0000000..3e325b7
--- /dev/null
+++ b/android/net/NetworkCapabilities.java
@@ -0,0 +1,1709 @@
+/*
+ * Copyright (C) 2014 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;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArraySet;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.BitUtils;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.Set;
+import java.util.StringJoiner;
+
+/**
+ * Representation of the capabilities of an active network. Instances are
+ * typically obtained through
+ * {@link NetworkCallback#onCapabilitiesChanged(Network, NetworkCapabilities)}
+ * or {@link ConnectivityManager#getNetworkCapabilities(Network)}.
+ * <p>
+ * This replaces the old {@link ConnectivityManager#TYPE_MOBILE} method of
+ * network selection. Rather than indicate a need for Wi-Fi because an
+ * application needs high bandwidth and risk obsolescence when a new, fast
+ * network appears (like LTE), the application should specify it needs high
+ * bandwidth. Similarly if an application needs an unmetered network for a bulk
+ * transfer it can specify that rather than assuming all cellular based
+ * connections are metered and all Wi-Fi based connections are not.
+ */
+public final class NetworkCapabilities implements Parcelable {
+ private static final String TAG = "NetworkCapabilities";
+ private static final int INVALID_UID = -1;
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public NetworkCapabilities() {
+ clearAll();
+ mNetworkCapabilities = DEFAULT_CAPABILITIES;
+ }
+
+ public NetworkCapabilities(NetworkCapabilities nc) {
+ if (nc != null) {
+ set(nc);
+ }
+ }
+
+ /**
+ * Completely clears the contents of this object, removing even the capabilities that are set
+ * by default when the object is constructed.
+ * @hide
+ */
+ public void clearAll() {
+ mNetworkCapabilities = mTransportTypes = mUnwantedNetworkCapabilities = 0;
+ mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED;
+ mNetworkSpecifier = null;
+ mTransportInfo = null;
+ mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED;
+ mUids = null;
+ mEstablishingVpnAppUid = INVALID_UID;
+ mSSID = null;
+ }
+
+ /**
+ * Set all contents of this object to the contents of a NetworkCapabilities.
+ * @hide
+ */
+ public void set(@NonNull NetworkCapabilities nc) {
+ mNetworkCapabilities = nc.mNetworkCapabilities;
+ mTransportTypes = nc.mTransportTypes;
+ mLinkUpBandwidthKbps = nc.mLinkUpBandwidthKbps;
+ mLinkDownBandwidthKbps = nc.mLinkDownBandwidthKbps;
+ mNetworkSpecifier = nc.mNetworkSpecifier;
+ mTransportInfo = nc.mTransportInfo;
+ mSignalStrength = nc.mSignalStrength;
+ setUids(nc.mUids); // Will make the defensive copy
+ mEstablishingVpnAppUid = nc.mEstablishingVpnAppUid;
+ mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities;
+ mSSID = nc.mSSID;
+ }
+
+ /**
+ * Represents the network's capabilities. If any are specified they will be satisfied
+ * by any Network that matches all of them.
+ */
+ @UnsupportedAppUsage
+ private long mNetworkCapabilities;
+
+ /**
+ * If any capabilities specified here they must not exist in the matching Network.
+ */
+ private long mUnwantedNetworkCapabilities;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "NET_CAPABILITY_" }, value = {
+ NET_CAPABILITY_MMS,
+ NET_CAPABILITY_SUPL,
+ NET_CAPABILITY_DUN,
+ NET_CAPABILITY_FOTA,
+ NET_CAPABILITY_IMS,
+ NET_CAPABILITY_CBS,
+ NET_CAPABILITY_WIFI_P2P,
+ NET_CAPABILITY_IA,
+ NET_CAPABILITY_RCS,
+ NET_CAPABILITY_XCAP,
+ NET_CAPABILITY_EIMS,
+ NET_CAPABILITY_NOT_METERED,
+ NET_CAPABILITY_INTERNET,
+ NET_CAPABILITY_NOT_RESTRICTED,
+ NET_CAPABILITY_TRUSTED,
+ NET_CAPABILITY_NOT_VPN,
+ NET_CAPABILITY_VALIDATED,
+ NET_CAPABILITY_CAPTIVE_PORTAL,
+ NET_CAPABILITY_NOT_ROAMING,
+ NET_CAPABILITY_FOREGROUND,
+ NET_CAPABILITY_NOT_CONGESTED,
+ NET_CAPABILITY_NOT_SUSPENDED,
+ NET_CAPABILITY_OEM_PAID,
+ NET_CAPABILITY_MCX,
+ NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+ })
+ public @interface NetCapability { }
+
+ /**
+ * Indicates this is a network that has the ability to reach the
+ * carrier's MMSC for sending and receiving MMS messages.
+ */
+ public static final int NET_CAPABILITY_MMS = 0;
+
+ /**
+ * Indicates this is a network that has the ability to reach the carrier's
+ * SUPL server, used to retrieve GPS information.
+ */
+ public static final int NET_CAPABILITY_SUPL = 1;
+
+ /**
+ * Indicates this is a network that has the ability to reach the carrier's
+ * DUN or tethering gateway.
+ */
+ public static final int NET_CAPABILITY_DUN = 2;
+
+ /**
+ * Indicates this is a network that has the ability to reach the carrier's
+ * FOTA portal, used for over the air updates.
+ */
+ public static final int NET_CAPABILITY_FOTA = 3;
+
+ /**
+ * Indicates this is a network that has the ability to reach the carrier's
+ * IMS servers, used for network registration and signaling.
+ */
+ public static final int NET_CAPABILITY_IMS = 4;
+
+ /**
+ * Indicates this is a network that has the ability to reach the carrier's
+ * CBS servers, used for carrier specific services.
+ */
+ public static final int NET_CAPABILITY_CBS = 5;
+
+ /**
+ * Indicates this is a network that has the ability to reach a Wi-Fi direct
+ * peer.
+ */
+ public static final int NET_CAPABILITY_WIFI_P2P = 6;
+
+ /**
+ * Indicates this is a network that has the ability to reach a carrier's
+ * Initial Attach servers.
+ */
+ public static final int NET_CAPABILITY_IA = 7;
+
+ /**
+ * Indicates this is a network that has the ability to reach a carrier's
+ * RCS servers, used for Rich Communication Services.
+ */
+ public static final int NET_CAPABILITY_RCS = 8;
+
+ /**
+ * Indicates this is a network that has the ability to reach a carrier's
+ * XCAP servers, used for configuration and control.
+ */
+ public static final int NET_CAPABILITY_XCAP = 9;
+
+ /**
+ * Indicates this is a network that has the ability to reach a carrier's
+ * Emergency IMS servers or other services, used for network signaling
+ * during emergency calls.
+ */
+ public static final int NET_CAPABILITY_EIMS = 10;
+
+ /**
+ * Indicates that this network is unmetered.
+ */
+ public static final int NET_CAPABILITY_NOT_METERED = 11;
+
+ /**
+ * Indicates that this network should be able to reach the internet.
+ */
+ public static final int NET_CAPABILITY_INTERNET = 12;
+
+ /**
+ * Indicates that this network is available for general use. If this is not set
+ * applications should not attempt to communicate on this network. Note that this
+ * is simply informative and not enforcement - enforcement is handled via other means.
+ * Set by default.
+ */
+ public static final int NET_CAPABILITY_NOT_RESTRICTED = 13;
+
+ /**
+ * Indicates that the user has indicated implicit trust of this network. This
+ * generally means it's a sim-selected carrier, a plugged in ethernet, a paired
+ * BT device or a wifi the user asked to connect to. Untrusted networks
+ * are probably limited to unknown wifi AP. Set by default.
+ */
+ public static final int NET_CAPABILITY_TRUSTED = 14;
+
+ /**
+ * Indicates that this network is not a VPN. This capability is set by default and should be
+ * explicitly cleared for VPN networks.
+ */
+ public static final int NET_CAPABILITY_NOT_VPN = 15;
+
+ /**
+ * Indicates that connectivity on this network was successfully validated. For example, for a
+ * network with NET_CAPABILITY_INTERNET, it means that Internet connectivity was successfully
+ * detected.
+ */
+ public static final int NET_CAPABILITY_VALIDATED = 16;
+
+ /**
+ * Indicates that this network was found to have a captive portal in place last time it was
+ * probed.
+ */
+ public static final int NET_CAPABILITY_CAPTIVE_PORTAL = 17;
+
+ /**
+ * Indicates that this network is not roaming.
+ */
+ public static final int NET_CAPABILITY_NOT_ROAMING = 18;
+
+ /**
+ * Indicates that this network is available for use by apps, and not a network that is being
+ * kept up in the background to facilitate fast network switching.
+ */
+ public static final int NET_CAPABILITY_FOREGROUND = 19;
+
+ /**
+ * Indicates that this network is not congested.
+ * <p>
+ * When a network is congested, applications should defer network traffic
+ * that can be done at a later time, such as uploading analytics.
+ */
+ public static final int NET_CAPABILITY_NOT_CONGESTED = 20;
+
+ /**
+ * Indicates that this network is not currently suspended.
+ * <p>
+ * When a network is suspended, the network's IP addresses and any connections
+ * established on the network remain valid, but the network is temporarily unable
+ * to transfer data. This can happen, for example, if a cellular network experiences
+ * a temporary loss of signal, such as when driving through a tunnel, etc.
+ * A network with this capability is not suspended, so is expected to be able to
+ * transfer data.
+ */
+ public static final int NET_CAPABILITY_NOT_SUSPENDED = 21;
+
+ /**
+ * Indicates that traffic that goes through this network is paid by oem. For example,
+ * this network can be used by system apps to upload telemetry data.
+ * @hide
+ */
+ @SystemApi
+ public static final int NET_CAPABILITY_OEM_PAID = 22;
+
+ /**
+ * Indicates this is a network that has the ability to reach a carrier's Mission Critical
+ * servers.
+ */
+ public static final int NET_CAPABILITY_MCX = 23;
+
+ /**
+ * Indicates that this network was tested to only provide partial connectivity.
+ * @hide
+ */
+ @SystemApi
+ public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24;
+
+ private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
+ private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_PARTIAL_CONNECTIVITY;
+
+ /**
+ * Network capabilities that are expected to be mutable, i.e., can change while a particular
+ * network is connected.
+ */
+ private static final long MUTABLE_CAPABILITIES =
+ // TRUSTED can change when user explicitly connects to an untrusted network in Settings.
+ // http://b/18206275
+ (1 << NET_CAPABILITY_TRUSTED)
+ | (1 << NET_CAPABILITY_VALIDATED)
+ | (1 << NET_CAPABILITY_CAPTIVE_PORTAL)
+ | (1 << NET_CAPABILITY_NOT_ROAMING)
+ | (1 << NET_CAPABILITY_FOREGROUND)
+ | (1 << NET_CAPABILITY_NOT_CONGESTED)
+ | (1 << NET_CAPABILITY_NOT_SUSPENDED)
+ | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY);
+
+ /**
+ * Network capabilities that are not allowed in NetworkRequests. This exists because the
+ * NetworkFactory / NetworkAgent model does not deal well with the situation where a
+ * capability's presence cannot be known in advance. If such a capability is requested, then we
+ * can get into a cycle where the NetworkFactory endlessly churns out NetworkAgents that then
+ * get immediately torn down because they do not have the requested capability.
+ */
+ private static final long NON_REQUESTABLE_CAPABILITIES =
+ MUTABLE_CAPABILITIES & ~(1 << NET_CAPABILITY_TRUSTED);
+
+ /**
+ * Capabilities that are set by default when the object is constructed.
+ */
+ private static final long DEFAULT_CAPABILITIES =
+ (1 << NET_CAPABILITY_NOT_RESTRICTED) |
+ (1 << NET_CAPABILITY_TRUSTED) |
+ (1 << NET_CAPABILITY_NOT_VPN);
+
+ /**
+ * Capabilities that suggest that a network is restricted.
+ * {@see #maybeMarkCapabilitiesRestricted}, {@see #FORCE_RESTRICTED_CAPABILITIES}
+ */
+ @VisibleForTesting
+ /* package */ static final long RESTRICTED_CAPABILITIES =
+ (1 << NET_CAPABILITY_CBS) |
+ (1 << NET_CAPABILITY_DUN) |
+ (1 << NET_CAPABILITY_EIMS) |
+ (1 << NET_CAPABILITY_FOTA) |
+ (1 << NET_CAPABILITY_IA) |
+ (1 << NET_CAPABILITY_IMS) |
+ (1 << NET_CAPABILITY_RCS) |
+ (1 << NET_CAPABILITY_XCAP) |
+ (1 << NET_CAPABILITY_MCX);
+
+ /**
+ * Capabilities that force network to be restricted.
+ * {@see #maybeMarkCapabilitiesRestricted}.
+ */
+ private static final long FORCE_RESTRICTED_CAPABILITIES =
+ (1 << NET_CAPABILITY_OEM_PAID);
+
+ /**
+ * Capabilities that suggest that a network is unrestricted.
+ * {@see #maybeMarkCapabilitiesRestricted}.
+ */
+ @VisibleForTesting
+ /* package */ static final long UNRESTRICTED_CAPABILITIES =
+ (1 << NET_CAPABILITY_INTERNET) |
+ (1 << NET_CAPABILITY_MMS) |
+ (1 << NET_CAPABILITY_SUPL) |
+ (1 << NET_CAPABILITY_WIFI_P2P);
+
+ /**
+ * Capabilities that are managed by ConnectivityService.
+ */
+ private static final long CONNECTIVITY_MANAGED_CAPABILITIES =
+ (1 << NET_CAPABILITY_VALIDATED)
+ | (1 << NET_CAPABILITY_CAPTIVE_PORTAL)
+ | (1 << NET_CAPABILITY_FOREGROUND)
+ | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY);
+
+ /**
+ * Adds the given capability to this {@code NetworkCapability} instance.
+ * Multiple capabilities may be applied sequentially. Note that when searching
+ * for a network to satisfy a request, all capabilities requested must be satisfied.
+ * <p>
+ * If the given capability was previously added to the list of unwanted capabilities
+ * then the capability will also be removed from the list of unwanted capabilities.
+ *
+ * @param capability the capability to be added.
+ * @return This NetworkCapabilities instance, to facilitate chaining.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public @NonNull NetworkCapabilities addCapability(@NetCapability int capability) {
+ checkValidCapability(capability);
+ mNetworkCapabilities |= 1 << capability;
+ mUnwantedNetworkCapabilities &= ~(1 << capability); // remove from unwanted capability list
+ return this;
+ }
+
+ /**
+ * Adds the given capability to the list of unwanted capabilities of this
+ * {@code NetworkCapability} instance. Multiple unwanted capabilities may be applied
+ * sequentially. Note that when searching for a network to satisfy a request, the network
+ * must not contain any capability from unwanted capability list.
+ * <p>
+ * If the capability was previously added to the list of required capabilities (for
+ * example, it was there by default or added using {@link #addCapability(int)} method), then
+ * it will be removed from the list of required capabilities as well.
+ *
+ * @see #addCapability(int)
+ * @hide
+ */
+ public void addUnwantedCapability(@NetCapability int capability) {
+ checkValidCapability(capability);
+ mUnwantedNetworkCapabilities |= 1 << capability;
+ mNetworkCapabilities &= ~(1 << capability); // remove from requested capabilities
+ }
+
+ /**
+ * Removes (if found) the given capability from this {@code NetworkCapability} instance.
+ * <p>
+ * Note that this method removes capabilities that were added via {@link #addCapability(int)},
+ * {@link #addUnwantedCapability(int)} or {@link #setCapabilities(int[], int[])} .
+ *
+ * @param capability the capability to be removed.
+ * @return This NetworkCapabilities instance, to facilitate chaining.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public @NonNull NetworkCapabilities removeCapability(@NetCapability int capability) {
+ checkValidCapability(capability);
+ final long mask = ~(1 << capability);
+ mNetworkCapabilities &= mask;
+ mUnwantedNetworkCapabilities &= mask;
+ return this;
+ }
+
+ /**
+ * Sets (or clears) the given capability on this {@link NetworkCapabilities}
+ * instance.
+ *
+ * @hide
+ */
+ public @NonNull NetworkCapabilities setCapability(@NetCapability int capability,
+ boolean value) {
+ if (value) {
+ addCapability(capability);
+ } else {
+ removeCapability(capability);
+ }
+ return this;
+ }
+
+ /**
+ * Gets all the capabilities set on this {@code NetworkCapability} instance.
+ *
+ * @return an array of capability values for this instance.
+ * @hide
+ */
+ @TestApi
+ public @NetCapability int[] getCapabilities() {
+ return BitUtils.unpackBits(mNetworkCapabilities);
+ }
+
+ /**
+ * Gets all the unwanted capabilities set on this {@code NetworkCapability} instance.
+ *
+ * @return an array of unwanted capability values for this instance.
+ * @hide
+ */
+ public @NetCapability int[] getUnwantedCapabilities() {
+ return BitUtils.unpackBits(mUnwantedNetworkCapabilities);
+ }
+
+
+ /**
+ * Sets all the capabilities set on this {@code NetworkCapability} instance.
+ * This overwrites any existing capabilities.
+ *
+ * @hide
+ */
+ public void setCapabilities(@NetCapability int[] capabilities,
+ @NetCapability int[] unwantedCapabilities) {
+ mNetworkCapabilities = BitUtils.packBits(capabilities);
+ mUnwantedNetworkCapabilities = BitUtils.packBits(unwantedCapabilities);
+ }
+
+ /**
+ * @deprecated use {@link #setCapabilities(int[], int[])}
+ * @hide
+ */
+ @Deprecated
+ public void setCapabilities(@NetCapability int[] capabilities) {
+ setCapabilities(capabilities, new int[] {});
+ }
+
+ /**
+ * Tests for the presence of a capability on this instance.
+ *
+ * @param capability the capabilities to be tested for.
+ * @return {@code true} if set on this instance.
+ */
+ public boolean hasCapability(@NetCapability int capability) {
+ return isValidCapability(capability)
+ && ((mNetworkCapabilities & (1 << capability)) != 0);
+ }
+
+ /** @hide */
+ public boolean hasUnwantedCapability(@NetCapability int capability) {
+ return isValidCapability(capability)
+ && ((mUnwantedNetworkCapabilities & (1 << capability)) != 0);
+ }
+
+ /**
+ * Check if this NetworkCapabilities has system managed capabilities or not.
+ * @hide
+ */
+ public boolean hasConnectivityManagedCapability() {
+ return ((mNetworkCapabilities & CONNECTIVITY_MANAGED_CAPABILITIES) != 0);
+ }
+
+ /** Note this method may result in having the same capability in wanted and unwanted lists. */
+ private void combineNetCapabilities(@NonNull NetworkCapabilities nc) {
+ this.mNetworkCapabilities |= nc.mNetworkCapabilities;
+ this.mUnwantedNetworkCapabilities |= nc.mUnwantedNetworkCapabilities;
+ }
+
+ /**
+ * Convenience function that returns a human-readable description of the first mutable
+ * capability we find. Used to present an error message to apps that request mutable
+ * capabilities.
+ *
+ * @hide
+ */
+ public @Nullable String describeFirstNonRequestableCapability() {
+ final long nonRequestable = (mNetworkCapabilities | mUnwantedNetworkCapabilities)
+ & NON_REQUESTABLE_CAPABILITIES;
+
+ if (nonRequestable != 0) {
+ return capabilityNameOf(BitUtils.unpackBits(nonRequestable)[0]);
+ }
+ if (mLinkUpBandwidthKbps != 0 || mLinkDownBandwidthKbps != 0) return "link bandwidth";
+ if (hasSignalStrength()) return "signalStrength";
+ return null;
+ }
+
+ private boolean satisfiedByNetCapabilities(@NonNull NetworkCapabilities nc,
+ boolean onlyImmutable) {
+ long requestedCapabilities = mNetworkCapabilities;
+ long requestedUnwantedCapabilities = mUnwantedNetworkCapabilities;
+ long providedCapabilities = nc.mNetworkCapabilities;
+
+ if (onlyImmutable) {
+ requestedCapabilities &= ~MUTABLE_CAPABILITIES;
+ requestedUnwantedCapabilities &= ~MUTABLE_CAPABILITIES;
+ }
+ return ((providedCapabilities & requestedCapabilities) == requestedCapabilities)
+ && ((requestedUnwantedCapabilities & providedCapabilities) == 0);
+ }
+
+ /** @hide */
+ public boolean equalsNetCapabilities(@NonNull NetworkCapabilities nc) {
+ return (nc.mNetworkCapabilities == this.mNetworkCapabilities)
+ && (nc.mUnwantedNetworkCapabilities == this.mUnwantedNetworkCapabilities);
+ }
+
+ private boolean equalsNetCapabilitiesRequestable(@NonNull NetworkCapabilities that) {
+ return ((this.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES) ==
+ (that.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES))
+ && ((this.mUnwantedNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES) ==
+ (that.mUnwantedNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES));
+ }
+
+ /**
+ * Removes the NET_CAPABILITY_NOT_RESTRICTED capability if all the capabilities it provides are
+ * typically provided by restricted networks.
+ *
+ * TODO: consider:
+ * - Renaming it to guessRestrictedCapability and make it set the
+ * restricted capability bit in addition to clearing it.
+ * @hide
+ */
+ public void maybeMarkCapabilitiesRestricted() {
+ // Check if we have any capability that forces the network to be restricted.
+ final boolean forceRestrictedCapability =
+ (mNetworkCapabilities & FORCE_RESTRICTED_CAPABILITIES) != 0;
+
+ // Verify there aren't any unrestricted capabilities. If there are we say
+ // the whole thing is unrestricted unless it is forced to be restricted.
+ final boolean hasUnrestrictedCapabilities =
+ (mNetworkCapabilities & UNRESTRICTED_CAPABILITIES) != 0;
+
+ // Must have at least some restricted capabilities.
+ final boolean hasRestrictedCapabilities =
+ (mNetworkCapabilities & RESTRICTED_CAPABILITIES) != 0;
+
+ if (forceRestrictedCapability
+ || (hasRestrictedCapabilities && !hasUnrestrictedCapabilities)) {
+ removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+ }
+ }
+
+ /**
+ * Representing the transport type. Apps should generally not care about transport. A
+ * request for a fast internet connection could be satisfied by a number of different
+ * transports. If any are specified here it will be satisfied a Network that matches
+ * any of them. If a caller doesn't care about the transport it should not specify any.
+ */
+ private long mTransportTypes;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "TRANSPORT_" }, value = {
+ TRANSPORT_CELLULAR,
+ TRANSPORT_WIFI,
+ TRANSPORT_BLUETOOTH,
+ TRANSPORT_ETHERNET,
+ TRANSPORT_VPN,
+ TRANSPORT_WIFI_AWARE,
+ TRANSPORT_LOWPAN,
+ TRANSPORT_TEST,
+ })
+ public @interface Transport { }
+
+ /**
+ * Indicates this network uses a Cellular transport.
+ */
+ public static final int TRANSPORT_CELLULAR = 0;
+
+ /**
+ * Indicates this network uses a Wi-Fi transport.
+ */
+ public static final int TRANSPORT_WIFI = 1;
+
+ /**
+ * Indicates this network uses a Bluetooth transport.
+ */
+ public static final int TRANSPORT_BLUETOOTH = 2;
+
+ /**
+ * Indicates this network uses an Ethernet transport.
+ */
+ public static final int TRANSPORT_ETHERNET = 3;
+
+ /**
+ * Indicates this network uses a VPN transport.
+ */
+ public static final int TRANSPORT_VPN = 4;
+
+ /**
+ * Indicates this network uses a Wi-Fi Aware transport.
+ */
+ public static final int TRANSPORT_WIFI_AWARE = 5;
+
+ /**
+ * Indicates this network uses a LoWPAN transport.
+ */
+ public static final int TRANSPORT_LOWPAN = 6;
+
+ /**
+ * Indicates this network uses a Test-only virtual interface as a transport.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final int TRANSPORT_TEST = 7;
+
+ /** @hide */
+ public static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
+ /** @hide */
+ public static final int MAX_TRANSPORT = TRANSPORT_TEST;
+
+ /** @hide */
+ public static boolean isValidTransport(@Transport int transportType) {
+ return (MIN_TRANSPORT <= transportType) && (transportType <= MAX_TRANSPORT);
+ }
+
+ private static final String[] TRANSPORT_NAMES = {
+ "CELLULAR",
+ "WIFI",
+ "BLUETOOTH",
+ "ETHERNET",
+ "VPN",
+ "WIFI_AWARE",
+ "LOWPAN",
+ "TEST"
+ };
+
+ /**
+ * Adds the given transport type to this {@code NetworkCapability} instance.
+ * Multiple transports may be applied sequentially. Note that when searching
+ * for a network to satisfy a request, any listed in the request will satisfy the request.
+ * For example {@code TRANSPORT_WIFI} and {@code TRANSPORT_ETHERNET} added to a
+ * {@code NetworkCapabilities} would cause either a Wi-Fi network or an Ethernet network
+ * to be selected. This is logically different than
+ * {@code NetworkCapabilities.NET_CAPABILITY_*} listed above.
+ *
+ * @param transportType the transport type to be added.
+ * @return This NetworkCapabilities instance, to facilitate chaining.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public @NonNull NetworkCapabilities addTransportType(@Transport int transportType) {
+ checkValidTransportType(transportType);
+ mTransportTypes |= 1 << transportType;
+ setNetworkSpecifier(mNetworkSpecifier); // used for exception checking
+ return this;
+ }
+
+ /**
+ * Removes (if found) the given transport from this {@code NetworkCapability} instance.
+ *
+ * @param transportType the transport type to be removed.
+ * @return This NetworkCapabilities instance, to facilitate chaining.
+ * @hide
+ */
+ public @NonNull NetworkCapabilities removeTransportType(@Transport int transportType) {
+ checkValidTransportType(transportType);
+ mTransportTypes &= ~(1 << transportType);
+ setNetworkSpecifier(mNetworkSpecifier); // used for exception checking
+ return this;
+ }
+
+ /**
+ * Sets (or clears) the given transport on this {@link NetworkCapabilities}
+ * instance.
+ *
+ * @hide
+ */
+ public @NonNull NetworkCapabilities setTransportType(@Transport int transportType,
+ boolean value) {
+ if (value) {
+ addTransportType(transportType);
+ } else {
+ removeTransportType(transportType);
+ }
+ return this;
+ }
+
+ /**
+ * Gets all the transports set on this {@code NetworkCapability} instance.
+ *
+ * @return an array of transport type values for this instance.
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ @NonNull public @Transport int[] getTransportTypes() {
+ return BitUtils.unpackBits(mTransportTypes);
+ }
+
+ /**
+ * Sets all the transports set on this {@code NetworkCapability} instance.
+ * This overwrites any existing transports.
+ *
+ * @hide
+ */
+ public void setTransportTypes(@Transport int[] transportTypes) {
+ mTransportTypes = BitUtils.packBits(transportTypes);
+ }
+
+ /**
+ * Tests for the presence of a transport on this instance.
+ *
+ * @param transportType the transport type to be tested for.
+ * @return {@code true} if set on this instance.
+ */
+ public boolean hasTransport(@Transport int transportType) {
+ return isValidTransport(transportType) && ((mTransportTypes & (1 << transportType)) != 0);
+ }
+
+ private void combineTransportTypes(NetworkCapabilities nc) {
+ this.mTransportTypes |= nc.mTransportTypes;
+ }
+
+ private boolean satisfiedByTransportTypes(NetworkCapabilities nc) {
+ return ((this.mTransportTypes == 0) ||
+ ((this.mTransportTypes & nc.mTransportTypes) != 0));
+ }
+
+ /** @hide */
+ public boolean equalsTransportTypes(NetworkCapabilities nc) {
+ return (nc.mTransportTypes == this.mTransportTypes);
+ }
+
+ /**
+ * UID of the app that manages this network, or INVALID_UID if none/unknown.
+ *
+ * This field keeps track of the UID of the app that created this network and is in charge
+ * of managing it. In the practice, it is used to store the UID of VPN apps so it is named
+ * accordingly, but it may be renamed if other mechanisms are offered for third party apps
+ * to create networks.
+ *
+ * Because this field is only used in the services side (and to avoid apps being able to
+ * set this to whatever they want), this field is not parcelled and will not be conserved
+ * across the IPC boundary.
+ * @hide
+ */
+ private int mEstablishingVpnAppUid = INVALID_UID;
+
+ /**
+ * Set the UID of the managing app.
+ * @hide
+ */
+ public void setEstablishingVpnAppUid(final int uid) {
+ mEstablishingVpnAppUid = uid;
+ }
+
+ /** @hide */
+ public int getEstablishingVpnAppUid() {
+ return mEstablishingVpnAppUid;
+ }
+
+ /**
+ * Value indicating that link bandwidth is unspecified.
+ * @hide
+ */
+ public static final int LINK_BANDWIDTH_UNSPECIFIED = 0;
+
+ /**
+ * Passive link bandwidth. This is a rough guide of the expected peak bandwidth
+ * for the first hop on the given transport. It is not measured, but may take into account
+ * link parameters (Radio technology, allocated channels, etc).
+ */
+ private int mLinkUpBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED;
+ private int mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED;
+
+ /**
+ * Sets the upstream bandwidth for this network in Kbps. This always only refers to
+ * the estimated first hop transport bandwidth.
+ * <p>
+ * Note that when used to request a network, this specifies the minimum acceptable.
+ * When received as the state of an existing network this specifies the typical
+ * first hop bandwidth expected. This is never measured, but rather is inferred
+ * from technology type and other link parameters. It could be used to differentiate
+ * between very slow 1xRTT cellular links and other faster networks or even between
+ * 802.11b vs 802.11AC wifi technologies. It should not be used to differentiate between
+ * fast backhauls and slow backhauls.
+ *
+ * @param upKbps the estimated first hop upstream (device to network) bandwidth.
+ * @hide
+ */
+ public @NonNull NetworkCapabilities setLinkUpstreamBandwidthKbps(int upKbps) {
+ mLinkUpBandwidthKbps = upKbps;
+ return this;
+ }
+
+ /**
+ * Retrieves the upstream bandwidth for this network in Kbps. This always only refers to
+ * the estimated first hop transport bandwidth.
+ *
+ * @return The estimated first hop upstream (device to network) bandwidth.
+ */
+ public int getLinkUpstreamBandwidthKbps() {
+ return mLinkUpBandwidthKbps;
+ }
+
+ /**
+ * Sets the downstream bandwidth for this network in Kbps. This always only refers to
+ * the estimated first hop transport bandwidth.
+ * <p>
+ * Note that when used to request a network, this specifies the minimum acceptable.
+ * When received as the state of an existing network this specifies the typical
+ * first hop bandwidth expected. This is never measured, but rather is inferred
+ * from technology type and other link parameters. It could be used to differentiate
+ * between very slow 1xRTT cellular links and other faster networks or even between
+ * 802.11b vs 802.11AC wifi technologies. It should not be used to differentiate between
+ * fast backhauls and slow backhauls.
+ *
+ * @param downKbps the estimated first hop downstream (network to device) bandwidth.
+ * @hide
+ */
+ public @NonNull NetworkCapabilities setLinkDownstreamBandwidthKbps(int downKbps) {
+ mLinkDownBandwidthKbps = downKbps;
+ return this;
+ }
+
+ /**
+ * Retrieves the downstream bandwidth for this network in Kbps. This always only refers to
+ * the estimated first hop transport bandwidth.
+ *
+ * @return The estimated first hop downstream (network to device) bandwidth.
+ */
+ public int getLinkDownstreamBandwidthKbps() {
+ return mLinkDownBandwidthKbps;
+ }
+
+ private void combineLinkBandwidths(NetworkCapabilities nc) {
+ this.mLinkUpBandwidthKbps =
+ Math.max(this.mLinkUpBandwidthKbps, nc.mLinkUpBandwidthKbps);
+ this.mLinkDownBandwidthKbps =
+ Math.max(this.mLinkDownBandwidthKbps, nc.mLinkDownBandwidthKbps);
+ }
+ private boolean satisfiedByLinkBandwidths(NetworkCapabilities nc) {
+ return !(this.mLinkUpBandwidthKbps > nc.mLinkUpBandwidthKbps ||
+ this.mLinkDownBandwidthKbps > nc.mLinkDownBandwidthKbps);
+ }
+ private boolean equalsLinkBandwidths(NetworkCapabilities nc) {
+ return (this.mLinkUpBandwidthKbps == nc.mLinkUpBandwidthKbps &&
+ this.mLinkDownBandwidthKbps == nc.mLinkDownBandwidthKbps);
+ }
+ /** @hide */
+ public static int minBandwidth(int a, int b) {
+ if (a == LINK_BANDWIDTH_UNSPECIFIED) {
+ return b;
+ } else if (b == LINK_BANDWIDTH_UNSPECIFIED) {
+ return a;
+ } else {
+ return Math.min(a, b);
+ }
+ }
+ /** @hide */
+ public static int maxBandwidth(int a, int b) {
+ return Math.max(a, b);
+ }
+
+ private NetworkSpecifier mNetworkSpecifier = null;
+ private TransportInfo mTransportInfo = null;
+
+ /**
+ * Sets the optional bearer specific network specifier.
+ * This has no meaning if a single transport is also not specified, so calling
+ * this without a single transport set will generate an exception, as will
+ * subsequently adding or removing transports after this is set.
+ * </p>
+ *
+ * @param networkSpecifier A concrete, parcelable framework class that extends
+ * NetworkSpecifier.
+ * @return This NetworkCapabilities instance, to facilitate chaining.
+ * @hide
+ */
+ public @NonNull NetworkCapabilities setNetworkSpecifier(NetworkSpecifier networkSpecifier) {
+ if (networkSpecifier != null && Long.bitCount(mTransportTypes) != 1) {
+ throw new IllegalStateException("Must have a single transport specified to use " +
+ "setNetworkSpecifier");
+ }
+
+ mNetworkSpecifier = networkSpecifier;
+
+ return this;
+ }
+
+ /**
+ * Sets the optional transport specific information.
+ *
+ * @param transportInfo A concrete, parcelable framework class that extends
+ * {@link TransportInfo}.
+ * @return This NetworkCapabilities instance, to facilitate chaining.
+ * @hide
+ */
+ public @NonNull NetworkCapabilities setTransportInfo(TransportInfo transportInfo) {
+ mTransportInfo = transportInfo;
+ return this;
+ }
+
+ /**
+ * Gets the optional bearer specific network specifier. May be {@code null} if not set.
+ *
+ * @return The optional {@link NetworkSpecifier} specifying the bearer specific network
+ * specifier or {@code null}. See {@link #setNetworkSpecifier}.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public @Nullable NetworkSpecifier getNetworkSpecifier() {
+ return mNetworkSpecifier;
+ }
+
+ /**
+ * Returns a transport-specific information container. The application may cast this
+ * container to a concrete sub-class based on its knowledge of the network request. The
+ * application should be able to deal with a {@code null} return value or an invalid case,
+ * e.g. use {@code instanceof} operator to verify expected type.
+ *
+ * @return A concrete implementation of the {@link TransportInfo} class or null if not
+ * available for the network.
+ */
+ @Nullable public TransportInfo getTransportInfo() {
+ return mTransportInfo;
+ }
+
+ private void combineSpecifiers(NetworkCapabilities nc) {
+ if (mNetworkSpecifier != null && !mNetworkSpecifier.equals(nc.mNetworkSpecifier)) {
+ throw new IllegalStateException("Can't combine two networkSpecifiers");
+ }
+ setNetworkSpecifier(nc.mNetworkSpecifier);
+ }
+
+ private boolean satisfiedBySpecifier(NetworkCapabilities nc) {
+ return mNetworkSpecifier == null || mNetworkSpecifier.satisfiedBy(nc.mNetworkSpecifier)
+ || nc.mNetworkSpecifier instanceof MatchAllNetworkSpecifier;
+ }
+
+ private boolean equalsSpecifier(NetworkCapabilities nc) {
+ return Objects.equals(mNetworkSpecifier, nc.mNetworkSpecifier);
+ }
+
+ private void combineTransportInfos(NetworkCapabilities nc) {
+ if (mTransportInfo != null && !mTransportInfo.equals(nc.mTransportInfo)) {
+ throw new IllegalStateException("Can't combine two TransportInfos");
+ }
+ setTransportInfo(nc.mTransportInfo);
+ }
+
+ private boolean equalsTransportInfo(NetworkCapabilities nc) {
+ return Objects.equals(mTransportInfo, nc.mTransportInfo);
+ }
+
+ /**
+ * Magic value that indicates no signal strength provided. A request specifying this value is
+ * always satisfied.
+ */
+ public static final int SIGNAL_STRENGTH_UNSPECIFIED = Integer.MIN_VALUE;
+
+ /**
+ * Signal strength. This is a signed integer, and higher values indicate better signal.
+ * The exact units are bearer-dependent. For example, Wi-Fi uses RSSI.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ private int mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED;
+
+ /**
+ * Sets the signal strength. This is a signed integer, with higher values indicating a stronger
+ * signal. The exact units are bearer-dependent. For example, Wi-Fi uses the same RSSI units
+ * reported by wifi code.
+ * <p>
+ * Note that when used to register a network callback, this specifies the minimum acceptable
+ * signal strength. When received as the state of an existing network it specifies the current
+ * value. A value of code SIGNAL_STRENGTH_UNSPECIFIED} means no value when received and has no
+ * effect when requesting a callback.
+ *
+ * @param signalStrength the bearer-specific signal strength.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public @NonNull NetworkCapabilities setSignalStrength(int signalStrength) {
+ mSignalStrength = signalStrength;
+ return this;
+ }
+
+ /**
+ * Returns {@code true} if this object specifies a signal strength.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public boolean hasSignalStrength() {
+ return mSignalStrength > SIGNAL_STRENGTH_UNSPECIFIED;
+ }
+
+ /**
+ * Retrieves the signal strength.
+ *
+ * @return The bearer-specific signal strength.
+ */
+ public int getSignalStrength() {
+ return mSignalStrength;
+ }
+
+ private void combineSignalStrength(NetworkCapabilities nc) {
+ this.mSignalStrength = Math.max(this.mSignalStrength, nc.mSignalStrength);
+ }
+
+ private boolean satisfiedBySignalStrength(NetworkCapabilities nc) {
+ return this.mSignalStrength <= nc.mSignalStrength;
+ }
+
+ private boolean equalsSignalStrength(NetworkCapabilities nc) {
+ return this.mSignalStrength == nc.mSignalStrength;
+ }
+
+ /**
+ * List of UIDs this network applies to. No restriction if null.
+ * <p>
+ * For networks, mUids represent the list of network this applies to, and null means this
+ * network applies to all UIDs.
+ * For requests, mUids is the list of UIDs this network MUST apply to to match ; ALL UIDs
+ * must be included in a network so that they match. As an exception to the general rule,
+ * a null mUids field for requests mean "no requirements" rather than what the general rule
+ * would suggest ("must apply to all UIDs") : this is because this has shown to be what users
+ * of this API expect in practice. A network that must match all UIDs can still be
+ * expressed with a set ranging the entire set of possible UIDs.
+ * <p>
+ * mUids is typically (and at this time, only) used by VPN. This network is only available to
+ * the UIDs in this list, and it is their default network. Apps in this list that wish to
+ * bypass the VPN can do so iff the VPN app allows them to or if they are privileged. If this
+ * member is null, then the network is not restricted by app UID. If it's an empty list, then
+ * it means nobody can use it.
+ * As a special exception, the app managing this network (as identified by its UID stored in
+ * mEstablishingVpnAppUid) can always see this network. This is embodied by a special check in
+ * satisfiedByUids. That still does not mean the network necessarily <strong>applies</strong>
+ * to the app that manages it as determined by #appliesToUid.
+ * <p>
+ * Please note that in principle a single app can be associated with multiple UIDs because
+ * each app will have a different UID when it's run as a different (macro-)user. A single
+ * macro user can only have a single active VPN app at any given time however.
+ * <p>
+ * Also please be aware this class does not try to enforce any normalization on this. Callers
+ * can only alter the UIDs by setting them wholesale : this class does not provide any utility
+ * to add or remove individual UIDs or ranges. If callers have any normalization needs on
+ * their own (like requiring sortedness or no overlap) they need to enforce it
+ * themselves. Some of the internal methods also assume this is normalized as in no adjacent
+ * or overlapping ranges are present.
+ *
+ * @hide
+ */
+ private ArraySet<UidRange> mUids = null;
+
+ /**
+ * Convenience method to set the UIDs this network applies to to a single UID.
+ * @hide
+ */
+ public @NonNull NetworkCapabilities setSingleUid(int uid) {
+ final ArraySet<UidRange> identity = new ArraySet<>(1);
+ identity.add(new UidRange(uid, uid));
+ setUids(identity);
+ return this;
+ }
+
+ /**
+ * Set the list of UIDs this network applies to.
+ * This makes a copy of the set so that callers can't modify it after the call.
+ * @hide
+ */
+ public @NonNull NetworkCapabilities setUids(Set<UidRange> uids) {
+ if (null == uids) {
+ mUids = null;
+ } else {
+ mUids = new ArraySet<>(uids);
+ }
+ return this;
+ }
+
+ /**
+ * Get the list of UIDs this network applies to.
+ * This returns a copy of the set so that callers can't modify the original object.
+ * @hide
+ */
+ public @Nullable Set<UidRange> getUids() {
+ return null == mUids ? null : new ArraySet<>(mUids);
+ }
+
+ /**
+ * Test whether this network applies to this UID.
+ * @hide
+ */
+ public boolean appliesToUid(int uid) {
+ if (null == mUids) return true;
+ for (UidRange range : mUids) {
+ if (range.contains(uid)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Tests if the set of UIDs that this network applies to is the same as the passed network.
+ * <p>
+ * This test only checks whether equal range objects are in both sets. It will
+ * return false if the ranges are not exactly the same, even if the covered UIDs
+ * are for an equivalent result.
+ * <p>
+ * Note that this method is not very optimized, which is fine as long as it's not used very
+ * often.
+ * <p>
+ * nc is assumed nonnull.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public boolean equalsUids(@NonNull NetworkCapabilities nc) {
+ Set<UidRange> comparedUids = nc.mUids;
+ if (null == comparedUids) return null == mUids;
+ if (null == mUids) return false;
+ // Make a copy so it can be mutated to check that all ranges in mUids
+ // also are in uids.
+ final Set<UidRange> uids = new ArraySet<>(mUids);
+ for (UidRange range : comparedUids) {
+ if (!uids.contains(range)) {
+ return false;
+ }
+ uids.remove(range);
+ }
+ return uids.isEmpty();
+ }
+
+ /**
+ * Test whether the passed NetworkCapabilities satisfies the UIDs this capabilities require.
+ *
+ * This method is called on the NetworkCapabilities embedded in a request with the
+ * capabilities of an available network. It checks whether all the UIDs from this listen
+ * (representing the UIDs that must have access to the network) are satisfied by the UIDs
+ * in the passed nc (representing the UIDs that this network is available to).
+ * <p>
+ * As a special exception, the UID that created the passed network (as represented by its
+ * mEstablishingVpnAppUid field) always satisfies a NetworkRequest requiring it (of LISTEN
+ * or REQUEST types alike), even if the network does not apply to it. That is so a VPN app
+ * can see its own network when it listens for it.
+ * <p>
+ * nc is assumed nonnull. Else, NPE.
+ * @see #appliesToUid
+ * @hide
+ */
+ public boolean satisfiedByUids(@NonNull NetworkCapabilities nc) {
+ if (null == nc.mUids || null == mUids) return true; // The network satisfies everything.
+ for (UidRange requiredRange : mUids) {
+ if (requiredRange.contains(nc.mEstablishingVpnAppUid)) return true;
+ if (!nc.appliesToUidRange(requiredRange)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns whether this network applies to the passed ranges.
+ * This assumes that to apply, the passed range has to be entirely contained
+ * within one of the ranges this network applies to. If the ranges are not normalized,
+ * this method may return false even though all required UIDs are covered because no
+ * single range contained them all.
+ * @hide
+ */
+ @VisibleForTesting
+ public boolean appliesToUidRange(@Nullable UidRange requiredRange) {
+ if (null == mUids) return true;
+ for (UidRange uidRange : mUids) {
+ if (uidRange.containsRange(requiredRange)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Combine the UIDs this network currently applies to with the UIDs the passed
+ * NetworkCapabilities apply to.
+ * nc is assumed nonnull.
+ */
+ private void combineUids(@NonNull NetworkCapabilities nc) {
+ if (null == nc.mUids || null == mUids) {
+ mUids = null;
+ return;
+ }
+ mUids.addAll(nc.mUids);
+ }
+
+
+ /**
+ * The SSID of the network, or null if not applicable or unknown.
+ * <p>
+ * This is filled in by wifi code.
+ * @hide
+ */
+ private String mSSID;
+
+ /**
+ * Sets the SSID of this network.
+ * @hide
+ */
+ public @NonNull NetworkCapabilities setSSID(@Nullable String ssid) {
+ mSSID = ssid;
+ return this;
+ }
+
+ /**
+ * Gets the SSID of this network, or null if none or unknown.
+ * @hide
+ */
+ public @Nullable String getSSID() {
+ return mSSID;
+ }
+
+ /**
+ * Tests if the SSID of this network is the same as the SSID of the passed network.
+ * @hide
+ */
+ public boolean equalsSSID(@NonNull NetworkCapabilities nc) {
+ return Objects.equals(mSSID, nc.mSSID);
+ }
+
+ /**
+ * Check if the SSID requirements of this object are matched by the passed object.
+ * @hide
+ */
+ public boolean satisfiedBySSID(@NonNull NetworkCapabilities nc) {
+ return mSSID == null || mSSID.equals(nc.mSSID);
+ }
+
+ /**
+ * Combine SSIDs of the capabilities.
+ * <p>
+ * This is only legal if either the SSID of this object is null, or both SSIDs are
+ * equal.
+ * @hide
+ */
+ private void combineSSIDs(@NonNull NetworkCapabilities nc) {
+ if (mSSID != null && !mSSID.equals(nc.mSSID)) {
+ throw new IllegalStateException("Can't combine two SSIDs");
+ }
+ setSSID(nc.mSSID);
+ }
+
+ /**
+ * Combine a set of Capabilities to this one. Useful for coming up with the complete set.
+ * <p>
+ * Note that this method may break an invariant of having a particular capability in either
+ * wanted or unwanted lists but never in both. Requests that have the same capability in
+ * both lists will never be satisfied.
+ * @hide
+ */
+ public void combineCapabilities(@NonNull NetworkCapabilities nc) {
+ combineNetCapabilities(nc);
+ combineTransportTypes(nc);
+ combineLinkBandwidths(nc);
+ combineSpecifiers(nc);
+ combineTransportInfos(nc);
+ combineSignalStrength(nc);
+ combineUids(nc);
+ combineSSIDs(nc);
+ }
+
+ /**
+ * Check if our requirements are satisfied by the given {@code NetworkCapabilities}.
+ *
+ * @param nc the {@code NetworkCapabilities} that may or may not satisfy our requirements.
+ * @param onlyImmutable if {@code true}, do not consider mutable requirements such as link
+ * bandwidth, signal strength, or validation / captive portal status.
+ *
+ * @hide
+ */
+ private boolean satisfiedByNetworkCapabilities(NetworkCapabilities nc, boolean onlyImmutable) {
+ return (nc != null
+ && satisfiedByNetCapabilities(nc, onlyImmutable)
+ && satisfiedByTransportTypes(nc)
+ && (onlyImmutable || satisfiedByLinkBandwidths(nc))
+ && satisfiedBySpecifier(nc)
+ && (onlyImmutable || satisfiedBySignalStrength(nc))
+ && (onlyImmutable || satisfiedByUids(nc))
+ && (onlyImmutable || satisfiedBySSID(nc)));
+ }
+
+ /**
+ * Check if our requirements are satisfied by the given {@code NetworkCapabilities}.
+ *
+ * @param nc the {@code NetworkCapabilities} that may or may not satisfy our requirements.
+ *
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public boolean satisfiedByNetworkCapabilities(@Nullable NetworkCapabilities nc) {
+ return satisfiedByNetworkCapabilities(nc, false);
+ }
+
+ /**
+ * Check if our immutable requirements are satisfied by the given {@code NetworkCapabilities}.
+ *
+ * @param nc the {@code NetworkCapabilities} that may or may not satisfy our requirements.
+ *
+ * @hide
+ */
+ public boolean satisfiedByImmutableNetworkCapabilities(@Nullable NetworkCapabilities nc) {
+ return satisfiedByNetworkCapabilities(nc, true);
+ }
+
+ /**
+ * Checks that our immutable capabilities are the same as those of the given
+ * {@code NetworkCapabilities} and return a String describing any difference.
+ * The returned String is empty if there is no difference.
+ *
+ * @hide
+ */
+ public String describeImmutableDifferences(@Nullable NetworkCapabilities that) {
+ if (that == null) {
+ return "other NetworkCapabilities was null";
+ }
+
+ StringJoiner joiner = new StringJoiner(", ");
+
+ // Ignore NOT_METERED being added or removed as it is effectively dynamic. http://b/63326103
+ // TODO: properly support NOT_METERED as a mutable and requestable capability.
+ final long mask = ~MUTABLE_CAPABILITIES & ~(1 << NET_CAPABILITY_NOT_METERED);
+ long oldImmutableCapabilities = this.mNetworkCapabilities & mask;
+ long newImmutableCapabilities = that.mNetworkCapabilities & mask;
+ if (oldImmutableCapabilities != newImmutableCapabilities) {
+ String before = capabilityNamesOf(BitUtils.unpackBits(oldImmutableCapabilities));
+ String after = capabilityNamesOf(BitUtils.unpackBits(newImmutableCapabilities));
+ joiner.add(String.format("immutable capabilities changed: %s -> %s", before, after));
+ }
+
+ if (!equalsSpecifier(that)) {
+ NetworkSpecifier before = this.getNetworkSpecifier();
+ NetworkSpecifier after = that.getNetworkSpecifier();
+ joiner.add(String.format("specifier changed: %s -> %s", before, after));
+ }
+
+ if (!equalsTransportTypes(that)) {
+ String before = transportNamesOf(this.getTransportTypes());
+ String after = transportNamesOf(that.getTransportTypes());
+ joiner.add(String.format("transports changed: %s -> %s", before, after));
+ }
+
+ return joiner.toString();
+ }
+
+ /**
+ * Checks that our requestable capabilities are the same as those of the given
+ * {@code NetworkCapabilities}.
+ *
+ * @hide
+ */
+ public boolean equalRequestableCapabilities(@Nullable NetworkCapabilities nc) {
+ if (nc == null) return false;
+ return (equalsNetCapabilitiesRequestable(nc) &&
+ equalsTransportTypes(nc) &&
+ equalsSpecifier(nc));
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (obj == null || (obj instanceof NetworkCapabilities == false)) return false;
+ NetworkCapabilities that = (NetworkCapabilities) obj;
+ return (equalsNetCapabilities(that)
+ && equalsTransportTypes(that)
+ && equalsLinkBandwidths(that)
+ && equalsSignalStrength(that)
+ && equalsSpecifier(that)
+ && equalsTransportInfo(that)
+ && equalsUids(that)
+ && equalsSSID(that));
+ }
+
+ @Override
+ public int hashCode() {
+ return (int) (mNetworkCapabilities & 0xFFFFFFFF)
+ + ((int) (mNetworkCapabilities >> 32) * 3)
+ + ((int) (mUnwantedNetworkCapabilities & 0xFFFFFFFF) * 5)
+ + ((int) (mUnwantedNetworkCapabilities >> 32) * 7)
+ + ((int) (mTransportTypes & 0xFFFFFFFF) * 11)
+ + ((int) (mTransportTypes >> 32) * 13)
+ + (mLinkUpBandwidthKbps * 17)
+ + (mLinkDownBandwidthKbps * 19)
+ + Objects.hashCode(mNetworkSpecifier) * 23
+ + (mSignalStrength * 29)
+ + Objects.hashCode(mUids) * 31
+ + Objects.hashCode(mSSID) * 37
+ + Objects.hashCode(mTransportInfo) * 41;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mNetworkCapabilities);
+ dest.writeLong(mUnwantedNetworkCapabilities);
+ dest.writeLong(mTransportTypes);
+ dest.writeInt(mLinkUpBandwidthKbps);
+ dest.writeInt(mLinkDownBandwidthKbps);
+ dest.writeParcelable((Parcelable) mNetworkSpecifier, flags);
+ dest.writeParcelable((Parcelable) mTransportInfo, flags);
+ dest.writeInt(mSignalStrength);
+ dest.writeArraySet(mUids);
+ dest.writeString(mSSID);
+ }
+
+ public static final @android.annotation.NonNull Creator<NetworkCapabilities> CREATOR =
+ new Creator<NetworkCapabilities>() {
+ @Override
+ public NetworkCapabilities createFromParcel(Parcel in) {
+ NetworkCapabilities netCap = new NetworkCapabilities();
+
+ netCap.mNetworkCapabilities = in.readLong();
+ netCap.mUnwantedNetworkCapabilities = in.readLong();
+ netCap.mTransportTypes = in.readLong();
+ netCap.mLinkUpBandwidthKbps = in.readInt();
+ netCap.mLinkDownBandwidthKbps = in.readInt();
+ netCap.mNetworkSpecifier = in.readParcelable(null);
+ netCap.mTransportInfo = in.readParcelable(null);
+ netCap.mSignalStrength = in.readInt();
+ netCap.mUids = (ArraySet<UidRange>) in.readArraySet(
+ null /* ClassLoader, null for default */);
+ netCap.mSSID = in.readString();
+ return netCap;
+ }
+ @Override
+ public NetworkCapabilities[] newArray(int size) {
+ return new NetworkCapabilities[size];
+ }
+ };
+
+ @Override
+ public @NonNull String toString() {
+ final StringBuilder sb = new StringBuilder("[");
+ if (0 != mTransportTypes) {
+ sb.append(" Transports: ");
+ appendStringRepresentationOfBitMaskToStringBuilder(sb, mTransportTypes,
+ NetworkCapabilities::transportNameOf, "|");
+ }
+ if (0 != mNetworkCapabilities) {
+ sb.append(" Capabilities: ");
+ appendStringRepresentationOfBitMaskToStringBuilder(sb, mNetworkCapabilities,
+ NetworkCapabilities::capabilityNameOf, "&");
+ }
+ if (0 != mUnwantedNetworkCapabilities) {
+ sb.append(" Unwanted: ");
+ appendStringRepresentationOfBitMaskToStringBuilder(sb, mUnwantedNetworkCapabilities,
+ NetworkCapabilities::capabilityNameOf, "&");
+ }
+ if (mLinkUpBandwidthKbps > 0) {
+ sb.append(" LinkUpBandwidth>=").append(mLinkUpBandwidthKbps).append("Kbps");
+ }
+ if (mLinkDownBandwidthKbps > 0) {
+ sb.append(" LinkDnBandwidth>=").append(mLinkDownBandwidthKbps).append("Kbps");
+ }
+ if (mNetworkSpecifier != null) {
+ sb.append(" Specifier: <").append(mNetworkSpecifier).append(">");
+ }
+ if (mTransportInfo != null) {
+ sb.append(" TransportInfo: <").append(mTransportInfo).append(">");
+ }
+ if (hasSignalStrength()) {
+ sb.append(" SignalStrength: ").append(mSignalStrength);
+ }
+
+ if (null != mUids) {
+ if ((1 == mUids.size()) && (mUids.valueAt(0).count() == 1)) {
+ sb.append(" Uid: ").append(mUids.valueAt(0).start);
+ } else {
+ sb.append(" Uids: <").append(mUids).append(">");
+ }
+ }
+ if (mEstablishingVpnAppUid != INVALID_UID) {
+ sb.append(" EstablishingAppUid: ").append(mEstablishingVpnAppUid);
+ }
+
+ if (null != mSSID) {
+ sb.append(" SSID: ").append(mSSID);
+ }
+
+ sb.append("]");
+ return sb.toString();
+ }
+
+
+ private interface NameOf {
+ String nameOf(int value);
+ }
+ /**
+ * @hide
+ */
+ public static void appendStringRepresentationOfBitMaskToStringBuilder(@NonNull StringBuilder sb,
+ long bitMask, @NonNull NameOf nameFetcher, @NonNull String separator) {
+ int bitPos = 0;
+ boolean firstElementAdded = false;
+ while (bitMask != 0) {
+ if ((bitMask & 1) != 0) {
+ if (firstElementAdded) {
+ sb.append(separator);
+ } else {
+ firstElementAdded = true;
+ }
+ sb.append(nameFetcher.nameOf(bitPos));
+ }
+ bitMask >>= 1;
+ ++bitPos;
+ }
+ }
+
+ /** @hide */
+ public void writeToProto(@NonNull ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+
+ for (int transport : getTransportTypes()) {
+ proto.write(NetworkCapabilitiesProto.TRANSPORTS, transport);
+ }
+
+ for (int capability : getCapabilities()) {
+ proto.write(NetworkCapabilitiesProto.CAPABILITIES, capability);
+ }
+
+ proto.write(NetworkCapabilitiesProto.LINK_UP_BANDWIDTH_KBPS, mLinkUpBandwidthKbps);
+ proto.write(NetworkCapabilitiesProto.LINK_DOWN_BANDWIDTH_KBPS, mLinkDownBandwidthKbps);
+
+ if (mNetworkSpecifier != null) {
+ proto.write(NetworkCapabilitiesProto.NETWORK_SPECIFIER, mNetworkSpecifier.toString());
+ }
+ if (mTransportInfo != null) {
+ // TODO b/120653863: write transport-specific info to proto?
+ }
+
+ proto.write(NetworkCapabilitiesProto.CAN_REPORT_SIGNAL_STRENGTH, hasSignalStrength());
+ proto.write(NetworkCapabilitiesProto.SIGNAL_STRENGTH, mSignalStrength);
+
+ proto.end(token);
+ }
+
+ /**
+ * @hide
+ */
+ public static @NonNull String capabilityNamesOf(@Nullable @NetCapability int[] capabilities) {
+ StringJoiner joiner = new StringJoiner("|");
+ if (capabilities != null) {
+ for (int c : capabilities) {
+ joiner.add(capabilityNameOf(c));
+ }
+ }
+ return joiner.toString();
+ }
+
+ /**
+ * @hide
+ */
+ public static @NonNull String capabilityNameOf(@NetCapability int capability) {
+ switch (capability) {
+ case NET_CAPABILITY_MMS: return "MMS";
+ case NET_CAPABILITY_SUPL: return "SUPL";
+ case NET_CAPABILITY_DUN: return "DUN";
+ case NET_CAPABILITY_FOTA: return "FOTA";
+ case NET_CAPABILITY_IMS: return "IMS";
+ case NET_CAPABILITY_CBS: return "CBS";
+ case NET_CAPABILITY_WIFI_P2P: return "WIFI_P2P";
+ case NET_CAPABILITY_IA: return "IA";
+ case NET_CAPABILITY_RCS: return "RCS";
+ case NET_CAPABILITY_XCAP: return "XCAP";
+ case NET_CAPABILITY_EIMS: return "EIMS";
+ case NET_CAPABILITY_NOT_METERED: return "NOT_METERED";
+ case NET_CAPABILITY_INTERNET: return "INTERNET";
+ case NET_CAPABILITY_NOT_RESTRICTED: return "NOT_RESTRICTED";
+ case NET_CAPABILITY_TRUSTED: return "TRUSTED";
+ case NET_CAPABILITY_NOT_VPN: return "NOT_VPN";
+ case NET_CAPABILITY_VALIDATED: return "VALIDATED";
+ case NET_CAPABILITY_CAPTIVE_PORTAL: return "CAPTIVE_PORTAL";
+ case NET_CAPABILITY_NOT_ROAMING: return "NOT_ROAMING";
+ case NET_CAPABILITY_FOREGROUND: return "FOREGROUND";
+ case NET_CAPABILITY_NOT_CONGESTED: return "NOT_CONGESTED";
+ case NET_CAPABILITY_NOT_SUSPENDED: return "NOT_SUSPENDED";
+ case NET_CAPABILITY_OEM_PAID: return "OEM_PAID";
+ case NET_CAPABILITY_MCX: return "MCX";
+ case NET_CAPABILITY_PARTIAL_CONNECTIVITY: return "PARTIAL_CONNECTIVITY";
+ default: return Integer.toString(capability);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static @NonNull String transportNamesOf(@Nullable @Transport int[] types) {
+ StringJoiner joiner = new StringJoiner("|");
+ if (types != null) {
+ for (int t : types) {
+ joiner.add(transportNameOf(t));
+ }
+ }
+ return joiner.toString();
+ }
+
+ /**
+ * @hide
+ */
+ public static @NonNull String transportNameOf(@Transport int transport) {
+ if (!isValidTransport(transport)) {
+ return "UNKNOWN";
+ }
+ return TRANSPORT_NAMES[transport];
+ }
+
+ private static void checkValidTransportType(@Transport int transport) {
+ Preconditions.checkArgument(
+ isValidTransport(transport), "Invalid TransportType " + transport);
+ }
+
+ private static boolean isValidCapability(@NetworkCapabilities.NetCapability int capability) {
+ return capability >= MIN_NET_CAPABILITY && capability <= MAX_NET_CAPABILITY;
+ }
+
+ private static void checkValidCapability(@NetworkCapabilities.NetCapability int capability) {
+ Preconditions.checkArgument(isValidCapability(capability),
+ "NetworkCapability " + capability + "out of range");
+ }
+
+ /**
+ * Check if this {@code NetworkCapability} instance is metered.
+ *
+ * @return {@code true} if {@code NET_CAPABILITY_NOT_METERED} is not set on this instance.
+ * @hide
+ */
+ public boolean isMetered() {
+ return !hasCapability(NET_CAPABILITY_NOT_METERED);
+ }
+}
diff --git a/android/net/NetworkConfig.java b/android/net/NetworkConfig.java
new file mode 100644
index 0000000..32a2cda
--- /dev/null
+++ b/android/net/NetworkConfig.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2010 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;
+
+import java.util.Locale;
+
+/**
+ * Describes the buildtime configuration of a network.
+ * Holds settings read from resources.
+ * @hide
+ */
+public class NetworkConfig {
+ /**
+ * Human readable string
+ */
+ public String name;
+
+ /**
+ * Type from ConnectivityManager
+ */
+ public int type;
+
+ /**
+ * the radio number from radio attributes config
+ */
+ public int radio;
+
+ /**
+ * higher number == higher priority when turning off connections
+ */
+ public int priority;
+
+ /**
+ * indicates the boot time dependencyMet setting
+ */
+ public boolean dependencyMet;
+
+ /**
+ * indicates the default restoral timer in seconds
+ * if the network is used as a special network feature
+ * -1 indicates no restoration of default
+ */
+ public int restoreTime;
+
+ /**
+ * input string from config.xml resource. Uses the form:
+ * [Connection name],[ConnectivityManager connection type],
+ * [associated radio-type],[priority],[dependencyMet]
+ */
+ public NetworkConfig(String init) {
+ String fragments[] = init.split(",");
+ name = fragments[0].trim().toLowerCase(Locale.ROOT);
+ type = Integer.parseInt(fragments[1]);
+ radio = Integer.parseInt(fragments[2]);
+ priority = Integer.parseInt(fragments[3]);
+ restoreTime = Integer.parseInt(fragments[4]);
+ dependencyMet = Boolean.parseBoolean(fragments[5]);
+ }
+
+ /**
+ * Indicates if this network is supposed to be default-routable
+ */
+ public boolean isDefault() {
+ return (type == radio);
+ }
+}
diff --git a/android/net/NetworkFactory.java b/android/net/NetworkFactory.java
new file mode 100644
index 0000000..5b1d12c
--- /dev/null
+++ b/android/net/NetworkFactory.java
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2014 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;
+
+import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Protocol;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A NetworkFactory is an entity that creates NetworkAgent objects.
+ * The bearers register with ConnectivityService using {@link #register} and
+ * their factory will start receiving scored NetworkRequests. NetworkRequests
+ * can be filtered 3 ways: by NetworkCapabilities, by score and more complexly by
+ * overridden function. All of these can be dynamic - changing NetworkCapabilities
+ * or score forces re-evaluation of all current requests.
+ *
+ * If any requests pass the filter some overrideable functions will be called.
+ * If the bearer only cares about very simple start/stopNetwork callbacks, those
+ * functions can be overridden. If the bearer needs more interaction, it can
+ * override addNetworkRequest and removeNetworkRequest which will give it each
+ * request that passes their current filters.
+ * @hide
+ **/
+public class NetworkFactory extends Handler {
+ /** @hide */
+ public static class SerialNumber {
+ // Guard used by no network factory.
+ public static final int NONE = -1;
+ // A hardcoded serial number for NetworkAgents representing VPNs. These agents are
+ // not created by any factory, so they use this constant for clarity instead of NONE.
+ public static final int VPN = -2;
+ private static final AtomicInteger sNetworkFactorySerialNumber = new AtomicInteger(1);
+ /** Returns a unique serial number for a factory. */
+ public static final int nextSerialNumber() {
+ return sNetworkFactorySerialNumber.getAndIncrement();
+ }
+ }
+
+ private static final boolean DBG = true;
+ private static final boolean VDBG = false;
+
+ private static final int BASE = Protocol.BASE_NETWORK_FACTORY;
+ /**
+ * Pass a network request to the bearer. If the bearer believes it can
+ * satisfy the request it should connect to the network and create a
+ * NetworkAgent. Once the NetworkAgent is fully functional it will
+ * register itself with ConnectivityService using registerNetworkAgent.
+ * If the bearer cannot immediately satisfy the request (no network,
+ * user disabled the radio, lower-scored network) it should remember
+ * any NetworkRequests it may be able to satisfy in the future. It may
+ * disregard any that it will never be able to service, for example
+ * those requiring a different bearer.
+ * msg.obj = NetworkRequest
+ * msg.arg1 = score - the score of the network currently satisfying this
+ * request. If this bearer knows in advance it cannot
+ * exceed this score it should not try to connect, holding the request
+ * for the future.
+ * Note that subsequent events may give a different (lower
+ * or higher) score for this request, transmitted to each
+ * NetworkFactory through additional CMD_REQUEST_NETWORK msgs
+ * with the same NetworkRequest but an updated score.
+ * Also, network conditions may change for this bearer
+ * allowing for a better score in the future.
+ * msg.arg2 = the serial number of the factory currently responsible for the
+ * NetworkAgent handling this request, or SerialNumber.NONE if none.
+ */
+ public static final int CMD_REQUEST_NETWORK = BASE;
+
+ /**
+ * Cancel a network request
+ * msg.obj = NetworkRequest
+ */
+ public static final int CMD_CANCEL_REQUEST = BASE + 1;
+
+ /**
+ * Internally used to set our best-guess score.
+ * msg.arg1 = new score
+ */
+ private static final int CMD_SET_SCORE = BASE + 2;
+
+ /**
+ * Internally used to set our current filter for coarse bandwidth changes with
+ * technology changes.
+ * msg.obj = new filter
+ */
+ private static final int CMD_SET_FILTER = BASE + 3;
+
+ /**
+ * Sent by NetworkFactory to ConnectivityService to indicate that a request is
+ * unfulfillable.
+ * @see #releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest).
+ */
+ public static final int EVENT_UNFULFILLABLE_REQUEST = BASE + 4;
+
+ private final Context mContext;
+ private final ArrayList<Message> mPreConnectedQueue = new ArrayList<Message>();
+ private AsyncChannel mAsyncChannel;
+ private final String LOG_TAG;
+
+ private final SparseArray<NetworkRequestInfo> mNetworkRequests =
+ new SparseArray<NetworkRequestInfo>();
+
+ private int mScore;
+ private NetworkCapabilities mCapabilityFilter;
+
+ private int mRefCount = 0;
+ private Messenger mMessenger = null;
+ private int mSerialNumber;
+
+ @UnsupportedAppUsage
+ public NetworkFactory(Looper looper, Context context, String logTag,
+ NetworkCapabilities filter) {
+ super(looper);
+ LOG_TAG = logTag;
+ mContext = context;
+ mCapabilityFilter = filter;
+ }
+
+ public void register() {
+ if (DBG) log("Registering NetworkFactory");
+ if (mMessenger == null) {
+ mMessenger = new Messenger(this);
+ mSerialNumber = ConnectivityManager.from(mContext).registerNetworkFactory(mMessenger,
+ LOG_TAG);
+ }
+ }
+
+ public void unregister() {
+ if (DBG) log("Unregistering NetworkFactory");
+ if (mMessenger != null) {
+ ConnectivityManager.from(mContext).unregisterNetworkFactory(mMessenger);
+ mMessenger = null;
+ }
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
+ if (mAsyncChannel != null) {
+ log("Received new connection while already connected!");
+ break;
+ }
+ if (VDBG) log("NetworkFactory fully connected");
+ AsyncChannel ac = new AsyncChannel();
+ ac.connected(null, this, msg.replyTo);
+ ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+ AsyncChannel.STATUS_SUCCESSFUL);
+ mAsyncChannel = ac;
+ for (Message m : mPreConnectedQueue) {
+ ac.sendMessage(m);
+ }
+ mPreConnectedQueue.clear();
+ break;
+ }
+ case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
+ if (VDBG) log("CMD_CHANNEL_DISCONNECT");
+ if (mAsyncChannel != null) {
+ mAsyncChannel.disconnect();
+ mAsyncChannel = null;
+ }
+ break;
+ }
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
+ if (DBG) log("NetworkFactory channel lost");
+ mAsyncChannel = null;
+ break;
+ }
+ case CMD_REQUEST_NETWORK: {
+ handleAddRequest((NetworkRequest) msg.obj, msg.arg1, msg.arg2);
+ break;
+ }
+ case CMD_CANCEL_REQUEST: {
+ handleRemoveRequest((NetworkRequest) msg.obj);
+ break;
+ }
+ case CMD_SET_SCORE: {
+ handleSetScore(msg.arg1);
+ break;
+ }
+ case CMD_SET_FILTER: {
+ handleSetFilter((NetworkCapabilities) msg.obj);
+ break;
+ }
+ }
+ }
+
+ private class NetworkRequestInfo {
+ public final NetworkRequest request;
+ public int score;
+ public boolean requested; // do we have a request outstanding, limited by score
+ public int factorySerialNumber;
+
+ NetworkRequestInfo(NetworkRequest request, int score, int factorySerialNumber) {
+ this.request = request;
+ this.score = score;
+ this.requested = false;
+ this.factorySerialNumber = factorySerialNumber;
+ }
+
+ @Override
+ public String toString() {
+ return "{" + request + ", score=" + score + ", requested=" + requested + "}";
+ }
+ }
+
+ /**
+ * Add a NetworkRequest that the bearer may want to attempt to satisfy.
+ * @see #CMD_REQUEST_NETWORK
+ *
+ * @param request the request to handle.
+ * @param score the score of the NetworkAgent currently satisfying this request.
+ * @param servingFactorySerialNumber the serial number of the NetworkFactory that
+ * created the NetworkAgent currently satisfying this request.
+ */
+ // TODO : remove this method. It is a stopgap measure to help sheperding a number
+ // of dependent changes that would conflict throughout the automerger graph. Having this
+ // temporarily helps with the process of going through with all these dependent changes across
+ // the entire tree.
+ @VisibleForTesting
+ protected void handleAddRequest(NetworkRequest request, int score) {
+ handleAddRequest(request, score, SerialNumber.NONE);
+ }
+
+ /**
+ * Add a NetworkRequest that the bearer may want to attempt to satisfy.
+ * @see #CMD_REQUEST_NETWORK
+ *
+ * @param request the request to handle.
+ * @param score the score of the NetworkAgent currently satisfying this request.
+ * @param servingFactorySerialNumber the serial number of the NetworkFactory that
+ * created the NetworkAgent currently satisfying this request.
+ */
+ @VisibleForTesting
+ protected void handleAddRequest(NetworkRequest request, int score,
+ int servingFactorySerialNumber) {
+ NetworkRequestInfo n = mNetworkRequests.get(request.requestId);
+ if (n == null) {
+ if (DBG) {
+ log("got request " + request + " with score " + score
+ + " and serial " + servingFactorySerialNumber);
+ }
+ n = new NetworkRequestInfo(request, score, servingFactorySerialNumber);
+ mNetworkRequests.put(n.request.requestId, n);
+ } else {
+ if (VDBG) {
+ log("new score " + score + " for exisiting request " + request
+ + " with serial " + servingFactorySerialNumber);
+ }
+ n.score = score;
+ n.factorySerialNumber = servingFactorySerialNumber;
+ }
+ if (VDBG) log(" my score=" + mScore + ", my filter=" + mCapabilityFilter);
+
+ evalRequest(n);
+ }
+
+ @VisibleForTesting
+ protected void handleRemoveRequest(NetworkRequest request) {
+ NetworkRequestInfo n = mNetworkRequests.get(request.requestId);
+ if (n != null) {
+ mNetworkRequests.remove(request.requestId);
+ if (n.requested) releaseNetworkFor(n.request);
+ }
+ }
+
+ private void handleSetScore(int score) {
+ mScore = score;
+ evalRequests();
+ }
+
+ private void handleSetFilter(NetworkCapabilities netCap) {
+ mCapabilityFilter = netCap;
+ evalRequests();
+ }
+
+ /**
+ * Overridable function to provide complex filtering.
+ * Called for every request every time a new NetworkRequest is seen
+ * and whenever the filterScore or filterNetworkCapabilities change.
+ *
+ * acceptRequest can be overridden to provide complex filter behavior
+ * for the incoming requests
+ *
+ * For output, this class will call {@link #needNetworkFor} and
+ * {@link #releaseNetworkFor} for every request that passes the filters.
+ * If you don't need to see every request, you can leave the base
+ * implementations of those two functions and instead override
+ * {@link #startNetwork} and {@link #stopNetwork}.
+ *
+ * If you want to see every score fluctuation on every request, set
+ * your score filter to a very high number and watch {@link #needNetworkFor}.
+ *
+ * @return {@code true} to accept the request.
+ */
+ public boolean acceptRequest(NetworkRequest request, int score) {
+ return true;
+ }
+
+ private void evalRequest(NetworkRequestInfo n) {
+ if (VDBG) {
+ log("evalRequest");
+ log(" n.requests = " + n.requested);
+ log(" n.score = " + n.score);
+ log(" mScore = " + mScore);
+ log(" n.factorySerialNumber = " + n.factorySerialNumber);
+ log(" mSerialNumber = " + mSerialNumber);
+ }
+ if (shouldNeedNetworkFor(n)) {
+ if (VDBG) log(" needNetworkFor");
+ needNetworkFor(n.request, n.score);
+ n.requested = true;
+ } else if (shouldReleaseNetworkFor(n)) {
+ if (VDBG) log(" releaseNetworkFor");
+ releaseNetworkFor(n.request);
+ n.requested = false;
+ } else {
+ if (VDBG) log(" done");
+ }
+ }
+
+ private boolean shouldNeedNetworkFor(NetworkRequestInfo n) {
+ // If this request is already tracked, it doesn't qualify for need
+ return !n.requested
+ // If the score of this request is higher or equal to that of this factory and some
+ // other factory is responsible for it, then this factory should not track the request
+ // because it has no hope of satisfying it.
+ && (n.score < mScore || n.factorySerialNumber == mSerialNumber)
+ // If this factory can't satisfy the capability needs of this request, then it
+ // should not be tracked.
+ && n.request.networkCapabilities.satisfiedByNetworkCapabilities(mCapabilityFilter)
+ // Finally if the concrete implementation of the factory rejects the request, then
+ // don't track it.
+ && acceptRequest(n.request, n.score);
+ }
+
+ private boolean shouldReleaseNetworkFor(NetworkRequestInfo n) {
+ // Don't release a request that's not tracked.
+ return n.requested
+ // The request should be released if it can't be satisfied by this factory. That
+ // means either of the following conditions are met :
+ // - Its score is too high to be satisfied by this factory and it's not already
+ // assigned to the factory
+ // - This factory can't satisfy the capability needs of the request
+ // - The concrete implementation of the factory rejects the request
+ && ((n.score > mScore && n.factorySerialNumber != mSerialNumber)
+ || !n.request.networkCapabilities.satisfiedByNetworkCapabilities(
+ mCapabilityFilter)
+ || !acceptRequest(n.request, n.score));
+ }
+
+ private void evalRequests() {
+ for (int i = 0; i < mNetworkRequests.size(); i++) {
+ NetworkRequestInfo n = mNetworkRequests.valueAt(i);
+ evalRequest(n);
+ }
+ }
+
+ /**
+ * Post a command, on this NetworkFactory Handler, to re-evaluate all
+ * oustanding requests. Can be called from a factory implementation.
+ */
+ protected void reevaluateAllRequests() {
+ post(() -> {
+ evalRequests();
+ });
+ }
+
+ /**
+ * Can be called by a factory to release a request as unfulfillable: the request will be
+ * removed, and the caller will get a
+ * {@link ConnectivityManager.NetworkCallback#onUnavailable()} callback after this function
+ * returns.
+ *
+ * Note: this should only be called by factory which KNOWS that it is the ONLY factory which
+ * is able to fulfill this request!
+ */
+ protected void releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest r) {
+ post(() -> {
+ if (DBG) log("releaseRequestAsUnfulfillableByAnyFactory: " + r);
+ Message msg = obtainMessage(EVENT_UNFULFILLABLE_REQUEST, r);
+ if (mAsyncChannel != null) {
+ mAsyncChannel.sendMessage(msg);
+ } else {
+ mPreConnectedQueue.add(msg);
+ }
+ });
+ }
+
+ // override to do simple mode (request independent)
+ protected void startNetwork() { }
+ protected void stopNetwork() { }
+
+ // override to do fancier stuff
+ protected void needNetworkFor(NetworkRequest networkRequest, int score) {
+ if (++mRefCount == 1) startNetwork();
+ }
+
+ protected void releaseNetworkFor(NetworkRequest networkRequest) {
+ if (--mRefCount == 0) stopNetwork();
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public void setScoreFilter(int score) {
+ sendMessage(obtainMessage(CMD_SET_SCORE, score, 0));
+ }
+
+ public void setCapabilityFilter(NetworkCapabilities netCap) {
+ sendMessage(obtainMessage(CMD_SET_FILTER, new NetworkCapabilities(netCap)));
+ }
+
+ @VisibleForTesting
+ protected int getRequestCount() {
+ return mNetworkRequests.size();
+ }
+
+ public int getSerialNumber() {
+ return mSerialNumber;
+ }
+
+ protected void log(String s) {
+ Log.d(LOG_TAG, s);
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
+ pw.println(toString());
+ pw.increaseIndent();
+ for (int i = 0; i < mNetworkRequests.size(); i++) {
+ pw.println(mNetworkRequests.valueAt(i));
+ }
+ pw.decreaseIndent();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("{").append(LOG_TAG).append(" - mSerialNumber=")
+ .append(mSerialNumber).append(", ScoreFilter=")
+ .append(mScore).append(", Filter=").append(mCapabilityFilter).append(", requests=")
+ .append(mNetworkRequests.size()).append(", refCount=").append(mRefCount)
+ .append("}");
+ return sb.toString();
+ }
+}
diff --git a/android/net/NetworkIdentity.java b/android/net/NetworkIdentity.java
new file mode 100644
index 0000000..ce2de85
--- /dev/null
+++ b/android/net/NetworkIdentity.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2011 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;
+
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.ConnectivityManager.getNetworkTypeName;
+import static android.net.ConnectivityManager.isNetworkTypeMobile;
+
+import android.content.Context;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.service.NetworkIdentityProto;
+import android.telephony.TelephonyManager;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+import java.util.Objects;
+
+/**
+ * Network definition that includes strong identity. Analogous to combining
+ * {@link NetworkInfo} and an IMSI.
+ *
+ * @hide
+ */
+public class NetworkIdentity implements Comparable<NetworkIdentity> {
+ private static final String TAG = "NetworkIdentity";
+
+ /**
+ * When enabled, combine all {@link #mSubType} together under
+ * {@link #SUBTYPE_COMBINED}.
+ *
+ * @deprecated we no longer offer to collect statistics on a per-subtype
+ * basis; this is always disabled.
+ */
+ @Deprecated
+ public static final boolean COMBINE_SUBTYPE_ENABLED = true;
+
+ public static final int SUBTYPE_COMBINED = -1;
+
+ final int mType;
+ final int mSubType;
+ final String mSubscriberId;
+ final String mNetworkId;
+ final boolean mRoaming;
+ final boolean mMetered;
+ final boolean mDefaultNetwork;
+
+ public NetworkIdentity(
+ int type, int subType, String subscriberId, String networkId, boolean roaming,
+ boolean metered, boolean defaultNetwork) {
+ mType = type;
+ mSubType = COMBINE_SUBTYPE_ENABLED ? SUBTYPE_COMBINED : subType;
+ mSubscriberId = subscriberId;
+ mNetworkId = networkId;
+ mRoaming = roaming;
+ mMetered = metered;
+ mDefaultNetwork = defaultNetwork;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mType, mSubType, mSubscriberId, mNetworkId, mRoaming, mMetered,
+ mDefaultNetwork);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof NetworkIdentity) {
+ final NetworkIdentity ident = (NetworkIdentity) obj;
+ return mType == ident.mType && mSubType == ident.mSubType && mRoaming == ident.mRoaming
+ && Objects.equals(mSubscriberId, ident.mSubscriberId)
+ && Objects.equals(mNetworkId, ident.mNetworkId)
+ && mMetered == ident.mMetered
+ && mDefaultNetwork == ident.mDefaultNetwork;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder("{");
+ builder.append("type=").append(getNetworkTypeName(mType));
+ builder.append(", subType=");
+ if (COMBINE_SUBTYPE_ENABLED) {
+ builder.append("COMBINED");
+ } else if (ConnectivityManager.isNetworkTypeMobile(mType)) {
+ builder.append(TelephonyManager.getNetworkTypeName(mSubType));
+ } else {
+ builder.append(mSubType);
+ }
+ if (mSubscriberId != null) {
+ builder.append(", subscriberId=").append(scrubSubscriberId(mSubscriberId));
+ }
+ if (mNetworkId != null) {
+ builder.append(", networkId=").append(mNetworkId);
+ }
+ if (mRoaming) {
+ builder.append(", ROAMING");
+ }
+ builder.append(", metered=").append(mMetered);
+ builder.append(", defaultNetwork=").append(mDefaultNetwork);
+ return builder.append("}").toString();
+ }
+
+ public void writeToProto(ProtoOutputStream proto, long tag) {
+ final long start = proto.start(tag);
+
+ proto.write(NetworkIdentityProto.TYPE, mType);
+
+ // Not dumping mSubType, subtypes are no longer supported.
+
+ if (mSubscriberId != null) {
+ proto.write(NetworkIdentityProto.SUBSCRIBER_ID, scrubSubscriberId(mSubscriberId));
+ }
+ proto.write(NetworkIdentityProto.NETWORK_ID, mNetworkId);
+ proto.write(NetworkIdentityProto.ROAMING, mRoaming);
+ proto.write(NetworkIdentityProto.METERED, mMetered);
+ proto.write(NetworkIdentityProto.DEFAULT_NETWORK, mDefaultNetwork);
+
+ proto.end(start);
+ }
+
+ public int getType() {
+ return mType;
+ }
+
+ public int getSubType() {
+ return mSubType;
+ }
+
+ public String getSubscriberId() {
+ return mSubscriberId;
+ }
+
+ public String getNetworkId() {
+ return mNetworkId;
+ }
+
+ public boolean getRoaming() {
+ return mRoaming;
+ }
+
+ public boolean getMetered() {
+ return mMetered;
+ }
+
+ public boolean getDefaultNetwork() {
+ return mDefaultNetwork;
+ }
+
+ /**
+ * Scrub given IMSI on production builds.
+ */
+ public static String scrubSubscriberId(String subscriberId) {
+ if (Build.IS_ENG) {
+ return subscriberId;
+ } else if (subscriberId != null) {
+ // TODO: parse this as MCC+MNC instead of hard-coding
+ return subscriberId.substring(0, Math.min(6, subscriberId.length())) + "...";
+ } else {
+ return "null";
+ }
+ }
+
+ /**
+ * Scrub given IMSI on production builds.
+ */
+ public static String[] scrubSubscriberId(String[] subscriberId) {
+ if (subscriberId == null) return null;
+ final String[] res = new String[subscriberId.length];
+ for (int i = 0; i < res.length; i++) {
+ res[i] = NetworkIdentity.scrubSubscriberId(subscriberId[i]);
+ }
+ return res;
+ }
+
+ /**
+ * Build a {@link NetworkIdentity} from the given {@link NetworkState},
+ * assuming that any mobile networks are using the current IMSI.
+ */
+ public static NetworkIdentity buildNetworkIdentity(Context context, NetworkState state,
+ boolean defaultNetwork) {
+ final int type = state.networkInfo.getType();
+ final int subType = state.networkInfo.getSubtype();
+
+ String subscriberId = null;
+ String networkId = null;
+ boolean roaming = !state.networkCapabilities.hasCapability(
+ NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
+ boolean metered = !state.networkCapabilities.hasCapability(
+ NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+
+ if (isNetworkTypeMobile(type)) {
+ if (state.subscriberId == null) {
+ if (state.networkInfo.getState() != NetworkInfo.State.DISCONNECTED &&
+ state.networkInfo.getState() != NetworkInfo.State.UNKNOWN) {
+ Slog.w(TAG, "Active mobile network without subscriber! ni = "
+ + state.networkInfo);
+ }
+ }
+
+ subscriberId = state.subscriberId;
+
+ } else if (type == TYPE_WIFI) {
+ if (state.networkId != null) {
+ networkId = state.networkId;
+ } else {
+ final WifiManager wifi = (WifiManager) context.getSystemService(
+ Context.WIFI_SERVICE);
+ final WifiInfo info = wifi.getConnectionInfo();
+ networkId = info != null ? info.getSSID() : null;
+ }
+ }
+
+ return new NetworkIdentity(type, subType, subscriberId, networkId, roaming, metered,
+ defaultNetwork);
+ }
+
+ @Override
+ public int compareTo(NetworkIdentity another) {
+ int res = Integer.compare(mType, another.mType);
+ if (res == 0) {
+ res = Integer.compare(mSubType, another.mSubType);
+ }
+ if (res == 0 && mSubscriberId != null && another.mSubscriberId != null) {
+ res = mSubscriberId.compareTo(another.mSubscriberId);
+ }
+ if (res == 0 && mNetworkId != null && another.mNetworkId != null) {
+ res = mNetworkId.compareTo(another.mNetworkId);
+ }
+ if (res == 0) {
+ res = Boolean.compare(mRoaming, another.mRoaming);
+ }
+ if (res == 0) {
+ res = Boolean.compare(mMetered, another.mMetered);
+ }
+ if (res == 0) {
+ res = Boolean.compare(mDefaultNetwork, another.mDefaultNetwork);
+ }
+ return res;
+ }
+}
diff --git a/android/net/NetworkInfo.java b/android/net/NetworkInfo.java
new file mode 100644
index 0000000..92f105f
--- /dev/null
+++ b/android/net/NetworkInfo.java
@@ -0,0 +1,586 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.EnumMap;
+
+/**
+ * Describes the status of a network interface.
+ * <p>Use {@link ConnectivityManager#getActiveNetworkInfo()} to get an instance that represents
+ * the current network connection.
+ *
+ * @deprecated Callers should instead use the {@link ConnectivityManager.NetworkCallback} API to
+ * learn about connectivity changes, or switch to use
+ * {@link ConnectivityManager#getNetworkCapabilities} or
+ * {@link ConnectivityManager#getLinkProperties} to get information synchronously. Keep
+ * in mind that while callbacks are guaranteed to be called for every event in order,
+ * synchronous calls have no such constraints, and as such it is unadvisable to use the
+ * synchronous methods inside the callbacks as they will often not offer a view of
+ * networking that is consistent (that is: they may return a past or a future state with
+ * respect to the event being processed by the callback). Instead, callers are advised
+ * to only use the arguments of the callbacks, possibly memorizing the specific bits of
+ * information they need to keep from one callback to another.
+ */
+@Deprecated
+public class NetworkInfo implements Parcelable {
+
+ /**
+ * Coarse-grained network state. This is probably what most applications should
+ * use, rather than {@link android.net.NetworkInfo.DetailedState DetailedState}.
+ * The mapping between the two is as follows:
+ * <br/><br/>
+ * <table>
+ * <tr><td><b>Detailed state</b></td><td><b>Coarse-grained state</b></td></tr>
+ * <tr><td><code>IDLE</code></td><td><code>DISCONNECTED</code></td></tr>
+ * <tr><td><code>SCANNING</code></td><td><code>DISCONNECTED</code></td></tr>
+ * <tr><td><code>CONNECTING</code></td><td><code>CONNECTING</code></td></tr>
+ * <tr><td><code>AUTHENTICATING</code></td><td><code>CONNECTING</code></td></tr>
+ * <tr><td><code>OBTAINING_IPADDR</code></td><td><code>CONNECTING</code></td></tr>
+ * <tr><td><code>VERIFYING_POOR_LINK</code></td><td><code>CONNECTING</code></td></tr>
+ * <tr><td><code>CAPTIVE_PORTAL_CHECK</code></td><td><code>CONNECTING</code></td></tr>
+ * <tr><td><code>CONNECTED</code></td><td><code>CONNECTED</code></td></tr>
+ * <tr><td><code>SUSPENDED</code></td><td><code>SUSPENDED</code></td></tr>
+ * <tr><td><code>DISCONNECTING</code></td><td><code>DISCONNECTING</code></td></tr>
+ * <tr><td><code>DISCONNECTED</code></td><td><code>DISCONNECTED</code></td></tr>
+ * <tr><td><code>FAILED</code></td><td><code>DISCONNECTED</code></td></tr>
+ * <tr><td><code>BLOCKED</code></td><td><code>DISCONNECTED</code></td></tr>
+ * </table>
+ *
+ * @deprecated See {@link NetworkInfo}.
+ */
+ @Deprecated
+ public enum State {
+ CONNECTING, CONNECTED, SUSPENDED, DISCONNECTING, DISCONNECTED, UNKNOWN
+ }
+
+ /**
+ * The fine-grained state of a network connection. This level of detail
+ * is probably of interest to few applications. Most should use
+ * {@link android.net.NetworkInfo.State State} instead.
+ *
+ * @deprecated See {@link NetworkInfo}.
+ */
+ @Deprecated
+ public enum DetailedState {
+ /** Ready to start data connection setup. */
+ IDLE,
+ /** Searching for an available access point. */
+ SCANNING,
+ /** Currently setting up data connection. */
+ CONNECTING,
+ /** Network link established, performing authentication. */
+ AUTHENTICATING,
+ /** Awaiting response from DHCP server in order to assign IP address information. */
+ OBTAINING_IPADDR,
+ /** IP traffic should be available. */
+ CONNECTED,
+ /** IP traffic is suspended */
+ SUSPENDED,
+ /** Currently tearing down data connection. */
+ DISCONNECTING,
+ /** IP traffic not available. */
+ DISCONNECTED,
+ /** Attempt to connect failed. */
+ FAILED,
+ /** Access to this network is blocked. */
+ BLOCKED,
+ /** Link has poor connectivity. */
+ VERIFYING_POOR_LINK,
+ /** Checking if network is a captive portal */
+ CAPTIVE_PORTAL_CHECK
+ }
+
+ /**
+ * This is the map described in the Javadoc comment above. The positions
+ * of the elements of the array must correspond to the ordinal values
+ * of <code>DetailedState</code>.
+ */
+ private static final EnumMap<DetailedState, State> stateMap =
+ new EnumMap<DetailedState, State>(DetailedState.class);
+
+ static {
+ stateMap.put(DetailedState.IDLE, State.DISCONNECTED);
+ stateMap.put(DetailedState.SCANNING, State.DISCONNECTED);
+ stateMap.put(DetailedState.CONNECTING, State.CONNECTING);
+ stateMap.put(DetailedState.AUTHENTICATING, State.CONNECTING);
+ stateMap.put(DetailedState.OBTAINING_IPADDR, State.CONNECTING);
+ stateMap.put(DetailedState.VERIFYING_POOR_LINK, State.CONNECTING);
+ stateMap.put(DetailedState.CAPTIVE_PORTAL_CHECK, State.CONNECTING);
+ stateMap.put(DetailedState.CONNECTED, State.CONNECTED);
+ stateMap.put(DetailedState.SUSPENDED, State.SUSPENDED);
+ stateMap.put(DetailedState.DISCONNECTING, State.DISCONNECTING);
+ stateMap.put(DetailedState.DISCONNECTED, State.DISCONNECTED);
+ stateMap.put(DetailedState.FAILED, State.DISCONNECTED);
+ stateMap.put(DetailedState.BLOCKED, State.DISCONNECTED);
+ }
+
+ private int mNetworkType;
+ private int mSubtype;
+ private String mTypeName;
+ private String mSubtypeName;
+ @NonNull
+ private State mState;
+ @NonNull
+ private DetailedState mDetailedState;
+ private String mReason;
+ private String mExtraInfo;
+ private boolean mIsFailover;
+ private boolean mIsAvailable;
+ private boolean mIsRoaming;
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public NetworkInfo(int type, int subtype, String typeName, String subtypeName) {
+ if (!ConnectivityManager.isNetworkTypeValid(type)
+ && type != ConnectivityManager.TYPE_NONE) {
+ throw new IllegalArgumentException("Invalid network type: " + type);
+ }
+ mNetworkType = type;
+ mSubtype = subtype;
+ mTypeName = typeName;
+ mSubtypeName = subtypeName;
+ setDetailedState(DetailedState.IDLE, null, null);
+ mState = State.UNKNOWN;
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public NetworkInfo(NetworkInfo source) {
+ if (source != null) {
+ synchronized (source) {
+ mNetworkType = source.mNetworkType;
+ mSubtype = source.mSubtype;
+ mTypeName = source.mTypeName;
+ mSubtypeName = source.mSubtypeName;
+ mState = source.mState;
+ mDetailedState = source.mDetailedState;
+ mReason = source.mReason;
+ mExtraInfo = source.mExtraInfo;
+ mIsFailover = source.mIsFailover;
+ mIsAvailable = source.mIsAvailable;
+ mIsRoaming = source.mIsRoaming;
+ }
+ }
+ }
+
+ /**
+ * Reports the type of network to which the
+ * info in this {@code NetworkInfo} pertains.
+ * @return one of {@link ConnectivityManager#TYPE_MOBILE}, {@link
+ * ConnectivityManager#TYPE_WIFI}, {@link ConnectivityManager#TYPE_WIMAX}, {@link
+ * ConnectivityManager#TYPE_ETHERNET}, {@link ConnectivityManager#TYPE_BLUETOOTH}, or other
+ * types defined by {@link ConnectivityManager}.
+ * @deprecated Callers should switch to checking {@link NetworkCapabilities#hasTransport}
+ * instead with one of the NetworkCapabilities#TRANSPORT_* constants :
+ * {@link #getType} and {@link #getTypeName} cannot account for networks using
+ * multiple transports. Note that generally apps should not care about transport;
+ * {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED} and
+ * {@link NetworkCapabilities#getLinkDownstreamBandwidthKbps} are calls that
+ * apps concerned with meteredness or bandwidth should be looking at, as they
+ * offer this information with much better accuracy.
+ */
+ @Deprecated
+ public int getType() {
+ synchronized (this) {
+ return mNetworkType;
+ }
+ }
+
+ /**
+ * @deprecated Use {@link NetworkCapabilities} instead
+ * @hide
+ */
+ @Deprecated
+ public void setType(int type) {
+ synchronized (this) {
+ mNetworkType = type;
+ }
+ }
+
+ /**
+ * Return a network-type-specific integer describing the subtype
+ * of the network.
+ * @return the network subtype
+ * @deprecated Use {@link android.telephony.TelephonyManager#getDataNetworkType} instead.
+ */
+ @Deprecated
+ public int getSubtype() {
+ synchronized (this) {
+ return mSubtype;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setSubtype(int subtype, String subtypeName) {
+ synchronized (this) {
+ mSubtype = subtype;
+ mSubtypeName = subtypeName;
+ }
+ }
+
+ /**
+ * Return a human-readable name describe the type of the network,
+ * for example "WIFI" or "MOBILE".
+ * @return the name of the network type
+ * @deprecated Callers should switch to checking {@link NetworkCapabilities#hasTransport}
+ * instead with one of the NetworkCapabilities#TRANSPORT_* constants :
+ * {@link #getType} and {@link #getTypeName} cannot account for networks using
+ * multiple transports. Note that generally apps should not care about transport;
+ * {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED} and
+ * {@link NetworkCapabilities#getLinkDownstreamBandwidthKbps} are calls that
+ * apps concerned with meteredness or bandwidth should be looking at, as they
+ * offer this information with much better accuracy.
+ */
+ @Deprecated
+ public String getTypeName() {
+ synchronized (this) {
+ return mTypeName;
+ }
+ }
+
+ /**
+ * Return a human-readable name describing the subtype of the network.
+ * @return the name of the network subtype
+ * @deprecated Use {@link android.telephony.TelephonyManager#getDataNetworkType} instead.
+ */
+ @Deprecated
+ public String getSubtypeName() {
+ synchronized (this) {
+ return mSubtypeName;
+ }
+ }
+
+ /**
+ * Indicates whether network connectivity exists or is in the process
+ * of being established. This is good for applications that need to
+ * do anything related to the network other than read or write data.
+ * For the latter, call {@link #isConnected()} instead, which guarantees
+ * that the network is fully usable.
+ * @return {@code true} if network connectivity exists or is in the process
+ * of being established, {@code false} otherwise.
+ * @deprecated Apps should instead use the
+ * {@link android.net.ConnectivityManager.NetworkCallback} API to
+ * learn about connectivity changes.
+ * {@link ConnectivityManager#registerDefaultNetworkCallback} and
+ * {@link ConnectivityManager#registerNetworkCallback}. These will
+ * give a more accurate picture of the connectivity state of
+ * the device and let apps react more easily and quickly to changes.
+ */
+ @Deprecated
+ public boolean isConnectedOrConnecting() {
+ synchronized (this) {
+ return mState == State.CONNECTED || mState == State.CONNECTING;
+ }
+ }
+
+ /**
+ * Indicates whether network connectivity exists and it is possible to establish
+ * connections and pass data.
+ * <p>Always call this before attempting to perform data transactions.
+ * @return {@code true} if network connectivity exists, {@code false} otherwise.
+ * @deprecated Apps should instead use the
+ * {@link android.net.ConnectivityManager.NetworkCallback} API to
+ * learn about connectivity changes. See
+ * {@link ConnectivityManager#registerDefaultNetworkCallback} and
+ * {@link ConnectivityManager#registerNetworkCallback}. These will
+ * give a more accurate picture of the connectivity state of
+ * the device and let apps react more easily and quickly to changes.
+ */
+ @Deprecated
+ public boolean isConnected() {
+ synchronized (this) {
+ return mState == State.CONNECTED;
+ }
+ }
+
+ /**
+ * Indicates whether network connectivity is possible. A network is unavailable
+ * when a persistent or semi-persistent condition prevents the possibility
+ * of connecting to that network. Examples include
+ * <ul>
+ * <li>The device is out of the coverage area for any network of this type.</li>
+ * <li>The device is on a network other than the home network (i.e., roaming), and
+ * data roaming has been disabled.</li>
+ * <li>The device's radio is turned off, e.g., because airplane mode is enabled.</li>
+ * </ul>
+ * Since Android L, this always returns {@code true}, because the system only
+ * returns info for available networks.
+ * @return {@code true} if the network is available, {@code false} otherwise
+ * @deprecated Apps should instead use the
+ * {@link android.net.ConnectivityManager.NetworkCallback} API to
+ * learn about connectivity changes.
+ * {@link ConnectivityManager#registerDefaultNetworkCallback} and
+ * {@link ConnectivityManager#registerNetworkCallback}. These will
+ * give a more accurate picture of the connectivity state of
+ * the device and let apps react more easily and quickly to changes.
+ */
+ @Deprecated
+ public boolean isAvailable() {
+ synchronized (this) {
+ return mIsAvailable;
+ }
+ }
+
+ /**
+ * Sets if the network is available, ie, if the connectivity is possible.
+ * @param isAvailable the new availability value.
+ * @deprecated Use {@link NetworkCapabilities} instead
+ *
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public void setIsAvailable(boolean isAvailable) {
+ synchronized (this) {
+ mIsAvailable = isAvailable;
+ }
+ }
+
+ /**
+ * Indicates whether the current attempt to connect to the network
+ * resulted from the ConnectivityManager trying to fail over to this
+ * network following a disconnect from another network.
+ * @return {@code true} if this is a failover attempt, {@code false}
+ * otherwise.
+ * @deprecated This field is not populated in recent Android releases,
+ * and does not make a lot of sense in a multi-network world.
+ */
+ @Deprecated
+ public boolean isFailover() {
+ synchronized (this) {
+ return mIsFailover;
+ }
+ }
+
+ /**
+ * Set the failover boolean.
+ * @param isFailover {@code true} to mark the current connection attempt
+ * as a failover.
+ * @deprecated This hasn't been set in any recent Android release.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public void setFailover(boolean isFailover) {
+ synchronized (this) {
+ mIsFailover = isFailover;
+ }
+ }
+
+ /**
+ * Indicates whether the device is currently roaming on this network. When
+ * {@code true}, it suggests that use of data on this network may incur
+ * extra costs.
+ *
+ * @return {@code true} if roaming is in effect, {@code false} otherwise.
+ * @deprecated Callers should switch to checking
+ * {@link NetworkCapabilities#NET_CAPABILITY_NOT_ROAMING}
+ * instead, since that handles more complex situations, such as
+ * VPNs.
+ */
+ @Deprecated
+ public boolean isRoaming() {
+ synchronized (this) {
+ return mIsRoaming;
+ }
+ }
+
+ /**
+ * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_NOT_ROAMING} instead.
+ * {@hide}
+ */
+ @VisibleForTesting
+ @Deprecated
+ @UnsupportedAppUsage
+ public void setRoaming(boolean isRoaming) {
+ synchronized (this) {
+ mIsRoaming = isRoaming;
+ }
+ }
+
+ /**
+ * Reports the current coarse-grained state of the network.
+ * @return the coarse-grained state
+ * @deprecated Apps should instead use the
+ * {@link android.net.ConnectivityManager.NetworkCallback} API to
+ * learn about connectivity changes.
+ * {@link ConnectivityManager#registerDefaultNetworkCallback} and
+ * {@link ConnectivityManager#registerNetworkCallback}. These will
+ * give a more accurate picture of the connectivity state of
+ * the device and let apps react more easily and quickly to changes.
+ */
+ @Deprecated
+ public State getState() {
+ synchronized (this) {
+ return mState;
+ }
+ }
+
+ /**
+ * Reports the current fine-grained state of the network.
+ * @return the fine-grained state
+ * @deprecated Apps should instead use the
+ * {@link android.net.ConnectivityManager.NetworkCallback} API to
+ * learn about connectivity changes. See
+ * {@link ConnectivityManager#registerDefaultNetworkCallback} and
+ * {@link ConnectivityManager#registerNetworkCallback}. These will
+ * give a more accurate picture of the connectivity state of
+ * the device and let apps react more easily and quickly to changes.
+ */
+ @Deprecated
+ public @NonNull DetailedState getDetailedState() {
+ synchronized (this) {
+ return mDetailedState;
+ }
+ }
+
+ /**
+ * Sets the fine-grained state of the network.
+ * @param detailedState the {@link DetailedState}.
+ * @param reason a {@code String} indicating the reason for the state change,
+ * if one was supplied. May be {@code null}.
+ * @param extraInfo an optional {@code String} providing addditional network state
+ * information passed up from the lower networking layers.
+ * @deprecated Use {@link NetworkCapabilities} instead.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public void setDetailedState(DetailedState detailedState, String reason, String extraInfo) {
+ synchronized (this) {
+ this.mDetailedState = detailedState;
+ this.mState = stateMap.get(detailedState);
+ this.mReason = reason;
+ this.mExtraInfo = extraInfo;
+ }
+ }
+
+ /**
+ * Set the extraInfo field.
+ * @param extraInfo an optional {@code String} providing addditional network state
+ * information passed up from the lower networking layers.
+ * @deprecated See {@link NetworkInfo#getExtraInfo}.
+ * @hide
+ */
+ @Deprecated
+ public void setExtraInfo(String extraInfo) {
+ synchronized (this) {
+ this.mExtraInfo = extraInfo;
+ }
+ }
+
+ /**
+ * Report the reason an attempt to establish connectivity failed,
+ * if one is available.
+ * @return the reason for failure, or null if not available
+ * @deprecated This method does not have a consistent contract that could make it useful
+ * to callers.
+ */
+ public String getReason() {
+ synchronized (this) {
+ return mReason;
+ }
+ }
+
+ /**
+ * Report the extra information about the network state, if any was
+ * provided by the lower networking layers.
+ * @return the extra information, or null if not available
+ * @deprecated Use other services e.g. WifiManager to get additional information passed up from
+ * the lower networking layers.
+ */
+ @Deprecated
+ public String getExtraInfo() {
+ synchronized (this) {
+ return mExtraInfo;
+ }
+ }
+
+ @Override
+ public String toString() {
+ synchronized (this) {
+ StringBuilder builder = new StringBuilder("[");
+ builder.append("type: ").append(getTypeName()).append("[").append(getSubtypeName()).
+ append("], state: ").append(mState).append("/").append(mDetailedState).
+ append(", reason: ").append(mReason == null ? "(unspecified)" : mReason).
+ append(", extra: ").append(mExtraInfo == null ? "(none)" : mExtraInfo).
+ append(", failover: ").append(mIsFailover).
+ append(", available: ").append(mIsAvailable).
+ append(", roaming: ").append(mIsRoaming).
+ append("]");
+ return builder.toString();
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ synchronized (this) {
+ dest.writeInt(mNetworkType);
+ dest.writeInt(mSubtype);
+ dest.writeString(mTypeName);
+ dest.writeString(mSubtypeName);
+ dest.writeString(mState.name());
+ dest.writeString(mDetailedState.name());
+ dest.writeInt(mIsFailover ? 1 : 0);
+ dest.writeInt(mIsAvailable ? 1 : 0);
+ dest.writeInt(mIsRoaming ? 1 : 0);
+ dest.writeString(mReason);
+ dest.writeString(mExtraInfo);
+ }
+ }
+
+ public static final @android.annotation.NonNull Creator<NetworkInfo> CREATOR = new Creator<NetworkInfo>() {
+ @Override
+ public NetworkInfo createFromParcel(Parcel in) {
+ int netType = in.readInt();
+ int subtype = in.readInt();
+ String typeName = in.readString();
+ String subtypeName = in.readString();
+ NetworkInfo netInfo = new NetworkInfo(netType, subtype, typeName, subtypeName);
+ netInfo.mState = State.valueOf(in.readString());
+ netInfo.mDetailedState = DetailedState.valueOf(in.readString());
+ netInfo.mIsFailover = in.readInt() != 0;
+ netInfo.mIsAvailable = in.readInt() != 0;
+ netInfo.mIsRoaming = in.readInt() != 0;
+ netInfo.mReason = in.readString();
+ netInfo.mExtraInfo = in.readString();
+ return netInfo;
+ }
+
+ @Override
+ public NetworkInfo[] newArray(int size) {
+ return new NetworkInfo[size];
+ }
+ };
+}
diff --git a/android/net/NetworkKey.java b/android/net/NetworkKey.java
new file mode 100644
index 0000000..0a9a3c8
--- /dev/null
+++ b/android/net/NetworkKey.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2014 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;
+
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiSsid;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.Objects;
+
+/**
+ * Information which identifies a specific network.
+ *
+ * @hide
+ */
+@SystemApi
+// NOTE: Ideally, we would abstract away the details of what identifies a network of a specific
+// type, so that all networks appear the same and can be scored without concern to the network type
+// itself. However, because no such cross-type identifier currently exists in the Android framework,
+// and because systems might obtain information about networks from sources other than Android
+// devices, we need to provide identifying details about each specific network type (wifi, cell,
+// etc.) so that clients can pull out these details depending on the type of network.
+public class NetworkKey implements Parcelable {
+
+ private static final String TAG = "NetworkKey";
+
+ /** A wifi network, for which {@link #wifiKey} will be populated. */
+ public static final int TYPE_WIFI = 1;
+
+ /**
+ * The type of this network.
+ * @see #TYPE_WIFI
+ */
+ public final int type;
+
+ /**
+ * Information identifying a Wi-Fi network. Only set when {@link #type} equals
+ * {@link #TYPE_WIFI}.
+ */
+ public final WifiKey wifiKey;
+
+ /**
+ * Constructs a new NetworkKey for the given wifi {@link ScanResult}.
+ *
+ * @return A new {@link NetworkKey} instance or <code>null</code> if the given
+ * {@link ScanResult} instance is malformed.
+ * @hide
+ */
+ @Nullable
+ public static NetworkKey createFromScanResult(@Nullable ScanResult result) {
+ if (result != null && result.wifiSsid != null) {
+ final String ssid = result.wifiSsid.toString();
+ final String bssid = result.BSSID;
+ if (!TextUtils.isEmpty(ssid) && !ssid.equals(WifiSsid.NONE)
+ && !TextUtils.isEmpty(bssid)) {
+ WifiKey wifiKey;
+ try {
+ wifiKey = new WifiKey(String.format("\"%s\"", ssid), bssid);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Unable to create WifiKey.", e);
+ return null;
+ }
+ return new NetworkKey(wifiKey);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Constructs a new NetworkKey for the given {@link WifiInfo}.
+ *
+ * @param wifiInfo the {@link WifiInfo} to create a {@link NetworkKey} for.
+ * @return A new {@link NetworkKey} instance or <code>null</code> if the given {@link WifiInfo}
+ * instance doesn't represent a connected WiFi network.
+ * @hide
+ */
+ @Nullable
+ public static NetworkKey createFromWifiInfo(@Nullable WifiInfo wifiInfo) {
+ if (wifiInfo != null) {
+ final String ssid = wifiInfo.getSSID();
+ final String bssid = wifiInfo.getBSSID();
+ if (!TextUtils.isEmpty(ssid) && !ssid.equals(WifiSsid.NONE)
+ && !TextUtils.isEmpty(bssid)) {
+ WifiKey wifiKey;
+ try {
+ wifiKey = new WifiKey(ssid, bssid);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Unable to create WifiKey.", e);
+ return null;
+ }
+ return new NetworkKey(wifiKey);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Construct a new {@link NetworkKey} for a Wi-Fi network.
+ * @param wifiKey the {@link WifiKey} identifying this Wi-Fi network.
+ */
+ public NetworkKey(WifiKey wifiKey) {
+ this.type = TYPE_WIFI;
+ this.wifiKey = wifiKey;
+ }
+
+ private NetworkKey(Parcel in) {
+ type = in.readInt();
+ switch (type) {
+ case TYPE_WIFI:
+ wifiKey = WifiKey.CREATOR.createFromParcel(in);
+ break;
+ default:
+ throw new IllegalArgumentException("Parcel has unknown type: " + type);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(type);
+ switch (type) {
+ case TYPE_WIFI:
+ wifiKey.writeToParcel(out, flags);
+ break;
+ default:
+ throw new IllegalStateException("NetworkKey has unknown type " + type);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ NetworkKey that = (NetworkKey) o;
+
+ return type == that.type && Objects.equals(wifiKey, that.wifiKey);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, wifiKey);
+ }
+
+ @Override
+ public String toString() {
+ switch (type) {
+ case TYPE_WIFI:
+ return wifiKey.toString();
+ default:
+ // Don't throw an exception here in case someone is logging this object in a catch
+ // block for debugging purposes.
+ return "InvalidKey";
+ }
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<NetworkKey> CREATOR =
+ new Parcelable.Creator<NetworkKey>() {
+ @Override
+ public NetworkKey createFromParcel(Parcel in) {
+ return new NetworkKey(in);
+ }
+
+ @Override
+ public NetworkKey[] newArray(int size) {
+ return new NetworkKey[size];
+ }
+ };
+}
diff --git a/android/net/NetworkMisc.java b/android/net/NetworkMisc.java
new file mode 100644
index 0000000..9ba3bd9
--- /dev/null
+++ b/android/net/NetworkMisc.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2014 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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A grab-bag of information (metadata, policies, properties, etc) about a
+ * {@link Network}. Since this contains PII, it should not be sent outside the
+ * system.
+ *
+ * @hide
+ */
+public class NetworkMisc implements Parcelable {
+
+ /**
+ * If the {@link Network} is a VPN, whether apps are allowed to bypass the
+ * VPN. This is set by a {@link VpnService} and used by
+ * {@link ConnectivityManager} when creating a VPN.
+ */
+ public boolean allowBypass;
+
+ /**
+ * Set if the network was manually/explicitly connected to by the user either from settings
+ * or a 3rd party app. For example, turning on cell data is not explicit but tapping on a wifi
+ * ap in the wifi settings to trigger a connection is explicit. A 3rd party app asking to
+ * connect to a particular access point is also explicit, though this may change in the future
+ * as we want apps to use the multinetwork apis.
+ */
+ public boolean explicitlySelected;
+
+ /**
+ * Set if the user desires to use this network even if it is unvalidated. This field has meaning
+ * only if {@link explicitlySelected} is true. If it is, this field must also be set to the
+ * appropriate value based on previous user choice.
+ */
+ public boolean acceptUnvalidated;
+
+ /**
+ * Whether the user explicitly set that this network should be validated even if presence of
+ * only partial internet connectivity.
+ */
+ public boolean acceptPartialConnectivity;
+
+ /**
+ * Set to avoid surfacing the "Sign in to network" notification.
+ * if carrier receivers/apps are registered to handle the carrier-specific provisioning
+ * procedure, a carrier specific provisioning notification will be placed.
+ * only one notification should be displayed. This field is set based on
+ * which notification should be used for provisioning.
+ */
+ public boolean provisioningNotificationDisabled;
+
+ /**
+ * For mobile networks, this is the subscriber ID (such as IMSI).
+ */
+ public String subscriberId;
+
+ /**
+ * Set to skip 464xlat. This means the device will treat the network as IPv6-only and
+ * will not attempt to detect a NAT64 via RFC 7050 DNS lookups.
+ */
+ public boolean skip464xlat;
+
+ public NetworkMisc() {
+ }
+
+ public NetworkMisc(NetworkMisc nm) {
+ if (nm != null) {
+ allowBypass = nm.allowBypass;
+ explicitlySelected = nm.explicitlySelected;
+ acceptUnvalidated = nm.acceptUnvalidated;
+ subscriberId = nm.subscriberId;
+ provisioningNotificationDisabled = nm.provisioningNotificationDisabled;
+ skip464xlat = nm.skip464xlat;
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(allowBypass ? 1 : 0);
+ out.writeInt(explicitlySelected ? 1 : 0);
+ out.writeInt(acceptUnvalidated ? 1 : 0);
+ out.writeString(subscriberId);
+ out.writeInt(provisioningNotificationDisabled ? 1 : 0);
+ out.writeInt(skip464xlat ? 1 : 0);
+ }
+
+ public static final @android.annotation.NonNull Creator<NetworkMisc> CREATOR = new Creator<NetworkMisc>() {
+ @Override
+ public NetworkMisc createFromParcel(Parcel in) {
+ NetworkMisc networkMisc = new NetworkMisc();
+ networkMisc.allowBypass = in.readInt() != 0;
+ networkMisc.explicitlySelected = in.readInt() != 0;
+ networkMisc.acceptUnvalidated = in.readInt() != 0;
+ networkMisc.subscriberId = in.readString();
+ networkMisc.provisioningNotificationDisabled = in.readInt() != 0;
+ networkMisc.skip464xlat = in.readInt() != 0;
+ return networkMisc;
+ }
+
+ @Override
+ public NetworkMisc[] newArray(int size) {
+ return new NetworkMisc[size];
+ }
+ };
+}
diff --git a/android/net/NetworkMonitorManager.java b/android/net/NetworkMonitorManager.java
new file mode 100644
index 0000000..0f41302
--- /dev/null
+++ b/android/net/NetworkMonitorManager.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A convenience wrapper for INetworkMonitor.
+ *
+ * Wraps INetworkMonitor calls, making them a bit more friendly to use. Currently handles:
+ * - Clearing calling identity
+ * - Ignoring RemoteExceptions
+ * - Converting to stable parcelables
+ *
+ * By design, all methods on INetworkMonitor are asynchronous oneway IPCs and are thus void. All the
+ * wrapper methods in this class return a boolean that callers can use to determine whether
+ * RemoteException was thrown.
+ */
+public class NetworkMonitorManager {
+
+ @NonNull private final INetworkMonitor mNetworkMonitor;
+ @NonNull private final String mTag;
+
+ public NetworkMonitorManager(@NonNull INetworkMonitor networkMonitorManager,
+ @NonNull String tag) {
+ mNetworkMonitor = networkMonitorManager;
+ mTag = tag;
+ }
+
+ public NetworkMonitorManager(@NonNull INetworkMonitor networkMonitorManager) {
+ this(networkMonitorManager, NetworkMonitorManager.class.getSimpleName());
+ }
+
+ private void log(String s, Throwable e) {
+ Log.e(mTag, s, e);
+ }
+
+ // CHECKSTYLE:OFF Generated code
+
+ public boolean start() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mNetworkMonitor.start();
+ return true;
+ } catch (RemoteException e) {
+ log("Error in start", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ public boolean launchCaptivePortalApp() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mNetworkMonitor.launchCaptivePortalApp();
+ return true;
+ } catch (RemoteException e) {
+ log("Error in launchCaptivePortalApp", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ public boolean notifyCaptivePortalAppFinished(int response) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mNetworkMonitor.notifyCaptivePortalAppFinished(response);
+ return true;
+ } catch (RemoteException e) {
+ log("Error in notifyCaptivePortalAppFinished", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ public boolean setAcceptPartialConnectivity() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mNetworkMonitor.setAcceptPartialConnectivity();
+ return true;
+ } catch (RemoteException e) {
+ log("Error in setAcceptPartialConnectivity", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ public boolean forceReevaluation(int uid) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mNetworkMonitor.forceReevaluation(uid);
+ return true;
+ } catch (RemoteException e) {
+ log("Error in forceReevaluation", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ public boolean notifyPrivateDnsChanged(PrivateDnsConfigParcel config) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mNetworkMonitor.notifyPrivateDnsChanged(config);
+ return true;
+ } catch (RemoteException e) {
+ log("Error in notifyPrivateDnsChanged", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ public boolean notifyDnsResponse(int returnCode) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mNetworkMonitor.notifyDnsResponse(returnCode);
+ return true;
+ } catch (RemoteException e) {
+ log("Error in notifyDnsResponse", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ public boolean notifyNetworkConnected(LinkProperties lp, NetworkCapabilities nc) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mNetworkMonitor.notifyNetworkConnected(lp, nc);
+ return true;
+ } catch (RemoteException e) {
+ log("Error in notifyNetworkConnected", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ public boolean notifyNetworkDisconnected() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mNetworkMonitor.notifyNetworkDisconnected();
+ return true;
+ } catch (RemoteException e) {
+ log("Error in notifyNetworkDisconnected", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ public boolean notifyLinkPropertiesChanged(LinkProperties lp) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mNetworkMonitor.notifyLinkPropertiesChanged(lp);
+ return true;
+ } catch (RemoteException e) {
+ log("Error in notifyLinkPropertiesChanged", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ public boolean notifyNetworkCapabilitiesChanged(NetworkCapabilities nc) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mNetworkMonitor.notifyNetworkCapabilitiesChanged(nc);
+ return true;
+ } catch (RemoteException e) {
+ log("Error in notifyNetworkCapabilitiesChanged", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // CHECKSTYLE:ON Generated code
+}
diff --git a/android/net/NetworkPolicy.java b/android/net/NetworkPolicy.java
new file mode 100644
index 0000000..33baebb
--- /dev/null
+++ b/android/net/NetworkPolicy.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2011 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;
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.BackupUtils;
+import android.util.Range;
+import android.util.RecurrenceRule;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Iterator;
+import java.util.Objects;
+
+/**
+ * Policy for networks matching a {@link NetworkTemplate}, including usage cycle
+ * and limits to be enforced.
+ *
+ * @hide
+ */
+public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> {
+ private static final int VERSION_INIT = 1;
+ private static final int VERSION_RULE = 2;
+ private static final int VERSION_RAPID = 3;
+
+ public static final int CYCLE_NONE = -1;
+ public static final long WARNING_DISABLED = -1;
+ public static final long LIMIT_DISABLED = -1;
+ public static final long SNOOZE_NEVER = -1;
+
+ @UnsupportedAppUsage
+ public NetworkTemplate template;
+ public RecurrenceRule cycleRule;
+ @UnsupportedAppUsage
+ public long warningBytes = WARNING_DISABLED;
+ @UnsupportedAppUsage
+ public long limitBytes = LIMIT_DISABLED;
+ public long lastWarningSnooze = SNOOZE_NEVER;
+ public long lastLimitSnooze = SNOOZE_NEVER;
+ public long lastRapidSnooze = SNOOZE_NEVER;
+ @UnsupportedAppUsage
+ @Deprecated public boolean metered = true;
+ @UnsupportedAppUsage
+ public boolean inferred = false;
+
+ private static final long DEFAULT_MTU = 1500;
+
+ public static RecurrenceRule buildRule(int cycleDay, ZoneId cycleTimezone) {
+ if (cycleDay != NetworkPolicy.CYCLE_NONE) {
+ return RecurrenceRule.buildRecurringMonthly(cycleDay, cycleTimezone);
+ } else {
+ return RecurrenceRule.buildNever();
+ }
+ }
+
+ @Deprecated
+ public NetworkPolicy(NetworkTemplate template, int cycleDay, String cycleTimezone,
+ long warningBytes, long limitBytes, boolean metered) {
+ this(template, cycleDay, cycleTimezone, warningBytes, limitBytes, SNOOZE_NEVER,
+ SNOOZE_NEVER, metered, false);
+ }
+
+ @Deprecated
+ @UnsupportedAppUsage
+ public NetworkPolicy(NetworkTemplate template, int cycleDay, String cycleTimezone,
+ long warningBytes, long limitBytes, long lastWarningSnooze, long lastLimitSnooze,
+ boolean metered, boolean inferred) {
+ this(template, buildRule(cycleDay, ZoneId.of(cycleTimezone)), warningBytes,
+ limitBytes, lastWarningSnooze, lastLimitSnooze, metered, inferred);
+ }
+
+ @Deprecated
+ public NetworkPolicy(NetworkTemplate template, RecurrenceRule cycleRule, long warningBytes,
+ long limitBytes, long lastWarningSnooze, long lastLimitSnooze, boolean metered,
+ boolean inferred) {
+ this(template, cycleRule, warningBytes, limitBytes, lastWarningSnooze, lastLimitSnooze,
+ SNOOZE_NEVER, metered, inferred);
+ }
+
+ public NetworkPolicy(NetworkTemplate template, RecurrenceRule cycleRule, long warningBytes,
+ long limitBytes, long lastWarningSnooze, long lastLimitSnooze, long lastRapidSnooze,
+ boolean metered, boolean inferred) {
+ this.template = Preconditions.checkNotNull(template, "missing NetworkTemplate");
+ this.cycleRule = Preconditions.checkNotNull(cycleRule, "missing RecurrenceRule");
+ this.warningBytes = warningBytes;
+ this.limitBytes = limitBytes;
+ this.lastWarningSnooze = lastWarningSnooze;
+ this.lastLimitSnooze = lastLimitSnooze;
+ this.lastRapidSnooze = lastRapidSnooze;
+ this.metered = metered;
+ this.inferred = inferred;
+ }
+
+ private NetworkPolicy(Parcel source) {
+ template = source.readParcelable(null);
+ cycleRule = source.readParcelable(null);
+ warningBytes = source.readLong();
+ limitBytes = source.readLong();
+ lastWarningSnooze = source.readLong();
+ lastLimitSnooze = source.readLong();
+ lastRapidSnooze = source.readLong();
+ metered = source.readInt() != 0;
+ inferred = source.readInt() != 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(template, flags);
+ dest.writeParcelable(cycleRule, flags);
+ dest.writeLong(warningBytes);
+ dest.writeLong(limitBytes);
+ dest.writeLong(lastWarningSnooze);
+ dest.writeLong(lastLimitSnooze);
+ dest.writeLong(lastRapidSnooze);
+ dest.writeInt(metered ? 1 : 0);
+ dest.writeInt(inferred ? 1 : 0);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public Iterator<Range<ZonedDateTime>> cycleIterator() {
+ return cycleRule.cycleIterator();
+ }
+
+ /**
+ * Test if given measurement is over {@link #warningBytes}.
+ */
+ @UnsupportedAppUsage
+ public boolean isOverWarning(long totalBytes) {
+ return warningBytes != WARNING_DISABLED && totalBytes >= warningBytes;
+ }
+
+ /**
+ * Test if given measurement is near enough to {@link #limitBytes} to be
+ * considered over-limit.
+ */
+ @UnsupportedAppUsage
+ public boolean isOverLimit(long totalBytes) {
+ // over-estimate, since kernel will trigger limit once first packet
+ // trips over limit.
+ totalBytes += 2 * DEFAULT_MTU;
+ return limitBytes != LIMIT_DISABLED && totalBytes >= limitBytes;
+ }
+
+ /**
+ * Clear any existing snooze values, setting to {@link #SNOOZE_NEVER}.
+ */
+ @UnsupportedAppUsage
+ public void clearSnooze() {
+ lastWarningSnooze = SNOOZE_NEVER;
+ lastLimitSnooze = SNOOZE_NEVER;
+ lastRapidSnooze = SNOOZE_NEVER;
+ }
+
+ /**
+ * Test if this policy has a cycle defined, after which usage should reset.
+ */
+ public boolean hasCycle() {
+ return cycleRule.cycleIterator().hasNext();
+ }
+
+ @Override
+ @UnsupportedAppUsage
+ public int compareTo(NetworkPolicy another) {
+ if (another == null || another.limitBytes == LIMIT_DISABLED) {
+ // other value is missing or disabled; we win
+ return -1;
+ }
+ if (limitBytes == LIMIT_DISABLED || another.limitBytes < limitBytes) {
+ // we're disabled or other limit is smaller; they win
+ return 1;
+ }
+ return 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(template, cycleRule, warningBytes, limitBytes,
+ lastWarningSnooze, lastLimitSnooze, lastRapidSnooze, metered, inferred);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof NetworkPolicy) {
+ final NetworkPolicy other = (NetworkPolicy) obj;
+ return warningBytes == other.warningBytes
+ && limitBytes == other.limitBytes
+ && lastWarningSnooze == other.lastWarningSnooze
+ && lastLimitSnooze == other.lastLimitSnooze
+ && lastRapidSnooze == other.lastRapidSnooze
+ && metered == other.metered
+ && inferred == other.inferred
+ && Objects.equals(template, other.template)
+ && Objects.equals(cycleRule, other.cycleRule);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder("NetworkPolicy{")
+ .append("template=").append(template)
+ .append(" cycleRule=").append(cycleRule)
+ .append(" warningBytes=").append(warningBytes)
+ .append(" limitBytes=").append(limitBytes)
+ .append(" lastWarningSnooze=").append(lastWarningSnooze)
+ .append(" lastLimitSnooze=").append(lastLimitSnooze)
+ .append(" lastRapidSnooze=").append(lastRapidSnooze)
+ .append(" metered=").append(metered)
+ .append(" inferred=").append(inferred)
+ .append("}").toString();
+ }
+
+ @UnsupportedAppUsage
+ public static final @android.annotation.NonNull Creator<NetworkPolicy> CREATOR = new Creator<NetworkPolicy>() {
+ @Override
+ public NetworkPolicy createFromParcel(Parcel in) {
+ return new NetworkPolicy(in);
+ }
+
+ @Override
+ public NetworkPolicy[] newArray(int size) {
+ return new NetworkPolicy[size];
+ }
+ };
+
+ public byte[] getBytesForBackup() throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream out = new DataOutputStream(baos);
+
+ out.writeInt(VERSION_RAPID);
+ out.write(template.getBytesForBackup());
+ cycleRule.writeToStream(out);
+ out.writeLong(warningBytes);
+ out.writeLong(limitBytes);
+ out.writeLong(lastWarningSnooze);
+ out.writeLong(lastLimitSnooze);
+ out.writeLong(lastRapidSnooze);
+ out.writeInt(metered ? 1 : 0);
+ out.writeInt(inferred ? 1 : 0);
+ return baos.toByteArray();
+ }
+
+ public static NetworkPolicy getNetworkPolicyFromBackup(DataInputStream in) throws IOException,
+ BackupUtils.BadVersionException {
+ final int version = in.readInt();
+ if (version < VERSION_INIT || version > VERSION_RAPID) {
+ throw new BackupUtils.BadVersionException("Unknown backup version: " + version);
+ }
+
+ final NetworkTemplate template = NetworkTemplate.getNetworkTemplateFromBackup(in);
+ final RecurrenceRule cycleRule;
+ if (version >= VERSION_RULE) {
+ cycleRule = new RecurrenceRule(in);
+ } else {
+ final int cycleDay = in.readInt();
+ final String cycleTimezone = BackupUtils.readString(in);
+ cycleRule = buildRule(cycleDay, ZoneId.of(cycleTimezone));
+ }
+ final long warningBytes = in.readLong();
+ final long limitBytes = in.readLong();
+ final long lastWarningSnooze = in.readLong();
+ final long lastLimitSnooze = in.readLong();
+ final long lastRapidSnooze;
+ if (version >= VERSION_RAPID) {
+ lastRapidSnooze = in.readLong();
+ } else {
+ lastRapidSnooze = SNOOZE_NEVER;
+ }
+ final boolean metered = in.readInt() == 1;
+ final boolean inferred = in.readInt() == 1;
+ return new NetworkPolicy(template, cycleRule, warningBytes, limitBytes, lastWarningSnooze,
+ lastLimitSnooze, lastRapidSnooze, metered, inferred);
+ }
+}
diff --git a/android/net/NetworkPolicyManager.java b/android/net/NetworkPolicyManager.java
new file mode 100644
index 0000000..bf27262
--- /dev/null
+++ b/android/net/NetworkPolicyManager.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2011 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;
+
+import static android.content.pm.PackageManager.GET_SIGNATURES;
+
+import android.annotation.SystemService;
+import android.annotation.UnsupportedAppUsage;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.os.Build;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.DebugUtils;
+import android.util.Pair;
+import android.util.Range;
+
+import com.google.android.collect.Sets;
+
+import java.time.ZonedDateTime;
+import java.util.HashSet;
+import java.util.Iterator;
+
+/**
+ * Manager for creating and modifying network policy rules.
+ *
+ * {@hide}
+ */
+@SystemService(Context.NETWORK_POLICY_SERVICE)
+public class NetworkPolicyManager {
+
+ /* POLICY_* are masks and can be ORed, although currently they are not.*/
+ /** No specific network policy, use system default. */
+ public static final int POLICY_NONE = 0x0;
+ /** Reject network usage on metered networks when application in background. */
+ public static final int POLICY_REJECT_METERED_BACKGROUND = 0x1;
+ /** Allow metered network use in the background even when in data usage save mode. */
+ public static final int POLICY_ALLOW_METERED_BACKGROUND = 0x4;
+
+ /*
+ * Rules defining whether an uid has access to a network given its type (metered / non-metered).
+ *
+ * These rules are bits and can be used in bitmask operations; in particular:
+ * - rule & RULE_MASK_METERED: returns the metered-networks status.
+ * - rule & RULE_MASK_ALL: returns the all-networks status.
+ *
+ * The RULE_xxx_ALL rules applies to all networks (metered or non-metered), but on
+ * metered networks, the RULE_xxx_METERED rules should be checked first. For example,
+ * if the device is on Battery Saver Mode and Data Saver Mode simulatenously, and a uid
+ * is whitelisted for the former but not the latter, its status would be
+ * RULE_REJECT_METERED | RULE_ALLOW_ALL, meaning it could have access to non-metered
+ * networks but not to metered networks.
+ *
+ * See network-policy-restrictions.md for more info.
+ */
+ /** No specific rule was set */
+ public static final int RULE_NONE = 0;
+ /** Allow traffic on metered networks. */
+ public static final int RULE_ALLOW_METERED = 1 << 0;
+ /** Temporarily allow traffic on metered networks because app is on foreground. */
+ public static final int RULE_TEMPORARY_ALLOW_METERED = 1 << 1;
+ /** Reject traffic on metered networks. */
+ public static final int RULE_REJECT_METERED = 1 << 2;
+ /** Network traffic should be allowed on all networks (metered or non-metered), although
+ * metered-network restrictions could still apply. */
+ public static final int RULE_ALLOW_ALL = 1 << 5;
+ /** Reject traffic on all networks. */
+ public static final int RULE_REJECT_ALL = 1 << 6;
+ /** Mask used to get the {@code RULE_xxx_METERED} rules */
+ public static final int MASK_METERED_NETWORKS = 0b00001111;
+ /** Mask used to get the {@code RULE_xxx_ALL} rules */
+ public static final int MASK_ALL_NETWORKS = 0b11110000;
+
+ public static final int FIREWALL_RULE_DEFAULT = 0;
+
+ public static final String FIREWALL_CHAIN_NAME_NONE = "none";
+ public static final String FIREWALL_CHAIN_NAME_DOZABLE = "dozable";
+ public static final String FIREWALL_CHAIN_NAME_STANDBY = "standby";
+ public static final String FIREWALL_CHAIN_NAME_POWERSAVE = "powersave";
+
+ private static final boolean ALLOW_PLATFORM_APP_POLICY = true;
+
+ public static final int FOREGROUND_THRESHOLD_STATE =
+ ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+
+ /**
+ * {@link Intent} extra that indicates which {@link NetworkTemplate} rule it
+ * applies to.
+ */
+ public static final String EXTRA_NETWORK_TEMPLATE = "android.net.NETWORK_TEMPLATE";
+
+ public static final int OVERRIDE_UNMETERED = 1 << 0;
+ public static final int OVERRIDE_CONGESTED = 1 << 1;
+
+ private final Context mContext;
+ @UnsupportedAppUsage
+ private INetworkPolicyManager mService;
+
+ public NetworkPolicyManager(Context context, INetworkPolicyManager service) {
+ if (service == null) {
+ throw new IllegalArgumentException("missing INetworkPolicyManager");
+ }
+ mContext = context;
+ mService = service;
+ }
+
+ @UnsupportedAppUsage
+ public static NetworkPolicyManager from(Context context) {
+ return (NetworkPolicyManager) context.getSystemService(Context.NETWORK_POLICY_SERVICE);
+ }
+
+ /**
+ * Set policy flags for specific UID.
+ *
+ * @param policy should be {@link #POLICY_NONE} or any combination of {@code POLICY_} flags,
+ * although it is not validated.
+ */
+ @UnsupportedAppUsage
+ public void setUidPolicy(int uid, int policy) {
+ try {
+ mService.setUidPolicy(uid, policy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Add policy flags for specific UID.
+ *
+ * <p>The given policy bits will be set for the uid.
+ *
+ * @param policy should be {@link #POLICY_NONE} or any combination of {@code POLICY_} flags,
+ * although it is not validated.
+ */
+ public void addUidPolicy(int uid, int policy) {
+ try {
+ mService.addUidPolicy(uid, policy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Clear/remove policy flags for specific UID.
+ *
+ * <p>The given policy bits will be set for the uid.
+ *
+ * @param policy should be {@link #POLICY_NONE} or any combination of {@code POLICY_} flags,
+ * although it is not validated.
+ */
+ public void removeUidPolicy(int uid, int policy) {
+ try {
+ mService.removeUidPolicy(uid, policy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @UnsupportedAppUsage
+ public int getUidPolicy(int uid) {
+ try {
+ return mService.getUidPolicy(uid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @UnsupportedAppUsage
+ public int[] getUidsWithPolicy(int policy) {
+ try {
+ return mService.getUidsWithPolicy(policy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public void registerListener(INetworkPolicyListener listener) {
+ try {
+ mService.registerListener(listener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public void unregisterListener(INetworkPolicyListener listener) {
+ try {
+ mService.unregisterListener(listener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ public void setNetworkPolicies(NetworkPolicy[] policies) {
+ try {
+ mService.setNetworkPolicies(policies);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @UnsupportedAppUsage
+ public NetworkPolicy[] getNetworkPolicies() {
+ try {
+ return mService.getNetworkPolicies(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @UnsupportedAppUsage
+ public void setRestrictBackground(boolean restrictBackground) {
+ try {
+ mService.setRestrictBackground(restrictBackground);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @UnsupportedAppUsage
+ public boolean getRestrictBackground() {
+ try {
+ return mService.getRestrictBackground();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Resets network policy settings back to factory defaults.
+ *
+ * @hide
+ */
+ public void factoryReset(String subscriber) {
+ try {
+ mService.factoryReset(subscriber);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
+ @Deprecated
+ public static Iterator<Pair<ZonedDateTime, ZonedDateTime>> cycleIterator(NetworkPolicy policy) {
+ final Iterator<Range<ZonedDateTime>> it = policy.cycleIterator();
+ return new Iterator<Pair<ZonedDateTime, ZonedDateTime>>() {
+ @Override
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+
+ @Override
+ public Pair<ZonedDateTime, ZonedDateTime> next() {
+ if (hasNext()) {
+ final Range<ZonedDateTime> r = it.next();
+ return Pair.create(r.getLower(), r.getUpper());
+ } else {
+ return Pair.create(null, null);
+ }
+ }
+ };
+ }
+
+ /**
+ * Check if given UID can have a {@link #setUidPolicy(int, int)} defined,
+ * usually to protect critical system services.
+ */
+ @Deprecated
+ public static boolean isUidValidForPolicy(Context context, int uid) {
+ // first, quick-reject non-applications
+ if (!UserHandle.isApp(uid)) {
+ return false;
+ }
+
+ if (!ALLOW_PLATFORM_APP_POLICY) {
+ final PackageManager pm = context.getPackageManager();
+ final HashSet<Signature> systemSignature;
+ try {
+ systemSignature = Sets.newHashSet(
+ pm.getPackageInfo("android", GET_SIGNATURES).signatures);
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException("problem finding system signature", e);
+ }
+
+ try {
+ // reject apps signed with platform cert
+ for (String packageName : pm.getPackagesForUid(uid)) {
+ final HashSet<Signature> packageSignature = Sets.newHashSet(
+ pm.getPackageInfo(packageName, GET_SIGNATURES).signatures);
+ if (packageSignature.containsAll(systemSignature)) {
+ return false;
+ }
+ }
+ } catch (NameNotFoundException e) {
+ }
+ }
+
+ // nothing found above; we can apply policy to UID
+ return true;
+ }
+
+ /**
+ * @hide
+ */
+ public static String uidRulesToString(int uidRules) {
+ final StringBuilder string = new StringBuilder().append(uidRules).append(" (");
+ if (uidRules == RULE_NONE) {
+ string.append("NONE");
+ } else {
+ string.append(DebugUtils.flagsToString(NetworkPolicyManager.class, "RULE_", uidRules));
+ }
+ string.append(")");
+ return string.toString();
+ }
+
+ /**
+ * @hide
+ */
+ public static String uidPoliciesToString(int uidPolicies) {
+ final StringBuilder string = new StringBuilder().append(uidPolicies).append(" (");
+ if (uidPolicies == POLICY_NONE) {
+ string.append("NONE");
+ } else {
+ string.append(DebugUtils.flagsToString(NetworkPolicyManager.class,
+ "POLICY_", uidPolicies));
+ }
+ string.append(")");
+ return string.toString();
+ }
+
+ /**
+ * Returns true if {@param procState} is considered foreground and as such will be allowed
+ * to access network when the device is idle or in battery saver mode. Otherwise, false.
+ */
+ public static boolean isProcStateAllowedWhileIdleOrPowerSaveMode(int procState) {
+ return procState <= FOREGROUND_THRESHOLD_STATE;
+ }
+
+ /**
+ * Returns true if {@param procState} is considered foreground and as such will be allowed
+ * to access network when the device is in data saver mode. Otherwise, false.
+ */
+ public static boolean isProcStateAllowedWhileOnRestrictBackground(int procState) {
+ return procState <= FOREGROUND_THRESHOLD_STATE;
+ }
+
+ public static String resolveNetworkId(WifiConfiguration config) {
+ return WifiInfo.removeDoubleQuotes(config.isPasspoint()
+ ? config.providerFriendlyName : config.SSID);
+ }
+
+ public static String resolveNetworkId(String ssid) {
+ return WifiInfo.removeDoubleQuotes(ssid);
+ }
+
+ /** {@hide} */
+ public static class Listener extends INetworkPolicyListener.Stub {
+ @Override public void onUidRulesChanged(int uid, int uidRules) { }
+ @Override public void onMeteredIfacesChanged(String[] meteredIfaces) { }
+ @Override public void onRestrictBackgroundChanged(boolean restrictBackground) { }
+ @Override public void onUidPoliciesChanged(int uid, int uidPolicies) { }
+ @Override public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue) { }
+ }
+}
diff --git a/android/net/NetworkQuotaInfo.java b/android/net/NetworkQuotaInfo.java
new file mode 100644
index 0000000..a46cdde
--- /dev/null
+++ b/android/net/NetworkQuotaInfo.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2011 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;
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @deprecated nobody should be using this, but keep it around returning stub
+ * values to prevent app crashes.
+ * @hide
+ */
+@Deprecated
+public class NetworkQuotaInfo implements Parcelable {
+ public static final long NO_LIMIT = -1;
+
+ /** {@hide} */
+ public NetworkQuotaInfo() {
+ }
+
+ /** {@hide} */
+ public NetworkQuotaInfo(Parcel in) {
+ }
+
+ @UnsupportedAppUsage
+ public long getEstimatedBytes() {
+ return 0;
+ }
+
+ @UnsupportedAppUsage
+ public long getSoftLimitBytes() {
+ return NO_LIMIT;
+ }
+
+ @UnsupportedAppUsage
+ public long getHardLimitBytes() {
+ return NO_LIMIT;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ }
+
+ @UnsupportedAppUsage
+ public static final @android.annotation.NonNull Creator<NetworkQuotaInfo> CREATOR = new Creator<NetworkQuotaInfo>() {
+ @Override
+ public NetworkQuotaInfo createFromParcel(Parcel in) {
+ return new NetworkQuotaInfo(in);
+ }
+
+ @Override
+ public NetworkQuotaInfo[] newArray(int size) {
+ return new NetworkQuotaInfo[size];
+ }
+ };
+}
diff --git a/android/net/NetworkRecommendationProvider.java b/android/net/NetworkRecommendationProvider.java
new file mode 100644
index 0000000..a70c97b
--- /dev/null
+++ b/android/net/NetworkRecommendationProvider.java
@@ -0,0 +1,126 @@
+/*
+ * 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 android.net;
+
+import android.Manifest.permission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.concurrent.Executor;
+
+/**
+ * The base class for implementing a network recommendation provider.
+ * <p>
+ * A network recommendation provider is any application which:
+ * <ul>
+ * <li>Is granted the {@link permission#SCORE_NETWORKS} permission.
+ * <li>Is granted the {@link permission#ACCESS_COARSE_LOCATION} permission.
+ * <li>Includes a Service for the {@link NetworkScoreManager#ACTION_RECOMMEND_NETWORKS} intent
+ * which is protected by the {@link permission#BIND_NETWORK_RECOMMENDATION_SERVICE} permission.
+ * </ul>
+ * <p>
+ * Implementations are required to implement the abstract methods in this class and return the
+ * result of {@link #getBinder()} from the <code>onBind()</code> method in their Service.
+ * <p>
+ * The default network recommendation provider is controlled via the
+ * <code>config_defaultNetworkRecommendationProviderPackage</code> config key.
+ * @hide
+ */
+@SystemApi
+public abstract class NetworkRecommendationProvider {
+ private static final String TAG = "NetworkRecProvider";
+ private static final boolean VERBOSE = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE);
+ private final IBinder mService;
+
+ /**
+ * Constructs a new instance.
+ * @param context the current context instance. Cannot be {@code null}.
+ * @param executor used to execute the incoming requests. Cannot be {@code null}.
+ */
+ public NetworkRecommendationProvider(Context context, Executor executor) {
+ Preconditions.checkNotNull(context);
+ Preconditions.checkNotNull(executor);
+ mService = new ServiceWrapper(context, executor);
+ }
+
+ /**
+ * Invoked when network scores have been requested.
+ * <p>
+ * Use {@link NetworkScoreManager#updateScores(ScoredNetwork[])} to respond to score requests.
+ *
+ * @param networks a non-empty array of {@link NetworkKey}s to score.
+ */
+ public abstract void onRequestScores(NetworkKey[] networks);
+
+ /**
+ * Services that can handle {@link NetworkScoreManager#ACTION_RECOMMEND_NETWORKS} should
+ * return this Binder from their <code>onBind()</code> method.
+ */
+ public final IBinder getBinder() {
+ return mService;
+ }
+
+ /**
+ * A wrapper around INetworkRecommendationProvider that dispatches to the provided Handler.
+ */
+ private final class ServiceWrapper extends INetworkRecommendationProvider.Stub {
+ private final Context mContext;
+ private final Executor mExecutor;
+ private final Handler mHandler;
+
+ ServiceWrapper(Context context, Executor executor) {
+ mContext = context;
+ mExecutor = executor;
+ mHandler = null;
+ }
+
+ @Override
+ public void requestScores(final NetworkKey[] networks) throws RemoteException {
+ enforceCallingPermission();
+ if (networks != null && networks.length > 0) {
+ execute(new Runnable() {
+ @Override
+ public void run() {
+ onRequestScores(networks);
+ }
+ });
+ }
+ }
+
+ private void execute(Runnable command) {
+ if (mExecutor != null) {
+ mExecutor.execute(command);
+ } else {
+ mHandler.post(command);
+ }
+ }
+
+ private void enforceCallingPermission() {
+ if (mContext != null) {
+ mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES,
+ "Permission denied.");
+ }
+ }
+ }
+}
diff --git a/android/net/NetworkRequest.java b/android/net/NetworkRequest.java
new file mode 100644
index 0000000..4270740
--- /dev/null
+++ b/android/net/NetworkRequest.java
@@ -0,0 +1,511 @@
+/*
+ * Copyright (C) 2014 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;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.UnsupportedAppUsage;
+import android.net.NetworkCapabilities.NetCapability;
+import android.net.NetworkCapabilities.Transport;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Process;
+import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
+
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Defines a request for a network, made through {@link NetworkRequest.Builder} and used
+ * to request a network via {@link ConnectivityManager#requestNetwork} or listen for changes
+ * via {@link ConnectivityManager#registerNetworkCallback}.
+ */
+public class NetworkRequest implements Parcelable {
+ /**
+ * The {@link NetworkCapabilities} that define this request.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public final @NonNull NetworkCapabilities networkCapabilities;
+
+ /**
+ * Identifies the request. NetworkRequests should only be constructed by
+ * the Framework and given out to applications as tokens to be used to identify
+ * the request.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public final int requestId;
+
+ /**
+ * Set for legacy requests and the default. Set to TYPE_NONE for none.
+ * Causes CONNECTIVITY_ACTION broadcasts to be sent.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public final int legacyType;
+
+ /**
+ * A NetworkRequest as used by the system can be one of the following types:
+ *
+ * - LISTEN, for which the framework will issue callbacks about any
+ * and all networks that match the specified NetworkCapabilities,
+ *
+ * - REQUEST, capable of causing a specific network to be created
+ * first (e.g. a telephony DUN request), the framework will issue
+ * callbacks about the single, highest scoring current network
+ * (if any) that matches the specified NetworkCapabilities, or
+ *
+ * - TRACK_DEFAULT, a hybrid of the two designed such that the
+ * framework will issue callbacks for the single, highest scoring
+ * current network (if any) that matches the capabilities of the
+ * default Internet request (mDefaultRequest), but which cannot cause
+ * the framework to either create or retain the existence of any
+ * specific network. Note that from the point of view of the request
+ * matching code, TRACK_DEFAULT is identical to REQUEST: its special
+ * behaviour is not due to different semantics, but to the fact that
+ * the system will only ever create a TRACK_DEFAULT with capabilities
+ * that are identical to the default request's capabilities, thus
+ * causing it to share fate in every way with the default request.
+ *
+ * - BACKGROUND_REQUEST, like REQUEST but does not cause any networks
+ * to retain the NET_CAPABILITY_FOREGROUND capability. A network with
+ * no foreground requests is in the background. A network that has
+ * one or more background requests and loses its last foreground
+ * request to a higher-scoring network will not go into the
+ * background immediately, but will linger and go into the background
+ * after the linger timeout.
+ *
+ * - The value NONE is used only by applications. When an application
+ * creates a NetworkRequest, it does not have a type; the type is set
+ * by the system depending on the method used to file the request
+ * (requestNetwork, registerNetworkCallback, etc.).
+ *
+ * @hide
+ */
+ public static enum Type {
+ NONE,
+ LISTEN,
+ TRACK_DEFAULT,
+ REQUEST,
+ BACKGROUND_REQUEST,
+ };
+
+ /**
+ * The type of the request. This is only used by the system and is always NONE elsewhere.
+ *
+ * @hide
+ */
+ public final Type type;
+
+ /**
+ * @hide
+ */
+ public NetworkRequest(NetworkCapabilities nc, int legacyType, int rId, Type type) {
+ if (nc == null) {
+ throw new NullPointerException();
+ }
+ requestId = rId;
+ networkCapabilities = nc;
+ this.legacyType = legacyType;
+ this.type = type;
+ }
+
+ /**
+ * @hide
+ */
+ public NetworkRequest(NetworkRequest that) {
+ networkCapabilities = new NetworkCapabilities(that.networkCapabilities);
+ requestId = that.requestId;
+ this.legacyType = that.legacyType;
+ this.type = that.type;
+ }
+
+ /**
+ * Builder used to create {@link NetworkRequest} objects. Specify the Network features
+ * needed in terms of {@link NetworkCapabilities} features
+ */
+ public static class Builder {
+ private final NetworkCapabilities mNetworkCapabilities;
+
+ /**
+ * Default constructor for Builder.
+ */
+ public Builder() {
+ // By default, restrict this request to networks available to this app.
+ // Apps can rescind this restriction, but ConnectivityService will enforce
+ // it for apps that do not have the NETWORK_SETTINGS permission.
+ mNetworkCapabilities = new NetworkCapabilities();
+ mNetworkCapabilities.setSingleUid(Process.myUid());
+ }
+
+ /**
+ * Build {@link NetworkRequest} give the current set of capabilities.
+ */
+ public NetworkRequest build() {
+ // Make a copy of mNetworkCapabilities so we don't inadvertently remove NOT_RESTRICTED
+ // when later an unrestricted capability could be added to mNetworkCapabilities, in
+ // which case NOT_RESTRICTED should be returned to mNetworkCapabilities, which
+ // maybeMarkCapabilitiesRestricted() doesn't add back.
+ final NetworkCapabilities nc = new NetworkCapabilities(mNetworkCapabilities);
+ nc.maybeMarkCapabilitiesRestricted();
+ return new NetworkRequest(nc, ConnectivityManager.TYPE_NONE,
+ ConnectivityManager.REQUEST_ID_UNSET, Type.NONE);
+ }
+
+ /**
+ * Add the given capability requirement to this builder. These represent
+ * the requested network's required capabilities. Note that when searching
+ * for a network to satisfy a request, all capabilities requested must be
+ * satisfied.
+ *
+ * @param capability The capability to add.
+ * @return The builder to facilitate chaining
+ * {@code builder.addCapability(...).addCapability();}.
+ */
+ public Builder addCapability(@NetworkCapabilities.NetCapability int capability) {
+ mNetworkCapabilities.addCapability(capability);
+ return this;
+ }
+
+ /**
+ * Removes (if found) the given capability from this builder instance.
+ *
+ * @param capability The capability to remove.
+ * @return The builder to facilitate chaining.
+ */
+ public Builder removeCapability(@NetworkCapabilities.NetCapability int capability) {
+ mNetworkCapabilities.removeCapability(capability);
+ return this;
+ }
+
+ /**
+ * Set the {@code NetworkCapabilities} for this builder instance,
+ * overriding any capabilities that had been previously set.
+ *
+ * @param nc The superseding {@code NetworkCapabilities} instance.
+ * @return The builder to facilitate chaining.
+ * @hide
+ */
+ public Builder setCapabilities(NetworkCapabilities nc) {
+ mNetworkCapabilities.set(nc);
+ return this;
+ }
+
+ /**
+ * Set the watched UIDs for this request. This will be reset and wiped out unless
+ * the calling app holds the CHANGE_NETWORK_STATE permission.
+ *
+ * @param uids The watched UIDs as a set of UidRanges, or null for everything.
+ * @return The builder to facilitate chaining.
+ * @hide
+ */
+ public Builder setUids(Set<UidRange> uids) {
+ mNetworkCapabilities.setUids(uids);
+ return this;
+ }
+
+ /**
+ * Add a capability that must not exist in the requested network.
+ * <p>
+ * If the capability was previously added to the list of required capabilities (for
+ * example, it was there by default or added using {@link #addCapability(int)} method), then
+ * it will be removed from the list of required capabilities as well.
+ *
+ * @see #addCapability(int)
+ *
+ * @param capability The capability to add to unwanted capability list.
+ * @return The builder to facilitate chaining.
+ *
+ * @hide
+ */
+ public Builder addUnwantedCapability(@NetworkCapabilities.NetCapability int capability) {
+ mNetworkCapabilities.addUnwantedCapability(capability);
+ return this;
+ }
+
+ /**
+ * Completely clears all the {@code NetworkCapabilities} from this builder instance,
+ * removing even the capabilities that are set by default when the object is constructed.
+ *
+ * @return The builder to facilitate chaining.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public Builder clearCapabilities() {
+ mNetworkCapabilities.clearAll();
+ return this;
+ }
+
+ /**
+ * Adds the given transport requirement to this builder. These represent
+ * the set of allowed transports for the request. Only networks using one
+ * of these transports will satisfy the request. If no particular transports
+ * are required, none should be specified here.
+ *
+ * @param transportType The transport type to add.
+ * @return The builder to facilitate chaining.
+ */
+ public Builder addTransportType(@NetworkCapabilities.Transport int transportType) {
+ mNetworkCapabilities.addTransportType(transportType);
+ return this;
+ }
+
+ /**
+ * Removes (if found) the given transport from this builder instance.
+ *
+ * @param transportType The transport type to remove.
+ * @return The builder to facilitate chaining.
+ */
+ public Builder removeTransportType(@NetworkCapabilities.Transport int transportType) {
+ mNetworkCapabilities.removeTransportType(transportType);
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ public Builder setLinkUpstreamBandwidthKbps(int upKbps) {
+ mNetworkCapabilities.setLinkUpstreamBandwidthKbps(upKbps);
+ return this;
+ }
+ /**
+ * @hide
+ */
+ public Builder setLinkDownstreamBandwidthKbps(int downKbps) {
+ mNetworkCapabilities.setLinkDownstreamBandwidthKbps(downKbps);
+ return this;
+ }
+
+ /**
+ * Sets the optional bearer specific network specifier.
+ * This has no meaning if a single transport is also not specified, so calling
+ * this without a single transport set will generate an exception, as will
+ * subsequently adding or removing transports after this is set.
+ * </p>
+ * The interpretation of this {@code String} is bearer specific and bearers that use
+ * it should document their particulars. For example, Bluetooth may use some sort of
+ * device id while WiFi could used ssid and/or bssid. Cellular may use carrier spn.
+ *
+ * @param networkSpecifier An {@code String} of opaque format used to specify the bearer
+ * specific network specifier where the bearer has a choice of
+ * networks.
+ */
+ public Builder setNetworkSpecifier(String networkSpecifier) {
+ /*
+ * A StringNetworkSpecifier does not accept null or empty ("") strings. When network
+ * specifiers were strings a null string and an empty string were considered equivalent.
+ * Hence no meaning is attached to a null or empty ("") string.
+ */
+ return setNetworkSpecifier(TextUtils.isEmpty(networkSpecifier) ? null
+ : new StringNetworkSpecifier(networkSpecifier));
+ }
+
+ /**
+ * Sets the optional bearer specific network specifier.
+ * This has no meaning if a single transport is also not specified, so calling
+ * this without a single transport set will generate an exception, as will
+ * subsequently adding or removing transports after this is set.
+ * </p>
+ *
+ * @param networkSpecifier A concrete, parcelable framework class that extends
+ * NetworkSpecifier.
+ */
+ public Builder setNetworkSpecifier(NetworkSpecifier networkSpecifier) {
+ MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(networkSpecifier);
+ mNetworkCapabilities.setNetworkSpecifier(networkSpecifier);
+ return this;
+ }
+
+ /**
+ * Sets the signal strength. This is a signed integer, with higher values indicating a
+ * stronger signal. The exact units are bearer-dependent. For example, Wi-Fi uses the same
+ * RSSI units reported by WifiManager.
+ * <p>
+ * Note that when used to register a network callback, this specifies the minimum acceptable
+ * signal strength. When received as the state of an existing network it specifies the
+ * current value. A value of {@code SIGNAL_STRENGTH_UNSPECIFIED} means no value when
+ * received and has no effect when requesting a callback.
+ *
+ * <p>This method requires the caller to hold the
+ * {@link android.Manifest.permission#NETWORK_SIGNAL_STRENGTH_WAKEUP} permission
+ *
+ * @param signalStrength the bearer-specific signal strength.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP)
+ public @NonNull Builder setSignalStrength(int signalStrength) {
+ mNetworkCapabilities.setSignalStrength(signalStrength);
+ return this;
+ }
+ }
+
+ // implement the Parcelable interface
+ public int describeContents() {
+ return 0;
+ }
+ public void writeToParcel(Parcel dest, int flags) {
+ networkCapabilities.writeToParcel(dest, flags);
+ dest.writeInt(legacyType);
+ dest.writeInt(requestId);
+ dest.writeString(type.name());
+ }
+ public static final @android.annotation.NonNull Creator<NetworkRequest> CREATOR =
+ new Creator<NetworkRequest>() {
+ public NetworkRequest createFromParcel(Parcel in) {
+ NetworkCapabilities nc = NetworkCapabilities.CREATOR.createFromParcel(in);
+ int legacyType = in.readInt();
+ int requestId = in.readInt();
+ Type type = Type.valueOf(in.readString()); // IllegalArgumentException if invalid.
+ NetworkRequest result = new NetworkRequest(nc, legacyType, requestId, type);
+ return result;
+ }
+ public NetworkRequest[] newArray(int size) {
+ return new NetworkRequest[size];
+ }
+ };
+
+ /**
+ * Returns true iff. this NetworkRequest is of type LISTEN.
+ *
+ * @hide
+ */
+ public boolean isListen() {
+ return type == Type.LISTEN;
+ }
+
+ /**
+ * Returns true iff. the contained NetworkRequest is one that:
+ *
+ * - should be associated with at most one satisfying network
+ * at a time;
+ *
+ * - should cause a network to be kept up, but not necessarily in
+ * the foreground, if it is the best network which can satisfy the
+ * NetworkRequest.
+ *
+ * For full detail of how isRequest() is used for pairing Networks with
+ * NetworkRequests read rematchNetworkAndRequests().
+ *
+ * @hide
+ */
+ public boolean isRequest() {
+ return isForegroundRequest() || isBackgroundRequest();
+ }
+
+ /**
+ * Returns true iff. the contained NetworkRequest is one that:
+ *
+ * - should be associated with at most one satisfying network
+ * at a time;
+ *
+ * - should cause a network to be kept up and in the foreground if
+ * it is the best network which can satisfy the NetworkRequest.
+ *
+ * For full detail of how isRequest() is used for pairing Networks with
+ * NetworkRequests read rematchNetworkAndRequests().
+ *
+ * @hide
+ */
+ public boolean isForegroundRequest() {
+ return type == Type.TRACK_DEFAULT || type == Type.REQUEST;
+ }
+
+ /**
+ * Returns true iff. this NetworkRequest is of type BACKGROUND_REQUEST.
+ *
+ * @hide
+ */
+ public boolean isBackgroundRequest() {
+ return type == Type.BACKGROUND_REQUEST;
+ }
+
+ /**
+ * @see Builder#addCapability(int)
+ */
+ public boolean hasCapability(@NetCapability int capability) {
+ return networkCapabilities.hasCapability(capability);
+ }
+
+ /**
+ * @see Builder#addUnwantedCapability(int)
+ *
+ * @hide
+ */
+ public boolean hasUnwantedCapability(@NetCapability int capability) {
+ return networkCapabilities.hasUnwantedCapability(capability);
+ }
+
+ /**
+ * @see Builder#addTransportType(int)
+ */
+ public boolean hasTransport(@Transport int transportType) {
+ return networkCapabilities.hasTransport(transportType);
+ }
+
+ public String toString() {
+ return "NetworkRequest [ " + type + " id=" + requestId +
+ (legacyType != ConnectivityManager.TYPE_NONE ? ", legacyType=" + legacyType : "") +
+ ", " + networkCapabilities.toString() + " ]";
+ }
+
+ private int typeToProtoEnum(Type t) {
+ switch (t) {
+ case NONE:
+ return NetworkRequestProto.TYPE_NONE;
+ case LISTEN:
+ return NetworkRequestProto.TYPE_LISTEN;
+ case TRACK_DEFAULT:
+ return NetworkRequestProto.TYPE_TRACK_DEFAULT;
+ case REQUEST:
+ return NetworkRequestProto.TYPE_REQUEST;
+ case BACKGROUND_REQUEST:
+ return NetworkRequestProto.TYPE_BACKGROUND_REQUEST;
+ default:
+ return NetworkRequestProto.TYPE_UNKNOWN;
+ }
+ }
+
+ /** @hide */
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+
+ proto.write(NetworkRequestProto.TYPE, typeToProtoEnum(type));
+ proto.write(NetworkRequestProto.REQUEST_ID, requestId);
+ proto.write(NetworkRequestProto.LEGACY_TYPE, legacyType);
+ networkCapabilities.writeToProto(proto, NetworkRequestProto.NETWORK_CAPABILITIES);
+
+ proto.end(token);
+ }
+
+ public boolean equals(Object obj) {
+ if (obj instanceof NetworkRequest == false) return false;
+ NetworkRequest that = (NetworkRequest)obj;
+ return (that.legacyType == this.legacyType &&
+ that.requestId == this.requestId &&
+ that.type == this.type &&
+ Objects.equals(that.networkCapabilities, this.networkCapabilities));
+ }
+
+ public int hashCode() {
+ return Objects.hash(requestId, legacyType, networkCapabilities, type);
+ }
+}
diff --git a/android/net/NetworkScoreManager.java b/android/net/NetworkScoreManager.java
new file mode 100644
index 0000000..50dd468
--- /dev/null
+++ b/android/net/NetworkScoreManager.java
@@ -0,0 +1,450 @@
+/*
+ * Copyright (C) 2014 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;
+
+import android.Manifest.permission;
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * Class that manages communication between network subsystems and a network scorer.
+ *
+ * <p>A network scorer is any application which:
+ * <ul>
+ * <li>Is granted the {@link permission#SCORE_NETWORKS} permission.
+ * <li>Is granted the {@link permission#ACCESS_COARSE_LOCATION} permission.
+ * <li>Include a Service for the {@link #ACTION_RECOMMEND_NETWORKS} action
+ * protected by the {@link permission#BIND_NETWORK_RECOMMENDATION_SERVICE}
+ * permission.
+ * </ul>
+ *
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.NETWORK_SCORE_SERVICE)
+public class NetworkScoreManager {
+ /**
+ * Activity action: ask the user to change the active network scorer. This will show a dialog
+ * that asks the user whether they want to replace the current active scorer with the one
+ * specified in {@link #EXTRA_PACKAGE_NAME}. The activity will finish with RESULT_OK if the
+ * active scorer was changed or RESULT_CANCELED if it failed for any reason.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CHANGE_ACTIVE = "android.net.scoring.CHANGE_ACTIVE";
+
+ /**
+ * Extra used with {@link #ACTION_CHANGE_ACTIVE} to specify the new scorer package. Set with
+ * {@link android.content.Intent#putExtra(String, String)}.
+ */
+ public static final String EXTRA_PACKAGE_NAME = "packageName";
+
+ /**
+ * Broadcast action: new network scores are being requested. This intent will only be delivered
+ * to the current active scorer app. That app is responsible for scoring the networks and
+ * calling {@link #updateScores} when complete. The networks to score are specified in
+ * {@link #EXTRA_NETWORKS_TO_SCORE}, and will generally consist of all networks which have been
+ * configured by the user as well as any open networks.
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SCORE_NETWORKS = "android.net.scoring.SCORE_NETWORKS";
+
+ /**
+ * Extra used with {@link #ACTION_SCORE_NETWORKS} to specify the networks to be scored, as an
+ * array of {@link NetworkKey}s. Can be obtained with
+ * {@link android.content.Intent#getParcelableArrayExtra(String)}}.
+ */
+ public static final String EXTRA_NETWORKS_TO_SCORE = "networksToScore";
+
+ /**
+ * Activity action: launch an activity for configuring a provider for the feature that connects
+ * and secures open wifi networks available before enabling it. Applications that enable this
+ * feature must provide an activity for this action. The framework will launch this activity
+ * which must return RESULT_OK if the feature should be enabled.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CUSTOM_ENABLE = "android.net.scoring.CUSTOM_ENABLE";
+
+ /**
+ * Meta-data specified on a {@link NetworkRecommendationProvider} that provides a user-visible
+ * label of the recommendation service.
+ * @hide
+ */
+ public static final String RECOMMENDATION_SERVICE_LABEL_META_DATA =
+ "android.net.scoring.recommendation_service_label";
+
+ /**
+ * Meta-data specified on a {@link NetworkRecommendationProvider} that specified the package
+ * name of the application that connects and secures open wifi networks automatically. The
+ * specified package must provide an Activity for {@link #ACTION_CUSTOM_ENABLE}.
+ * @hide
+ */
+ public static final String USE_OPEN_WIFI_PACKAGE_META_DATA =
+ "android.net.wifi.use_open_wifi_package";
+
+ /**
+ * Meta-data specified on a {@link NetworkRecommendationProvider} that specifies the
+ * {@link android.app.NotificationChannel} ID used to post open network notifications.
+ * @hide
+ */
+ public static final String NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID_META_DATA =
+ "android.net.wifi.notification_channel_id_network_available";
+
+ /**
+ * Broadcast action: the active scorer has been changed. Scorer apps may listen to this to
+ * perform initialization once selected as the active scorer, or clean up unneeded resources
+ * if another scorer has been selected. This is an explicit broadcast only sent to the
+ * previous scorer and new scorer. Note that it is unnecessary to clear existing scores as
+ * this is handled by the system.
+ *
+ * <p>The new scorer will be specified in {@link #EXTRA_NEW_SCORER}.
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SCORER_CHANGED = "android.net.scoring.SCORER_CHANGED";
+
+ /**
+ * Service action: Used to discover and bind to a network recommendation provider.
+ * Implementations should return {@link NetworkRecommendationProvider#getBinder()} from
+ * their <code>onBind()</code> method.
+ */
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
+ public static final String ACTION_RECOMMEND_NETWORKS = "android.net.action.RECOMMEND_NETWORKS";
+
+ /**
+ * Extra used with {@link #ACTION_SCORER_CHANGED} to specify the newly selected scorer's package
+ * name. Will be null if scoring was disabled. Can be obtained with
+ * {@link android.content.Intent#getStringExtra(String)}.
+ */
+ public static final String EXTRA_NEW_SCORER = "newScorer";
+
+ /** @hide */
+ @IntDef({CACHE_FILTER_NONE, CACHE_FILTER_CURRENT_NETWORK, CACHE_FILTER_SCAN_RESULTS})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CacheUpdateFilter {}
+
+ /**
+ * Do not filter updates sent to the cache.
+ * @hide
+ */
+ public static final int CACHE_FILTER_NONE = 0;
+
+ /**
+ * Only send cache updates when the network matches the connected network.
+ * @hide
+ */
+ public static final int CACHE_FILTER_CURRENT_NETWORK = 1;
+
+ /**
+ * Only send cache updates when the network is part of the current scan result set.
+ * @hide
+ */
+ public static final int CACHE_FILTER_SCAN_RESULTS = 2;
+
+ /** @hide */
+ @IntDef({RECOMMENDATIONS_ENABLED_FORCED_OFF, RECOMMENDATIONS_ENABLED_OFF,
+ RECOMMENDATIONS_ENABLED_ON})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RecommendationsEnabledSetting {}
+
+ /**
+ * Recommendations have been forced off.
+ * <p>
+ * This value is never set by any of the NetworkScore classes, it must be set via other means.
+ * This state is also "sticky" and we won't transition out of this state once entered. To move
+ * to a different state this value has to be explicitly set to a different value via
+ * other means.
+ * @hide
+ */
+ public static final int RECOMMENDATIONS_ENABLED_FORCED_OFF = -1;
+
+ /**
+ * Recommendations are not enabled.
+ * <p>
+ * This is a transient state that can be entered when the default recommendation app is enabled
+ * but no longer valid. This state will transition to RECOMMENDATIONS_ENABLED_ON when a valid
+ * recommendation app is enabled.
+ * @hide
+ */
+ public static final int RECOMMENDATIONS_ENABLED_OFF = 0;
+
+ /**
+ * Recommendations are enabled.
+ * <p>
+ * This is a transient state that means a valid recommendation app is active. This state will
+ * transition to RECOMMENDATIONS_ENABLED_OFF if the current and default recommendation apps
+ * become invalid.
+ * @hide
+ */
+ public static final int RECOMMENDATIONS_ENABLED_ON = 1;
+
+ private final Context mContext;
+ private final INetworkScoreService mService;
+
+ /** @hide */
+ public NetworkScoreManager(Context context) throws ServiceNotFoundException {
+ mContext = context;
+ mService = INetworkScoreService.Stub
+ .asInterface(ServiceManager.getServiceOrThrow(Context.NETWORK_SCORE_SERVICE));
+ }
+
+ /**
+ * Obtain the package name of the current active network scorer.
+ *
+ * <p>At any time, only one scorer application will receive {@link #ACTION_SCORE_NETWORKS}
+ * broadcasts and be allowed to call {@link #updateScores}. Applications may use this method to
+ * determine the current scorer and offer the user the ability to select a different scorer via
+ * the {@link #ACTION_CHANGE_ACTIVE} intent.
+ * @return the full package name of the current active scorer, or null if there is no active
+ * scorer.
+ * @throws SecurityException if the caller doesn't hold either {@link permission#SCORE_NETWORKS}
+ * or {@link permission#REQUEST_NETWORK_SCORES} permissions.
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.SCORE_NETWORKS,
+ android.Manifest.permission.REQUEST_NETWORK_SCORES})
+ public String getActiveScorerPackage() {
+ try {
+ return mService.getActiveScorerPackage();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns metadata about the active scorer or <code>null</code> if there is no active scorer.
+ *
+ * @throws SecurityException if the caller does not hold the
+ * {@link permission#REQUEST_NETWORK_SCORES} permission.
+ * @hide
+ */
+ @Nullable
+ @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES)
+ public NetworkScorerAppData getActiveScorer() {
+ try {
+ return mService.getActiveScorer();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the list of available scorer apps. The list will be empty if there are
+ * no valid scorers.
+ *
+ * @throws SecurityException if the caller does not hold the
+ * {@link permission#REQUEST_NETWORK_SCORES} permission.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES)
+ public List<NetworkScorerAppData> getAllValidScorers() {
+ try {
+ return mService.getAllValidScorers();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Update network scores.
+ *
+ * <p>This may be called at any time to re-score active networks. Scores will generally be
+ * updated quickly, but if this method is called too frequently, the scores may be held and
+ * applied at a later time.
+ *
+ * @param networks the networks which have been scored by the scorer.
+ * @return whether the update was successful.
+ * @throws SecurityException if the caller is not the active scorer.
+ */
+ @RequiresPermission(android.Manifest.permission.SCORE_NETWORKS)
+ public boolean updateScores(ScoredNetwork[] networks) throws SecurityException {
+ try {
+ return mService.updateScores(networks);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Clear network scores.
+ *
+ * <p>Should be called when all scores need to be invalidated, i.e. because the scoring
+ * algorithm has changed and old scores can no longer be compared to future scores.
+ *
+ * <p>Note that scores will be cleared automatically when the active scorer changes, as scores
+ * from one scorer cannot be compared to those from another scorer.
+ *
+ * @return whether the clear was successful.
+ * @throws SecurityException if the caller is not the active scorer or if the caller doesn't
+ * hold the {@link permission#REQUEST_NETWORK_SCORES} permission.
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.SCORE_NETWORKS,
+ android.Manifest.permission.REQUEST_NETWORK_SCORES})
+ public boolean clearScores() throws SecurityException {
+ try {
+ return mService.clearScores();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set the active scorer to a new package and clear existing scores.
+ *
+ * <p>Should never be called directly without obtaining user consent. This can be done by using
+ * the {@link #ACTION_CHANGE_ACTIVE} broadcast, or using a custom configuration activity.
+ *
+ * @return true if the operation succeeded, or false if the new package is not a valid scorer.
+ * @throws SecurityException if the caller doesn't hold either {@link permission#SCORE_NETWORKS}
+ * or {@link permission#REQUEST_NETWORK_SCORES} permissions.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {android.Manifest.permission.SCORE_NETWORKS,
+ android.Manifest.permission.REQUEST_NETWORK_SCORES})
+ public boolean setActiveScorer(String packageName) throws SecurityException {
+ try {
+ return mService.setActiveScorer(packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Turn off network scoring.
+ *
+ * <p>May only be called by the current scorer app, or the system.
+ *
+ * @throws SecurityException if the caller is not the active scorer or if the caller doesn't
+ * hold the {@link permission#REQUEST_NETWORK_SCORES} permission.
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.SCORE_NETWORKS,
+ android.Manifest.permission.REQUEST_NETWORK_SCORES})
+ public void disableScoring() throws SecurityException {
+ try {
+ mService.disableScoring();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Request scoring for networks.
+ *
+ * @return true if the broadcast was sent, or false if there is no active scorer.
+ * @throws SecurityException if the caller does not hold the
+ * {@link permission#REQUEST_NETWORK_SCORES} permission.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES)
+ public boolean requestScores(NetworkKey[] networks) throws SecurityException {
+ try {
+ return mService.requestScores(networks);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Register a network score cache.
+ *
+ * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}.
+ * @param scoreCache implementation of {@link INetworkScoreCache} to store the scores.
+ * @throws SecurityException if the caller does not hold the
+ * {@link permission#REQUEST_NETWORK_SCORES} permission.
+ * @throws IllegalArgumentException if a score cache is already registered for this type.
+ * @deprecated equivalent to registering for cache updates with CACHE_FILTER_NONE.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES)
+ @Deprecated // migrate to registerNetworkScoreCache(int, INetworkScoreCache, int)
+ public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
+ registerNetworkScoreCache(networkType, scoreCache, CACHE_FILTER_NONE);
+ }
+
+ /**
+ * Register a network score cache.
+ *
+ * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}
+ * @param scoreCache implementation of {@link INetworkScoreCache} to store the scores
+ * @param filterType the {@link CacheUpdateFilter} to apply
+ * @throws SecurityException if the caller does not hold the
+ * {@link permission#REQUEST_NETWORK_SCORES} permission.
+ * @throws IllegalArgumentException if a score cache is already registered for this type.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES)
+ public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache,
+ @CacheUpdateFilter int filterType) {
+ try {
+ mService.registerNetworkScoreCache(networkType, scoreCache, filterType);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregister a network score cache.
+ *
+ * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}.
+ * @param scoreCache implementation of {@link INetworkScoreCache} to store the scores.
+ * @throws SecurityException if the caller does not hold the
+ * {@link permission#REQUEST_NETWORK_SCORES} permission.
+ * @throws IllegalArgumentException if a score cache is already registered for this type.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES)
+ public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
+ try {
+ mService.unregisterNetworkScoreCache(networkType, scoreCache);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Determine whether the application with the given UID is the enabled scorer.
+ *
+ * @param callingUid the UID to check
+ * @return true if the provided UID is the active scorer, false otherwise.
+ * @throws SecurityException if the caller does not hold the
+ * {@link permission#REQUEST_NETWORK_SCORES} permission.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES)
+ public boolean isCallerActiveScorer(int callingUid) {
+ try {
+ return mService.isCallerActiveScorer(callingUid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/android/net/NetworkScorerAppData.java b/android/net/NetworkScorerAppData.java
new file mode 100644
index 0000000..116e39e
--- /dev/null
+++ b/android/net/NetworkScorerAppData.java
@@ -0,0 +1,130 @@
+package android.net;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Holds metadata about a discovered network scorer/recommendation application.
+ *
+ * @hide
+ */
+public final class NetworkScorerAppData implements Parcelable {
+ /** UID of the scorer app. */
+ public final int packageUid;
+ private final ComponentName mRecommendationService;
+ /** User visible label in Settings for the recommendation service. */
+ private final String mRecommendationServiceLabel;
+ /**
+ * The {@link ComponentName} of the Activity to start before enabling the "connect to open
+ * wifi networks automatically" feature.
+ */
+ private final ComponentName mEnableUseOpenWifiActivity;
+ /**
+ * The {@link android.app.NotificationChannel} ID used by {@link #mRecommendationService} to
+ * post open network notifications.
+ */
+ private final String mNetworkAvailableNotificationChannelId;
+
+ public NetworkScorerAppData(int packageUid, ComponentName recommendationServiceComp,
+ String recommendationServiceLabel, ComponentName enableUseOpenWifiActivity,
+ String networkAvailableNotificationChannelId) {
+ this.packageUid = packageUid;
+ this.mRecommendationService = recommendationServiceComp;
+ this.mRecommendationServiceLabel = recommendationServiceLabel;
+ this.mEnableUseOpenWifiActivity = enableUseOpenWifiActivity;
+ this.mNetworkAvailableNotificationChannelId = networkAvailableNotificationChannelId;
+ }
+
+ protected NetworkScorerAppData(Parcel in) {
+ packageUid = in.readInt();
+ mRecommendationService = ComponentName.readFromParcel(in);
+ mRecommendationServiceLabel = in.readString();
+ mEnableUseOpenWifiActivity = ComponentName.readFromParcel(in);
+ mNetworkAvailableNotificationChannelId = in.readString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(packageUid);
+ ComponentName.writeToParcel(mRecommendationService, dest);
+ dest.writeString(mRecommendationServiceLabel);
+ ComponentName.writeToParcel(mEnableUseOpenWifiActivity, dest);
+ dest.writeString(mNetworkAvailableNotificationChannelId);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @android.annotation.NonNull Creator<NetworkScorerAppData> CREATOR =
+ new Creator<NetworkScorerAppData>() {
+ @Override
+ public NetworkScorerAppData createFromParcel(Parcel in) {
+ return new NetworkScorerAppData(in);
+ }
+
+ @Override
+ public NetworkScorerAppData[] newArray(int size) {
+ return new NetworkScorerAppData[size];
+ }
+ };
+
+ public String getRecommendationServicePackageName() {
+ return mRecommendationService.getPackageName();
+ }
+
+ public ComponentName getRecommendationServiceComponent() {
+ return mRecommendationService;
+ }
+
+ @Nullable
+ public ComponentName getEnableUseOpenWifiActivity() {
+ return mEnableUseOpenWifiActivity;
+ }
+
+ @Nullable
+ public String getRecommendationServiceLabel() {
+ return mRecommendationServiceLabel;
+ }
+
+ @Nullable
+ public String getNetworkAvailableNotificationChannelId() {
+ return mNetworkAvailableNotificationChannelId;
+ }
+
+ @Override
+ public String toString() {
+ return "NetworkScorerAppData{" +
+ "packageUid=" + packageUid +
+ ", mRecommendationService=" + mRecommendationService +
+ ", mRecommendationServiceLabel=" + mRecommendationServiceLabel +
+ ", mEnableUseOpenWifiActivity=" + mEnableUseOpenWifiActivity +
+ ", mNetworkAvailableNotificationChannelId=" +
+ mNetworkAvailableNotificationChannelId +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ NetworkScorerAppData that = (NetworkScorerAppData) o;
+ return packageUid == that.packageUid &&
+ Objects.equals(mRecommendationService, that.mRecommendationService) &&
+ Objects.equals(mRecommendationServiceLabel, that.mRecommendationServiceLabel) &&
+ Objects.equals(mEnableUseOpenWifiActivity, that.mEnableUseOpenWifiActivity) &&
+ Objects.equals(mNetworkAvailableNotificationChannelId,
+ that.mNetworkAvailableNotificationChannelId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(packageUid, mRecommendationService, mRecommendationServiceLabel,
+ mEnableUseOpenWifiActivity, mNetworkAvailableNotificationChannelId);
+ }
+}
diff --git a/android/net/NetworkSpecifier.java b/android/net/NetworkSpecifier.java
new file mode 100644
index 0000000..2bc3eb5
--- /dev/null
+++ b/android/net/NetworkSpecifier.java
@@ -0,0 +1,76 @@
+/*
+ * 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 android.net;
+
+/**
+ * Describes specific properties of a requested network for use in a {@link NetworkRequest}.
+ *
+ * Applications cannot instantiate this class by themselves, but can obtain instances of
+ * subclasses of this class via other APIs.
+ */
+public abstract class NetworkSpecifier {
+ public NetworkSpecifier() {}
+
+ /**
+ * Returns true if a request with this {@link NetworkSpecifier} is satisfied by a network
+ * with the given NetworkSpecifier.
+ *
+ * @hide
+ */
+ public abstract boolean satisfiedBy(NetworkSpecifier other);
+
+ /**
+ * Optional method which can be overridden by concrete implementations of NetworkSpecifier to
+ * check a self-reported UID. A concrete implementation may contain a UID which would be self-
+ * reported by the caller (since NetworkSpecifier implementations should be non-mutable). This
+ * function is called by ConnectivityService and is passed the actual UID of the caller -
+ * allowing the verification of the self-reported UID. In cases of mismatch the implementation
+ * should throw a SecurityException.
+ *
+ * @param requestorUid The UID of the requestor as obtained from its binder.
+ *
+ * @hide
+ */
+ public void assertValidFromUid(int requestorUid) {
+ // empty
+ }
+
+ /**
+ * Optional method which can be overridden by concrete implementations of NetworkSpecifier to
+ * perform any redaction of information from the NetworkSpecifier, e.g. if it contains
+ * sensitive information. The default implementation simply returns the object itself - i.e.
+ * no information is redacted. A concrete implementation may return a modified (copy) of the
+ * NetworkSpecifier, or even return a null to fully remove all information.
+ * <p>
+ * This method is relevant to NetworkSpecifier objects used by agents - those are shared with
+ * apps by default. Some agents may store sensitive matching information in the specifier,
+ * e.g. a Wi-Fi SSID (which should not be shared since it may leak location). Those classes
+ * can redact to a null. Other agents use the Network Specifier to share public information
+ * with apps - those should not be redacted.
+ * <p>
+ * The default implementation redacts no information.
+ *
+ * @return A NetworkSpecifier object to be passed along to the requesting app.
+ *
+ * @hide
+ */
+ public NetworkSpecifier redact() {
+ // TODO (b/122160111): convert default to null once all platform NetworkSpecifiers
+ // implement this method.
+ return this;
+ }
+}
diff --git a/android/net/NetworkStack.java b/android/net/NetworkStack.java
new file mode 100644
index 0000000..a46c410
--- /dev/null
+++ b/android/net/NetworkStack.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import static android.Manifest.permission.NETWORK_STACK;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.content.Context;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+/**
+ *
+ * Constants for client code communicating with the network stack service.
+ * @hide
+ */
+@SystemApi
+@TestApi
+public class NetworkStack {
+ /**
+ * Permission granted only to the NetworkStack APK, defined in NetworkStackStub with signature
+ * protection level.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final String PERMISSION_MAINLINE_NETWORK_STACK =
+ "android.permission.MAINLINE_NETWORK_STACK";
+
+ private NetworkStack() {}
+
+ /**
+ * If the NetworkStack, MAINLINE_NETWORK_STACK are not allowed for a particular process, throw a
+ * {@link SecurityException}.
+ *
+ * @param context {@link android.content.Context} for the process.
+ *
+ * @hide
+ */
+ public static void checkNetworkStackPermission(final @NonNull Context context) {
+ checkNetworkStackPermissionOr(context);
+ }
+
+ /**
+ * If the NetworkStack, MAINLINE_NETWORK_STACK or other specified permissions are not allowed
+ * for a particular process, throw a {@link SecurityException}.
+ *
+ * @param context {@link android.content.Context} for the process.
+ * @param otherPermissions The set of permissions that could be the candidate permissions , or
+ * empty string if none of other permissions needed.
+ * @hide
+ */
+ public static void checkNetworkStackPermissionOr(final @NonNull Context context,
+ final @NonNull String... otherPermissions) {
+ ArrayList<String> permissions = new ArrayList<String>(Arrays.asList(otherPermissions));
+ permissions.add(NETWORK_STACK);
+ permissions.add(PERMISSION_MAINLINE_NETWORK_STACK);
+ enforceAnyPermissionOf(context, permissions.toArray(new String[0]));
+ }
+
+ private static void enforceAnyPermissionOf(final @NonNull Context context,
+ final @NonNull String... permissions) {
+ if (!checkAnyPermissionOf(context, permissions)) {
+ throw new SecurityException("Requires one of the following permissions: "
+ + String.join(", ", permissions) + ".");
+ }
+ }
+
+ private static boolean checkAnyPermissionOf(final @NonNull Context context,
+ final @NonNull String... permissions) {
+ for (String permission : permissions) {
+ if (context.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/android/net/NetworkStackClient.java b/android/net/NetworkStackClient.java
new file mode 100644
index 0000000..99da637
--- /dev/null
+++ b/android/net/NetworkStackClient.java
@@ -0,0 +1,534 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.net.dhcp.DhcpServingParamsParcel;
+import android.net.dhcp.IDhcpServerCallbacks;
+import android.net.ip.IIpClientCallbacks;
+import android.net.util.SharedLog;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Environment;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.text.format.DateUtils;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Service used to communicate with the network stack, which is running in a separate module.
+ * @hide
+ */
+public class NetworkStackClient {
+ private static final String TAG = NetworkStackClient.class.getSimpleName();
+
+ private static final int NETWORKSTACK_TIMEOUT_MS = 10_000;
+ private static final String IN_PROCESS_SUFFIX = ".InProcess";
+ private static final String PREFS_FILE = "NetworkStackClientPrefs.xml";
+ private static final String PREF_KEY_LAST_CRASH_TIME = "lastcrash_time";
+ private static final String CONFIG_MIN_CRASH_INTERVAL_MS = "min_crash_interval";
+ private static final String CONFIG_MIN_UPTIME_BEFORE_CRASH_MS = "min_uptime_before_crash";
+ private static final String CONFIG_ALWAYS_RATELIMIT_NETWORKSTACK_CRASH =
+ "always_ratelimit_networkstack_crash";
+
+ // Even if the network stack is lost, do not crash the system more often than this.
+ // Connectivity would be broken, but if the user needs the device for something urgent
+ // (like calling emergency services) we should not bootloop the device.
+ // This is the default value: the actual value can be adjusted via device config.
+ private static final long DEFAULT_MIN_CRASH_INTERVAL_MS = 6 * DateUtils.HOUR_IN_MILLIS;
+
+ // Even if the network stack is lost, do not crash the system server if it was less than
+ // this much after boot. This avoids bootlooping the device, and crashes should address very
+ // infrequent failures, not failures on boot.
+ private static final long DEFAULT_MIN_UPTIME_BEFORE_CRASH_MS = 30 * DateUtils.MINUTE_IN_MILLIS;
+
+ private static NetworkStackClient sInstance;
+
+ @NonNull
+ @GuardedBy("mPendingNetStackRequests")
+ private final ArrayList<NetworkStackCallback> mPendingNetStackRequests = new ArrayList<>();
+ @Nullable
+ @GuardedBy("mPendingNetStackRequests")
+ private INetworkStackConnector mConnector;
+
+ @GuardedBy("mLog")
+ private final SharedLog mLog = new SharedLog(TAG);
+
+ private volatile boolean mWasSystemServerInitialized = false;
+
+ @GuardedBy("mHealthListeners")
+ private final ArraySet<NetworkStackHealthListener> mHealthListeners = new ArraySet<>();
+
+ private interface NetworkStackCallback {
+ void onNetworkStackConnected(INetworkStackConnector connector);
+ }
+
+ /**
+ * Callback interface for severe failures of the NetworkStack.
+ *
+ * <p>Useful for health monitors such as PackageWatchdog.
+ */
+ public interface NetworkStackHealthListener {
+ /**
+ * Called when there is a severe failure of the network stack.
+ * @param packageName Package name of the network stack.
+ */
+ void onNetworkStackFailure(@NonNull String packageName);
+ }
+
+ private NetworkStackClient() { }
+
+ /**
+ * Get the NetworkStackClient singleton instance.
+ */
+ public static synchronized NetworkStackClient getInstance() {
+ if (sInstance == null) {
+ sInstance = new NetworkStackClient();
+ }
+ return sInstance;
+ }
+
+ /**
+ * Add a {@link NetworkStackHealthListener} to listen to network stack health events.
+ */
+ public void registerHealthListener(@NonNull NetworkStackHealthListener listener) {
+ synchronized (mHealthListeners) {
+ mHealthListeners.add(listener);
+ }
+ }
+
+ /**
+ * Create a DHCP server according to the specified parameters.
+ *
+ * <p>The server will be returned asynchronously through the provided callbacks.
+ */
+ public void makeDhcpServer(final String ifName, final DhcpServingParamsParcel params,
+ final IDhcpServerCallbacks cb) {
+ requestConnector(connector -> {
+ try {
+ connector.makeDhcpServer(ifName, params, cb);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ });
+ }
+
+ /**
+ * Create an IpClient on the specified interface.
+ *
+ * <p>The IpClient will be returned asynchronously through the provided callbacks.
+ */
+ public void makeIpClient(String ifName, IIpClientCallbacks cb) {
+ requestConnector(connector -> {
+ try {
+ connector.makeIpClient(ifName, cb);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ });
+ }
+
+ /**
+ * Create a NetworkMonitor.
+ *
+ * <p>The INetworkMonitor will be returned asynchronously through the provided callbacks.
+ */
+ public void makeNetworkMonitor(Network network, String name, INetworkMonitorCallbacks cb) {
+ requestConnector(connector -> {
+ try {
+ connector.makeNetworkMonitor(network, name, cb);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ });
+ }
+
+ /**
+ * Get an instance of the IpMemoryStore.
+ *
+ * <p>The IpMemoryStore will be returned asynchronously through the provided callbacks.
+ */
+ public void fetchIpMemoryStore(IIpMemoryStoreCallbacks cb) {
+ requestConnector(connector -> {
+ try {
+ connector.fetchIpMemoryStore(cb);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ });
+ }
+
+ private class NetworkStackConnection implements ServiceConnection {
+ @NonNull
+ private final Context mContext;
+ @NonNull
+ private final String mPackageName;
+
+ private NetworkStackConnection(@NonNull Context context, @NonNull String packageName) {
+ mContext = context;
+ mPackageName = packageName;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ logi("Network stack service connected");
+ registerNetworkStackService(service);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ // onServiceDisconnected is not being called on device shutdown, so this method being
+ // called always indicates a bad state for the system server.
+ // This code path is only run by the system server: only the system server binds
+ // to the NetworkStack as a service. Other processes get the NetworkStack from
+ // the ServiceManager.
+ maybeCrashWithTerribleFailure("Lost network stack", mContext, mPackageName);
+ }
+ }
+
+ private void registerNetworkStackService(@NonNull IBinder service) {
+ final INetworkStackConnector connector = INetworkStackConnector.Stub.asInterface(service);
+
+ ServiceManager.addService(Context.NETWORK_STACK_SERVICE, service, false /* allowIsolated */,
+ DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
+ log("Network stack service registered");
+
+ final ArrayList<NetworkStackCallback> requests;
+ synchronized (mPendingNetStackRequests) {
+ requests = new ArrayList<>(mPendingNetStackRequests);
+ mPendingNetStackRequests.clear();
+ mConnector = connector;
+ }
+
+ for (NetworkStackCallback r : requests) {
+ r.onNetworkStackConnected(connector);
+ }
+ }
+
+ /**
+ * Initialize the network stack. Should be called only once on device startup, before any
+ * client attempts to use the network stack.
+ */
+ public void init() {
+ log("Network stack init");
+ mWasSystemServerInitialized = true;
+ }
+
+ /**
+ * Start the network stack. Should be called only once on device startup.
+ *
+ * <p>This method will start the network stack either in the network stack process, or inside
+ * the system server on devices that do not support the network stack module. The network stack
+ * connector will then be delivered asynchronously to clients that requested it before it was
+ * started.
+ */
+ public void start(Context context) {
+ log("Starting network stack");
+
+ final PackageManager pm = context.getPackageManager();
+
+ // Try to bind in-process if the device was shipped with an in-process version
+ Intent intent = getNetworkStackIntent(pm, true /* inSystemProcess */);
+
+ // Otherwise use the updatable module version
+ if (intent == null) {
+ intent = getNetworkStackIntent(pm, false /* inSystemProcess */);
+ log("Starting network stack process");
+ } else {
+ log("Starting network stack in-process");
+ }
+
+ if (intent == null) {
+ maybeCrashWithTerribleFailure("Could not resolve the network stack", context, null);
+ return;
+ }
+
+ final String packageName = intent.getComponent().getPackageName();
+
+ // Start the network stack. The service will be added to the service manager in
+ // NetworkStackConnection.onServiceConnected().
+ if (!context.bindServiceAsUser(intent, new NetworkStackConnection(context, packageName),
+ Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.SYSTEM)) {
+ maybeCrashWithTerribleFailure(
+ "Could not bind to network stack in-process, or in app with " + intent,
+ context, packageName);
+ return;
+ }
+
+ log("Network stack service start requested");
+ }
+
+ @Nullable
+ private Intent getNetworkStackIntent(@NonNull PackageManager pm, boolean inSystemProcess) {
+ final String baseAction = INetworkStackConnector.class.getName();
+ final Intent intent =
+ new Intent(inSystemProcess ? baseAction + IN_PROCESS_SUFFIX : baseAction);
+ final ComponentName comp = intent.resolveSystemService(pm, 0);
+
+ if (comp == null) {
+ return null;
+ }
+ intent.setComponent(comp);
+
+ int uid = -1;
+ try {
+ uid = pm.getPackageUidAsUser(comp.getPackageName(), UserHandle.USER_SYSTEM);
+ } catch (PackageManager.NameNotFoundException e) {
+ logWtf("Network stack package not found", e);
+ // Fall through
+ }
+
+ final int expectedUid = inSystemProcess ? Process.SYSTEM_UID : Process.NETWORK_STACK_UID;
+ if (uid != expectedUid) {
+ throw new SecurityException("Invalid network stack UID: " + uid);
+ }
+
+ if (!inSystemProcess) {
+ checkNetworkStackPermission(pm, comp);
+ }
+
+ return intent;
+ }
+
+ private void checkNetworkStackPermission(
+ @NonNull PackageManager pm, @NonNull ComponentName comp) {
+ final int hasPermission =
+ pm.checkPermission(PERMISSION_MAINLINE_NETWORK_STACK, comp.getPackageName());
+ if (hasPermission != PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "Network stack does not have permission " + PERMISSION_MAINLINE_NETWORK_STACK);
+ }
+ }
+
+ private void maybeCrashWithTerribleFailure(@NonNull String message,
+ @NonNull Context context, @Nullable String packageName) {
+ logWtf(message, null);
+ // uptime is monotonic even after a framework restart
+ final long uptime = SystemClock.elapsedRealtime();
+ final long now = System.currentTimeMillis();
+ final long minCrashIntervalMs = DeviceConfig.getLong(DeviceConfig.NAMESPACE_CONNECTIVITY,
+ CONFIG_MIN_CRASH_INTERVAL_MS, DEFAULT_MIN_CRASH_INTERVAL_MS);
+ final long minUptimeBeforeCrash = DeviceConfig.getLong(DeviceConfig.NAMESPACE_CONNECTIVITY,
+ CONFIG_MIN_UPTIME_BEFORE_CRASH_MS, DEFAULT_MIN_UPTIME_BEFORE_CRASH_MS);
+ final boolean alwaysRatelimit = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CONNECTIVITY,
+ CONFIG_ALWAYS_RATELIMIT_NETWORKSTACK_CRASH, false);
+
+ final SharedPreferences prefs = getSharedPreferences(context);
+ final long lastCrashTime = tryGetLastCrashTime(prefs);
+
+ // Only crash if there was enough time since boot, and (if known) enough time passed since
+ // the last crash.
+ // time and lastCrashTime may be unreliable if devices have incorrect clock time, but they
+ // are only used to limit the number of crashes compared to only using the time since boot,
+ // which would also be OK behavior by itself.
+ // - If lastCrashTime is incorrectly more than the current time, only look at uptime
+ // - If it is much less than current time, only look at uptime
+ // - If current time is during the next few hours after last crash time, don't crash.
+ // Considering that this only matters if last boot was some time ago, it's likely that
+ // time will be set correctly. Otherwise, not crashing is not a big problem anyway. Being
+ // in this last state would also not last for long since the window is only a few hours.
+ final boolean alwaysCrash = Build.IS_DEBUGGABLE && !alwaysRatelimit;
+ final boolean justBooted = uptime < minUptimeBeforeCrash;
+ final boolean haveLastCrashTime = (lastCrashTime != 0) && (lastCrashTime < now);
+ final boolean haveKnownRecentCrash =
+ haveLastCrashTime && (now < lastCrashTime + minCrashIntervalMs);
+ if (alwaysCrash || (!justBooted && !haveKnownRecentCrash)) {
+ // The system is not bound to its network stack (for example due to a crash in the
+ // network stack process): better crash rather than stay in a bad state where all
+ // networking is broken.
+ // Using device-encrypted SharedPreferences as DeviceConfig does not have a synchronous
+ // API to persist settings before a crash.
+ tryWriteLastCrashTime(prefs, now);
+ throw new IllegalStateException(message);
+ }
+
+ // Here the system crashed recently already. Inform listeners that something is
+ // definitely wrong.
+ if (packageName != null) {
+ final ArraySet<NetworkStackHealthListener> listeners;
+ synchronized (mHealthListeners) {
+ listeners = new ArraySet<>(mHealthListeners);
+ }
+ for (NetworkStackHealthListener listener : listeners) {
+ listener.onNetworkStackFailure(packageName);
+ }
+ }
+ }
+
+ @Nullable
+ private SharedPreferences getSharedPreferences(@NonNull Context context) {
+ try {
+ final File prefsFile = new File(
+ Environment.getDataSystemDeDirectory(UserHandle.USER_SYSTEM), PREFS_FILE);
+ return context.createDeviceProtectedStorageContext()
+ .getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
+ } catch (Throwable e) {
+ logWtf("Error loading shared preferences", e);
+ return null;
+ }
+ }
+
+ private long tryGetLastCrashTime(@Nullable SharedPreferences prefs) {
+ if (prefs == null) return 0L;
+ try {
+ return prefs.getLong(PREF_KEY_LAST_CRASH_TIME, 0L);
+ } catch (Throwable e) {
+ logWtf("Error getting last crash time", e);
+ return 0L;
+ }
+ }
+
+ private void tryWriteLastCrashTime(@Nullable SharedPreferences prefs, long value) {
+ if (prefs == null) return;
+ try {
+ prefs.edit().putLong(PREF_KEY_LAST_CRASH_TIME, value).commit();
+ } catch (Throwable e) {
+ logWtf("Error writing last crash time", e);
+ }
+ }
+
+ /**
+ * Log a message in the local log.
+ */
+ private void log(@NonNull String message) {
+ synchronized (mLog) {
+ mLog.log(message);
+ }
+ }
+
+ private void logWtf(@NonNull String message, @Nullable Throwable e) {
+ Slog.wtf(TAG, message);
+ synchronized (mLog) {
+ mLog.e(message, e);
+ }
+ }
+
+ private void loge(@NonNull String message, @Nullable Throwable e) {
+ synchronized (mLog) {
+ mLog.e(message, e);
+ }
+ }
+
+ /**
+ * Log a message in the local and system logs.
+ */
+ private void logi(@NonNull String message) {
+ synchronized (mLog) {
+ mLog.i(message);
+ }
+ }
+
+ /**
+ * For non-system server clients, get the connector registered by the system server.
+ */
+ private INetworkStackConnector getRemoteConnector() {
+ // Block until the NetworkStack connector is registered in ServiceManager.
+ // <p>This is only useful for non-system processes that do not have a way to be notified of
+ // registration completion. Adding a callback system would be too heavy weight considering
+ // that the connector is registered on boot, so it is unlikely that a client would request
+ // it before it is registered.
+ // TODO: consider blocking boot on registration and simplify much of the logic in this class
+ IBinder connector;
+ try {
+ final long before = System.currentTimeMillis();
+ while ((connector = ServiceManager.getService(Context.NETWORK_STACK_SERVICE)) == null) {
+ Thread.sleep(20);
+ if (System.currentTimeMillis() - before > NETWORKSTACK_TIMEOUT_MS) {
+ loge("Timeout waiting for NetworkStack connector", null);
+ return null;
+ }
+ }
+ } catch (InterruptedException e) {
+ loge("Error waiting for NetworkStack connector", e);
+ return null;
+ }
+
+ return INetworkStackConnector.Stub.asInterface(connector);
+ }
+
+ private void requestConnector(@NonNull NetworkStackCallback request) {
+ // TODO: PID check.
+ final int caller = Binder.getCallingUid();
+ if (caller != Process.SYSTEM_UID && !UserHandle.isSameApp(caller, Process.BLUETOOTH_UID)
+ && !UserHandle.isSameApp(caller, Process.PHONE_UID)) {
+ // Don't even attempt to obtain the connector and give a nice error message
+ throw new SecurityException(
+ "Only the system server should try to bind to the network stack.");
+ }
+
+ if (!mWasSystemServerInitialized) {
+ // The network stack is not being started in this process, e.g. this process is not
+ // the system server. Get a remote connector registered by the system server.
+ final INetworkStackConnector connector = getRemoteConnector();
+ synchronized (mPendingNetStackRequests) {
+ mConnector = connector;
+ }
+ request.onNetworkStackConnected(connector);
+ return;
+ }
+
+ final INetworkStackConnector connector;
+ synchronized (mPendingNetStackRequests) {
+ connector = mConnector;
+ if (connector == null) {
+ mPendingNetStackRequests.add(request);
+ return;
+ }
+ }
+
+ request.onNetworkStackConnected(connector);
+ }
+
+ /**
+ * Dump NetworkStackClient logs to the specified {@link PrintWriter}.
+ */
+ public void dump(PrintWriter pw) {
+ // dump is thread-safe on SharedLog
+ mLog.dump(null, pw, null);
+
+ final int requestsQueueLength;
+ synchronized (mPendingNetStackRequests) {
+ requestsQueueLength = mPendingNetStackRequests.size();
+ }
+
+ pw.println();
+ pw.println("pendingNetStackRequests length: " + requestsQueueLength);
+ }
+}
diff --git a/android/net/NetworkState.java b/android/net/NetworkState.java
new file mode 100644
index 0000000..292cf50
--- /dev/null
+++ b/android/net/NetworkState.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2011 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;
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Slog;
+
+/**
+ * Snapshot of network state.
+ *
+ * @hide
+ */
+public class NetworkState implements Parcelable {
+ private static final boolean SANITY_CHECK_ROAMING = false;
+
+ public static final NetworkState EMPTY = new NetworkState(null, null, null, null, null, null);
+
+ public final NetworkInfo networkInfo;
+ public final LinkProperties linkProperties;
+ public final NetworkCapabilities networkCapabilities;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public final Network network;
+ public final String subscriberId;
+ public final String networkId;
+
+ public NetworkState(NetworkInfo networkInfo, LinkProperties linkProperties,
+ NetworkCapabilities networkCapabilities, Network network, String subscriberId,
+ String networkId) {
+ this.networkInfo = networkInfo;
+ this.linkProperties = linkProperties;
+ this.networkCapabilities = networkCapabilities;
+ this.network = network;
+ this.subscriberId = subscriberId;
+ this.networkId = networkId;
+
+ // This object is an atomic view of a network, so the various components
+ // should always agree on roaming state.
+ if (SANITY_CHECK_ROAMING && networkInfo != null && networkCapabilities != null) {
+ if (networkInfo.isRoaming() == networkCapabilities
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)) {
+ Slog.wtf("NetworkState", "Roaming state disagreement between " + networkInfo
+ + " and " + networkCapabilities);
+ }
+ }
+ }
+
+ @UnsupportedAppUsage
+ public NetworkState(Parcel in) {
+ networkInfo = in.readParcelable(null);
+ linkProperties = in.readParcelable(null);
+ networkCapabilities = in.readParcelable(null);
+ network = in.readParcelable(null);
+ subscriberId = in.readString();
+ networkId = in.readString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeParcelable(networkInfo, flags);
+ out.writeParcelable(linkProperties, flags);
+ out.writeParcelable(networkCapabilities, flags);
+ out.writeParcelable(network, flags);
+ out.writeString(subscriberId);
+ out.writeString(networkId);
+ }
+
+ @UnsupportedAppUsage
+ public static final @android.annotation.NonNull Creator<NetworkState> CREATOR = new Creator<NetworkState>() {
+ @Override
+ public NetworkState createFromParcel(Parcel in) {
+ return new NetworkState(in);
+ }
+
+ @Override
+ public NetworkState[] newArray(int size) {
+ return new NetworkState[size];
+ }
+ };
+}
diff --git a/android/net/NetworkStats.java b/android/net/NetworkStats.java
new file mode 100644
index 0000000..e892b65
--- /dev/null
+++ b/android/net/NetworkStats.java
@@ -0,0 +1,1362 @@
+/*
+ * Copyright (C) 2011 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;
+
+import static android.os.Process.CLAT_UID;
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import libcore.util.EmptyArray;
+
+import java.io.CharArrayWriter;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Collection of active network statistics. Can contain summary details across
+ * all interfaces, or details with per-UID granularity. Internally stores data
+ * as a large table, closely matching {@code /proc/} data format. This structure
+ * optimizes for rapid in-memory comparison, but consider using
+ * {@link NetworkStatsHistory} when persisting.
+ *
+ * @hide
+ */
+// @NotThreadSafe
+public class NetworkStats implements Parcelable {
+ private static final String TAG = "NetworkStats";
+ /** {@link #iface} value when interface details unavailable. */
+ public static final String IFACE_ALL = null;
+ /** {@link #uid} value when UID details unavailable. */
+ public static final int UID_ALL = -1;
+ /** {@link #tag} value matching any tag. */
+ // TODO: Rename TAG_ALL to TAG_ANY.
+ public static final int TAG_ALL = -1;
+ /** {@link #set} value for all sets combined, not including debug sets. */
+ public static final int SET_ALL = -1;
+ /** {@link #set} value where background data is accounted. */
+ public static final int SET_DEFAULT = 0;
+ /** {@link #set} value where foreground data is accounted. */
+ public static final int SET_FOREGROUND = 1;
+ /** All {@link #set} value greater than SET_DEBUG_START are debug {@link #set} values. */
+ public static final int SET_DEBUG_START = 1000;
+ /** Debug {@link #set} value when the VPN stats are moved in. */
+ public static final int SET_DBG_VPN_IN = 1001;
+ /** Debug {@link #set} value when the VPN stats are moved out of a vpn UID. */
+ public static final int SET_DBG_VPN_OUT = 1002;
+
+ /** Include all interfaces when filtering */
+ public static final String[] INTERFACES_ALL = null;
+
+ /** {@link #tag} value for total data across all tags. */
+ // TODO: Rename TAG_NONE to TAG_ALL.
+ public static final int TAG_NONE = 0;
+
+ /** {@link #metered} value to account for all metered states. */
+ public static final int METERED_ALL = -1;
+ /** {@link #metered} value where native, unmetered data is accounted. */
+ public static final int METERED_NO = 0;
+ /** {@link #metered} value where metered data is accounted. */
+ public static final int METERED_YES = 1;
+
+ /** {@link #roaming} value to account for all roaming states. */
+ public static final int ROAMING_ALL = -1;
+ /** {@link #roaming} value where native, non-roaming data is accounted. */
+ public static final int ROAMING_NO = 0;
+ /** {@link #roaming} value where roaming data is accounted. */
+ public static final int ROAMING_YES = 1;
+
+ /** {@link #onDefaultNetwork} value to account for all default network states. */
+ public static final int DEFAULT_NETWORK_ALL = -1;
+ /** {@link #onDefaultNetwork} value to account for usage while not the default network. */
+ public static final int DEFAULT_NETWORK_NO = 0;
+ /** {@link #onDefaultNetwork} value to account for usage while the default network. */
+ public static final int DEFAULT_NETWORK_YES = 1;
+
+ /** Denotes a request for stats at the interface level. */
+ public static final int STATS_PER_IFACE = 0;
+ /** Denotes a request for stats at the interface and UID level. */
+ public static final int STATS_PER_UID = 1;
+
+ private static final String CLATD_INTERFACE_PREFIX = "v4-";
+ // Delta between IPv4 header (20b) and IPv6 header (40b).
+ // Used for correct stats accounting on clatd interfaces.
+ private static final int IPV4V6_HEADER_DELTA = 20;
+
+ // TODO: move fields to "mVariable" notation
+
+ /**
+ * {@link SystemClock#elapsedRealtime()} timestamp when this data was
+ * generated.
+ */
+ private long elapsedRealtime;
+ @UnsupportedAppUsage
+ private int size;
+ @UnsupportedAppUsage
+ private int capacity;
+ @UnsupportedAppUsage
+ private String[] iface;
+ @UnsupportedAppUsage
+ private int[] uid;
+ @UnsupportedAppUsage
+ private int[] set;
+ @UnsupportedAppUsage
+ private int[] tag;
+ @UnsupportedAppUsage
+ private int[] metered;
+ @UnsupportedAppUsage
+ private int[] roaming;
+ @UnsupportedAppUsage
+ private int[] defaultNetwork;
+ @UnsupportedAppUsage
+ private long[] rxBytes;
+ @UnsupportedAppUsage
+ private long[] rxPackets;
+ @UnsupportedAppUsage
+ private long[] txBytes;
+ @UnsupportedAppUsage
+ private long[] txPackets;
+ @UnsupportedAppUsage
+ private long[] operations;
+
+ public static class Entry {
+ @UnsupportedAppUsage
+ public String iface;
+ @UnsupportedAppUsage
+ public int uid;
+ @UnsupportedAppUsage
+ public int set;
+ @UnsupportedAppUsage
+ public int tag;
+ /**
+ * Note that this is only populated w/ the default value when read from /proc or written
+ * to disk. We merge in the correct value when reporting this value to clients of
+ * getSummary().
+ */
+ public int metered;
+ /**
+ * Note that this is only populated w/ the default value when read from /proc or written
+ * to disk. We merge in the correct value when reporting this value to clients of
+ * getSummary().
+ */
+ public int roaming;
+ /**
+ * Note that this is only populated w/ the default value when read from /proc or written
+ * to disk. We merge in the correct value when reporting this value to clients of
+ * getSummary().
+ */
+ public int defaultNetwork;
+ @UnsupportedAppUsage
+ public long rxBytes;
+ @UnsupportedAppUsage
+ public long rxPackets;
+ @UnsupportedAppUsage
+ public long txBytes;
+ @UnsupportedAppUsage
+ public long txPackets;
+ public long operations;
+
+ @UnsupportedAppUsage
+ public Entry() {
+ this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
+ }
+
+ public Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
+ this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets,
+ operations);
+ }
+
+ public Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets,
+ long txBytes, long txPackets, long operations) {
+ this(iface, uid, set, tag, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO,
+ rxBytes, rxPackets, txBytes, txPackets, operations);
+ }
+
+ public Entry(String iface, int uid, int set, int tag, int metered, int roaming,
+ int defaultNetwork, long rxBytes, long rxPackets, long txBytes, long txPackets,
+ long operations) {
+ this.iface = iface;
+ this.uid = uid;
+ this.set = set;
+ this.tag = tag;
+ this.metered = metered;
+ this.roaming = roaming;
+ this.defaultNetwork = defaultNetwork;
+ this.rxBytes = rxBytes;
+ this.rxPackets = rxPackets;
+ this.txBytes = txBytes;
+ this.txPackets = txPackets;
+ this.operations = operations;
+ }
+
+ public boolean isNegative() {
+ return rxBytes < 0 || rxPackets < 0 || txBytes < 0 || txPackets < 0 || operations < 0;
+ }
+
+ public boolean isEmpty() {
+ return rxBytes == 0 && rxPackets == 0 && txBytes == 0 && txPackets == 0
+ && operations == 0;
+ }
+
+ public void add(Entry another) {
+ this.rxBytes += another.rxBytes;
+ this.rxPackets += another.rxPackets;
+ this.txBytes += another.txBytes;
+ this.txPackets += another.txPackets;
+ this.operations += another.operations;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("iface=").append(iface);
+ builder.append(" uid=").append(uid);
+ builder.append(" set=").append(setToString(set));
+ builder.append(" tag=").append(tagToString(tag));
+ builder.append(" metered=").append(meteredToString(metered));
+ builder.append(" roaming=").append(roamingToString(roaming));
+ builder.append(" defaultNetwork=").append(defaultNetworkToString(defaultNetwork));
+ builder.append(" rxBytes=").append(rxBytes);
+ builder.append(" rxPackets=").append(rxPackets);
+ builder.append(" txBytes=").append(txBytes);
+ builder.append(" txPackets=").append(txPackets);
+ builder.append(" operations=").append(operations);
+ return builder.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof Entry) {
+ final Entry e = (Entry) o;
+ return uid == e.uid && set == e.set && tag == e.tag && metered == e.metered
+ && roaming == e.roaming && defaultNetwork == e.defaultNetwork
+ && rxBytes == e.rxBytes && rxPackets == e.rxPackets
+ && txBytes == e.txBytes && txPackets == e.txPackets
+ && operations == e.operations && iface.equals(e.iface);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(uid, set, tag, metered, roaming, defaultNetwork, iface);
+ }
+ }
+
+ @UnsupportedAppUsage
+ public NetworkStats(long elapsedRealtime, int initialSize) {
+ this.elapsedRealtime = elapsedRealtime;
+ this.size = 0;
+ if (initialSize > 0) {
+ this.capacity = initialSize;
+ this.iface = new String[initialSize];
+ this.uid = new int[initialSize];
+ this.set = new int[initialSize];
+ this.tag = new int[initialSize];
+ this.metered = new int[initialSize];
+ this.roaming = new int[initialSize];
+ this.defaultNetwork = new int[initialSize];
+ this.rxBytes = new long[initialSize];
+ this.rxPackets = new long[initialSize];
+ this.txBytes = new long[initialSize];
+ this.txPackets = new long[initialSize];
+ this.operations = new long[initialSize];
+ } else {
+ // Special case for use by NetworkStatsFactory to start out *really* empty.
+ clear();
+ }
+ }
+
+ @UnsupportedAppUsage
+ public NetworkStats(Parcel parcel) {
+ elapsedRealtime = parcel.readLong();
+ size = parcel.readInt();
+ capacity = parcel.readInt();
+ iface = parcel.createStringArray();
+ uid = parcel.createIntArray();
+ set = parcel.createIntArray();
+ tag = parcel.createIntArray();
+ metered = parcel.createIntArray();
+ roaming = parcel.createIntArray();
+ defaultNetwork = parcel.createIntArray();
+ rxBytes = parcel.createLongArray();
+ rxPackets = parcel.createLongArray();
+ txBytes = parcel.createLongArray();
+ txPackets = parcel.createLongArray();
+ operations = parcel.createLongArray();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(elapsedRealtime);
+ dest.writeInt(size);
+ dest.writeInt(capacity);
+ dest.writeStringArray(iface);
+ dest.writeIntArray(uid);
+ dest.writeIntArray(set);
+ dest.writeIntArray(tag);
+ dest.writeIntArray(metered);
+ dest.writeIntArray(roaming);
+ dest.writeIntArray(defaultNetwork);
+ dest.writeLongArray(rxBytes);
+ dest.writeLongArray(rxPackets);
+ dest.writeLongArray(txBytes);
+ dest.writeLongArray(txPackets);
+ dest.writeLongArray(operations);
+ }
+
+ @Override
+ public NetworkStats clone() {
+ final NetworkStats clone = new NetworkStats(elapsedRealtime, size);
+ NetworkStats.Entry entry = null;
+ for (int i = 0; i < size; i++) {
+ entry = getValues(i, entry);
+ clone.addValues(entry);
+ }
+ return clone;
+ }
+
+ /**
+ * Clear all data stored in this object.
+ */
+ public void clear() {
+ this.capacity = 0;
+ this.iface = EmptyArray.STRING;
+ this.uid = EmptyArray.INT;
+ this.set = EmptyArray.INT;
+ this.tag = EmptyArray.INT;
+ this.metered = EmptyArray.INT;
+ this.roaming = EmptyArray.INT;
+ this.defaultNetwork = EmptyArray.INT;
+ this.rxBytes = EmptyArray.LONG;
+ this.rxPackets = EmptyArray.LONG;
+ this.txBytes = EmptyArray.LONG;
+ this.txPackets = EmptyArray.LONG;
+ this.operations = EmptyArray.LONG;
+ }
+
+ @VisibleForTesting
+ public NetworkStats addIfaceValues(
+ String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) {
+ return addValues(
+ iface, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, 0L);
+ }
+
+ @VisibleForTesting
+ public NetworkStats addValues(String iface, int uid, int set, int tag, long rxBytes,
+ long rxPackets, long txBytes, long txPackets, long operations) {
+ return addValues(new Entry(
+ iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
+ }
+
+ @VisibleForTesting
+ public NetworkStats addValues(String iface, int uid, int set, int tag, int metered, int roaming,
+ int defaultNetwork, long rxBytes, long rxPackets, long txBytes, long txPackets,
+ long operations) {
+ return addValues(new Entry(
+ iface, uid, set, tag, metered, roaming, defaultNetwork, rxBytes, rxPackets,
+ txBytes, txPackets, operations));
+ }
+
+ /**
+ * Add new stats entry, copying from given {@link Entry}. The {@link Entry}
+ * object can be recycled across multiple calls.
+ */
+ public NetworkStats addValues(Entry entry) {
+ if (size >= capacity) {
+ final int newLength = Math.max(size, 10) * 3 / 2;
+ iface = Arrays.copyOf(iface, newLength);
+ uid = Arrays.copyOf(uid, newLength);
+ set = Arrays.copyOf(set, newLength);
+ tag = Arrays.copyOf(tag, newLength);
+ metered = Arrays.copyOf(metered, newLength);
+ roaming = Arrays.copyOf(roaming, newLength);
+ defaultNetwork = Arrays.copyOf(defaultNetwork, newLength);
+ rxBytes = Arrays.copyOf(rxBytes, newLength);
+ rxPackets = Arrays.copyOf(rxPackets, newLength);
+ txBytes = Arrays.copyOf(txBytes, newLength);
+ txPackets = Arrays.copyOf(txPackets, newLength);
+ operations = Arrays.copyOf(operations, newLength);
+ capacity = newLength;
+ }
+
+ setValues(size, entry);
+ size++;
+
+ return this;
+ }
+
+ private void setValues(int i, Entry entry) {
+ iface[i] = entry.iface;
+ uid[i] = entry.uid;
+ set[i] = entry.set;
+ tag[i] = entry.tag;
+ metered[i] = entry.metered;
+ roaming[i] = entry.roaming;
+ defaultNetwork[i] = entry.defaultNetwork;
+ rxBytes[i] = entry.rxBytes;
+ rxPackets[i] = entry.rxPackets;
+ txBytes[i] = entry.txBytes;
+ txPackets[i] = entry.txPackets;
+ operations[i] = entry.operations;
+ }
+
+ /**
+ * Return specific stats entry.
+ */
+ @UnsupportedAppUsage
+ public Entry getValues(int i, Entry recycle) {
+ final Entry entry = recycle != null ? recycle : new Entry();
+ entry.iface = iface[i];
+ entry.uid = uid[i];
+ entry.set = set[i];
+ entry.tag = tag[i];
+ entry.metered = metered[i];
+ entry.roaming = roaming[i];
+ entry.defaultNetwork = defaultNetwork[i];
+ entry.rxBytes = rxBytes[i];
+ entry.rxPackets = rxPackets[i];
+ entry.txBytes = txBytes[i];
+ entry.txPackets = txPackets[i];
+ entry.operations = operations[i];
+ return entry;
+ }
+
+ /**
+ * If @{code dest} is not equal to @{code src}, copy entry from index @{code src} to index
+ * @{code dest}.
+ */
+ private void maybeCopyEntry(int dest, int src) {
+ if (dest == src) return;
+ iface[dest] = iface[src];
+ uid[dest] = uid[src];
+ set[dest] = set[src];
+ tag[dest] = tag[src];
+ metered[dest] = metered[src];
+ roaming[dest] = roaming[src];
+ defaultNetwork[dest] = defaultNetwork[src];
+ rxBytes[dest] = rxBytes[src];
+ rxPackets[dest] = rxPackets[src];
+ txBytes[dest] = txBytes[src];
+ txPackets[dest] = txPackets[src];
+ operations[dest] = operations[src];
+ }
+
+ public long getElapsedRealtime() {
+ return elapsedRealtime;
+ }
+
+ public void setElapsedRealtime(long time) {
+ elapsedRealtime = time;
+ }
+
+ /**
+ * Return age of this {@link NetworkStats} object with respect to
+ * {@link SystemClock#elapsedRealtime()}.
+ */
+ public long getElapsedRealtimeAge() {
+ return SystemClock.elapsedRealtime() - elapsedRealtime;
+ }
+
+ @UnsupportedAppUsage
+ public int size() {
+ return size;
+ }
+
+ @VisibleForTesting
+ public int internalSize() {
+ return capacity;
+ }
+
+ @Deprecated
+ public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets,
+ long txBytes, long txPackets, long operations) {
+ return combineValues(
+ iface, uid, SET_DEFAULT, tag, rxBytes, rxPackets, txBytes,
+ txPackets, operations);
+ }
+
+ public NetworkStats combineValues(String iface, int uid, int set, int tag,
+ long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
+ return combineValues(new Entry(
+ iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
+ }
+
+ /**
+ * Combine given values with an existing row, or create a new row if
+ * {@link #findIndex(String, int, int, int, int)} is unable to find match. Can
+ * also be used to subtract values from existing rows.
+ */
+ @UnsupportedAppUsage
+ public NetworkStats combineValues(Entry entry) {
+ final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag, entry.metered,
+ entry.roaming, entry.defaultNetwork);
+ if (i == -1) {
+ // only create new entry when positive contribution
+ addValues(entry);
+ } else {
+ rxBytes[i] += entry.rxBytes;
+ rxPackets[i] += entry.rxPackets;
+ txBytes[i] += entry.txBytes;
+ txPackets[i] += entry.txPackets;
+ operations[i] += entry.operations;
+ }
+ return this;
+ }
+
+ /**
+ * Combine all values from another {@link NetworkStats} into this object.
+ */
+ @UnsupportedAppUsage
+ public void combineAllValues(NetworkStats another) {
+ NetworkStats.Entry entry = null;
+ for (int i = 0; i < another.size; i++) {
+ entry = another.getValues(i, entry);
+ combineValues(entry);
+ }
+ }
+
+ /**
+ * Find first stats index that matches the requested parameters.
+ */
+ public int findIndex(String iface, int uid, int set, int tag, int metered, int roaming,
+ int defaultNetwork) {
+ for (int i = 0; i < size; i++) {
+ if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
+ && metered == this.metered[i] && roaming == this.roaming[i]
+ && defaultNetwork == this.defaultNetwork[i]
+ && Objects.equals(iface, this.iface[i])) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Find first stats index that matches the requested parameters, starting
+ * search around the hinted index as an optimization.
+ */
+ @VisibleForTesting
+ public int findIndexHinted(String iface, int uid, int set, int tag, int metered, int roaming,
+ int defaultNetwork, int hintIndex) {
+ for (int offset = 0; offset < size; offset++) {
+ final int halfOffset = offset / 2;
+
+ // search outwards from hint index, alternating forward and backward
+ final int i;
+ if (offset % 2 == 0) {
+ i = (hintIndex + halfOffset) % size;
+ } else {
+ i = (size + hintIndex - halfOffset - 1) % size;
+ }
+
+ if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
+ && metered == this.metered[i] && roaming == this.roaming[i]
+ && defaultNetwork == this.defaultNetwork[i]
+ && Objects.equals(iface, this.iface[i])) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Splice in {@link #operations} from the given {@link NetworkStats} based
+ * on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface},
+ * since operation counts are at data layer.
+ */
+ public void spliceOperationsFrom(NetworkStats stats) {
+ for (int i = 0; i < size; i++) {
+ final int j = stats.findIndex(iface[i], uid[i], set[i], tag[i], metered[i], roaming[i],
+ defaultNetwork[i]);
+ if (j == -1) {
+ operations[i] = 0;
+ } else {
+ operations[i] = stats.operations[j];
+ }
+ }
+ }
+
+ /**
+ * Return list of unique interfaces known by this data structure.
+ */
+ public String[] getUniqueIfaces() {
+ final HashSet<String> ifaces = new HashSet<String>();
+ for (String iface : this.iface) {
+ if (iface != IFACE_ALL) {
+ ifaces.add(iface);
+ }
+ }
+ return ifaces.toArray(new String[ifaces.size()]);
+ }
+
+ /**
+ * Return list of unique UIDs known by this data structure.
+ */
+ @UnsupportedAppUsage
+ public int[] getUniqueUids() {
+ final SparseBooleanArray uids = new SparseBooleanArray();
+ for (int uid : this.uid) {
+ uids.put(uid, true);
+ }
+
+ final int size = uids.size();
+ final int[] result = new int[size];
+ for (int i = 0; i < size; i++) {
+ result[i] = uids.keyAt(i);
+ }
+ return result;
+ }
+
+ /**
+ * Return total bytes represented by this snapshot object, usually used when
+ * checking if a {@link #subtract(NetworkStats)} delta passes a threshold.
+ */
+ @UnsupportedAppUsage
+ public long getTotalBytes() {
+ final Entry entry = getTotal(null);
+ return entry.rxBytes + entry.txBytes;
+ }
+
+ /**
+ * Return total of all fields represented by this snapshot object.
+ */
+ @UnsupportedAppUsage
+ public Entry getTotal(Entry recycle) {
+ return getTotal(recycle, null, UID_ALL, false);
+ }
+
+ /**
+ * Return total of all fields represented by this snapshot object matching
+ * the requested {@link #uid}.
+ */
+ @UnsupportedAppUsage
+ public Entry getTotal(Entry recycle, int limitUid) {
+ return getTotal(recycle, null, limitUid, false);
+ }
+
+ /**
+ * Return total of all fields represented by this snapshot object matching
+ * the requested {@link #iface}.
+ */
+ public Entry getTotal(Entry recycle, HashSet<String> limitIface) {
+ return getTotal(recycle, limitIface, UID_ALL, false);
+ }
+
+ @UnsupportedAppUsage
+ public Entry getTotalIncludingTags(Entry recycle) {
+ return getTotal(recycle, null, UID_ALL, true);
+ }
+
+ /**
+ * Return total of all fields represented by this snapshot object matching
+ * the requested {@link #iface} and {@link #uid}.
+ *
+ * @param limitIface Set of {@link #iface} to include in total; or {@code
+ * null} to include all ifaces.
+ */
+ private Entry getTotal(
+ Entry recycle, HashSet<String> limitIface, int limitUid, boolean includeTags) {
+ final Entry entry = recycle != null ? recycle : new Entry();
+
+ entry.iface = IFACE_ALL;
+ entry.uid = limitUid;
+ entry.set = SET_ALL;
+ entry.tag = TAG_NONE;
+ entry.metered = METERED_ALL;
+ entry.roaming = ROAMING_ALL;
+ entry.defaultNetwork = DEFAULT_NETWORK_ALL;
+ entry.rxBytes = 0;
+ entry.rxPackets = 0;
+ entry.txBytes = 0;
+ entry.txPackets = 0;
+ entry.operations = 0;
+
+ for (int i = 0; i < size; i++) {
+ final boolean matchesUid = (limitUid == UID_ALL) || (limitUid == uid[i]);
+ final boolean matchesIface = (limitIface == null) || (limitIface.contains(iface[i]));
+
+ if (matchesUid && matchesIface) {
+ // skip specific tags, since already counted in TAG_NONE
+ if (tag[i] != TAG_NONE && !includeTags) continue;
+
+ entry.rxBytes += rxBytes[i];
+ entry.rxPackets += rxPackets[i];
+ entry.txBytes += txBytes[i];
+ entry.txPackets += txPackets[i];
+ entry.operations += operations[i];
+ }
+ }
+ return entry;
+ }
+
+ /**
+ * Fast path for battery stats.
+ */
+ public long getTotalPackets() {
+ long total = 0;
+ for (int i = size-1; i >= 0; i--) {
+ total += rxPackets[i] + txPackets[i];
+ }
+ return total;
+ }
+
+ /**
+ * Subtract the given {@link NetworkStats}, effectively leaving the delta
+ * between two snapshots in time. Assumes that statistics rows collect over
+ * time, and that none of them have disappeared.
+ */
+ public NetworkStats subtract(NetworkStats right) {
+ return subtract(this, right, null, null);
+ }
+
+ /**
+ * Subtract the two given {@link NetworkStats} objects, returning the delta
+ * between two snapshots in time. Assumes that statistics rows collect over
+ * time, and that none of them have disappeared.
+ * <p>
+ * If counters have rolled backwards, they are clamped to {@code 0} and
+ * reported to the given {@link NonMonotonicObserver}.
+ */
+ public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right,
+ NonMonotonicObserver<C> observer, C cookie) {
+ return subtract(left, right, observer, cookie, null);
+ }
+
+ /**
+ * Subtract the two given {@link NetworkStats} objects, returning the delta
+ * between two snapshots in time. Assumes that statistics rows collect over
+ * time, and that none of them have disappeared.
+ * <p>
+ * If counters have rolled backwards, they are clamped to {@code 0} and
+ * reported to the given {@link NonMonotonicObserver}.
+ * <p>
+ * If <var>recycle</var> is supplied, this NetworkStats object will be
+ * reused (and returned) as the result if it is large enough to contain
+ * the data.
+ */
+ public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right,
+ NonMonotonicObserver<C> observer, C cookie, NetworkStats recycle) {
+ long deltaRealtime = left.elapsedRealtime - right.elapsedRealtime;
+ if (deltaRealtime < 0) {
+ if (observer != null) {
+ observer.foundNonMonotonic(left, -1, right, -1, cookie);
+ }
+ deltaRealtime = 0;
+ }
+
+ // result will have our rows, and elapsed time between snapshots
+ final Entry entry = new Entry();
+ final NetworkStats result;
+ if (recycle != null && recycle.capacity >= left.size) {
+ result = recycle;
+ result.size = 0;
+ result.elapsedRealtime = deltaRealtime;
+ } else {
+ result = new NetworkStats(deltaRealtime, left.size);
+ }
+ for (int i = 0; i < left.size; i++) {
+ entry.iface = left.iface[i];
+ entry.uid = left.uid[i];
+ entry.set = left.set[i];
+ entry.tag = left.tag[i];
+ entry.metered = left.metered[i];
+ entry.roaming = left.roaming[i];
+ entry.defaultNetwork = left.defaultNetwork[i];
+ entry.rxBytes = left.rxBytes[i];
+ entry.rxPackets = left.rxPackets[i];
+ entry.txBytes = left.txBytes[i];
+ entry.txPackets = left.txPackets[i];
+ entry.operations = left.operations[i];
+
+ // find remote row that matches, and subtract
+ final int j = right.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag,
+ entry.metered, entry.roaming, entry.defaultNetwork, i);
+ if (j != -1) {
+ // Found matching row, subtract remote value.
+ entry.rxBytes -= right.rxBytes[j];
+ entry.rxPackets -= right.rxPackets[j];
+ entry.txBytes -= right.txBytes[j];
+ entry.txPackets -= right.txPackets[j];
+ entry.operations -= right.operations[j];
+ }
+
+ if (entry.isNegative()) {
+ if (observer != null) {
+ observer.foundNonMonotonic(left, i, right, j, cookie);
+ }
+ entry.rxBytes = Math.max(entry.rxBytes, 0);
+ entry.rxPackets = Math.max(entry.rxPackets, 0);
+ entry.txBytes = Math.max(entry.txBytes, 0);
+ entry.txPackets = Math.max(entry.txPackets, 0);
+ entry.operations = Math.max(entry.operations, 0);
+ }
+
+ result.addValues(entry);
+ }
+
+ return result;
+ }
+
+ /**
+ * Calculate and apply adjustments to captured statistics for 464xlat traffic counted twice.
+ *
+ * <p>This mutates both base and stacked traffic stats, to account respectively for
+ * double-counted traffic and IPv4/IPv6 header size difference.
+ *
+ * <p>For 464xlat traffic, xt_qtaguid sees every IPv4 packet twice, once as a native IPv4
+ * packet on the stacked interface, and once as translated to an IPv6 packet on the
+ * base interface. For correct stats accounting on the base interface, if using xt_qtaguid,
+ * every rx 464xlat packet needs to be subtracted from the root UID on the base interface
+ * (http://b/12249687, http:/b/33681750), and every tx 464xlat packet which was counted onto
+ * clat uid should be ignored.
+ *
+ * As for eBPF, the per uid stats is collected by different hook, the rx packets on base
+ * interface will not be counted. Thus, the adjustment on root uid is not needed. However, the
+ * tx traffic counted in the same way xt_qtaguid does, so the traffic on clat uid still
+ * needs to be ignored.
+ *
+ * <p>This method will behave fine if {@code stackedIfaces} is an non-synchronized but add-only
+ * {@code ConcurrentHashMap}
+ * @param baseTraffic Traffic on the base interfaces. Will be mutated.
+ * @param stackedTraffic Stats with traffic stacked on top of our ifaces. Will also be mutated.
+ * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both.
+ * @param useBpfStats True if eBPF is in use.
+ */
+ public static void apply464xlatAdjustments(NetworkStats baseTraffic,
+ NetworkStats stackedTraffic, Map<String, String> stackedIfaces, boolean useBpfStats) {
+ // Total 464xlat traffic to subtract from uid 0 on all base interfaces.
+ // stackedIfaces may grow afterwards, but NetworkStats will just be resized automatically.
+ final NetworkStats adjustments = new NetworkStats(0, stackedIfaces.size());
+
+ // For recycling
+ Entry entry = null;
+ Entry adjust = new NetworkStats.Entry(IFACE_ALL, 0, 0, 0, 0, 0, 0, 0L, 0L, 0L, 0L, 0L);
+
+ for (int i = 0; i < stackedTraffic.size; i++) {
+ entry = stackedTraffic.getValues(i, entry);
+ if (entry.iface == null || !entry.iface.startsWith(CLATD_INTERFACE_PREFIX)) {
+ continue;
+ }
+ final String baseIface = stackedIfaces.get(entry.iface);
+ if (baseIface == null) {
+ continue;
+ }
+ // Subtract xt_qtaguid 464lat rx traffic seen for the root UID on the current base
+ // interface. As for eBPF, the per uid stats is collected by different hook, the rx
+ // packets on base interface will not be counted.
+ adjust.iface = baseIface;
+ if (!useBpfStats) {
+ adjust.rxBytes = -(entry.rxBytes + entry.rxPackets * IPV4V6_HEADER_DELTA);
+ adjust.rxPackets = -entry.rxPackets;
+ }
+ adjustments.combineValues(adjust);
+
+ // For 464xlat traffic, per uid stats only counts the bytes of the native IPv4 packet
+ // sent on the stacked interface with prefix "v4-" and drops the IPv6 header size after
+ // unwrapping. To account correctly for on-the-wire traffic, add the 20 additional bytes
+ // difference for all packets (http://b/12249687, http:/b/33681750).
+ entry.rxBytes += entry.rxPackets * IPV4V6_HEADER_DELTA;
+ entry.txBytes += entry.txPackets * IPV4V6_HEADER_DELTA;
+ stackedTraffic.setValues(i, entry);
+ }
+
+ // Traffic on clat uid is v6 tx traffic that is already counted with app uid on the stacked
+ // v4 interface, so it needs to be removed to avoid double-counting.
+ baseTraffic.removeUids(new int[] {CLAT_UID});
+ baseTraffic.combineAllValues(adjustments);
+ }
+
+ /**
+ * Calculate and apply adjustments to captured statistics for 464xlat traffic counted twice.
+ *
+ * <p>This mutates the object this method is called on. Equivalent to calling
+ * {@link #apply464xlatAdjustments(NetworkStats, NetworkStats, Map)} with {@code this} as
+ * base and stacked traffic.
+ * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both.
+ */
+ public void apply464xlatAdjustments(Map<String, String> stackedIfaces, boolean useBpfStats) {
+ apply464xlatAdjustments(this, this, stackedIfaces, useBpfStats);
+ }
+
+ /**
+ * Return total statistics grouped by {@link #iface}; doesn't mutate the
+ * original structure.
+ */
+ public NetworkStats groupedByIface() {
+ final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
+
+ final Entry entry = new Entry();
+ entry.uid = UID_ALL;
+ entry.set = SET_ALL;
+ entry.tag = TAG_NONE;
+ entry.metered = METERED_ALL;
+ entry.roaming = ROAMING_ALL;
+ entry.defaultNetwork = DEFAULT_NETWORK_ALL;
+ entry.operations = 0L;
+
+ for (int i = 0; i < size; i++) {
+ // skip specific tags, since already counted in TAG_NONE
+ if (tag[i] != TAG_NONE) continue;
+
+ entry.iface = iface[i];
+ entry.rxBytes = rxBytes[i];
+ entry.rxPackets = rxPackets[i];
+ entry.txBytes = txBytes[i];
+ entry.txPackets = txPackets[i];
+ stats.combineValues(entry);
+ }
+
+ return stats;
+ }
+
+ /**
+ * Return total statistics grouped by {@link #uid}; doesn't mutate the
+ * original structure.
+ */
+ public NetworkStats groupedByUid() {
+ final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
+
+ final Entry entry = new Entry();
+ entry.iface = IFACE_ALL;
+ entry.set = SET_ALL;
+ entry.tag = TAG_NONE;
+ entry.metered = METERED_ALL;
+ entry.roaming = ROAMING_ALL;
+ entry.defaultNetwork = DEFAULT_NETWORK_ALL;
+
+ for (int i = 0; i < size; i++) {
+ // skip specific tags, since already counted in TAG_NONE
+ if (tag[i] != TAG_NONE) continue;
+
+ entry.uid = uid[i];
+ entry.rxBytes = rxBytes[i];
+ entry.rxPackets = rxPackets[i];
+ entry.txBytes = txBytes[i];
+ entry.txPackets = txPackets[i];
+ entry.operations = operations[i];
+ stats.combineValues(entry);
+ }
+
+ return stats;
+ }
+
+ /**
+ * Remove all rows that match one of specified UIDs.
+ */
+ public void removeUids(int[] uids) {
+ int nextOutputEntry = 0;
+ for (int i = 0; i < size; i++) {
+ if (!ArrayUtils.contains(uids, uid[i])) {
+ maybeCopyEntry(nextOutputEntry, i);
+ nextOutputEntry++;
+ }
+ }
+
+ size = nextOutputEntry;
+ }
+
+ /**
+ * Only keep entries that match all specified filters.
+ *
+ * <p>This mutates the original structure in place. After this method is called,
+ * size is the number of matching entries, and capacity is the previous capacity.
+ * @param limitUid UID to filter for, or {@link #UID_ALL}.
+ * @param limitIfaces Interfaces to filter for, or {@link #INTERFACES_ALL}.
+ * @param limitTag Tag to filter for, or {@link #TAG_ALL}.
+ */
+ public void filter(int limitUid, String[] limitIfaces, int limitTag) {
+ if (limitUid == UID_ALL && limitTag == TAG_ALL && limitIfaces == INTERFACES_ALL) {
+ return;
+ }
+
+ Entry entry = new Entry();
+ int nextOutputEntry = 0;
+ for (int i = 0; i < size; i++) {
+ entry = getValues(i, entry);
+ final boolean matches =
+ (limitUid == UID_ALL || limitUid == entry.uid)
+ && (limitTag == TAG_ALL || limitTag == entry.tag)
+ && (limitIfaces == INTERFACES_ALL
+ || ArrayUtils.contains(limitIfaces, entry.iface));
+
+ if (matches) {
+ setValues(nextOutputEntry, entry);
+ nextOutputEntry++;
+ }
+ }
+
+ size = nextOutputEntry;
+ }
+
+ public void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix);
+ pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
+ for (int i = 0; i < size; i++) {
+ pw.print(prefix);
+ pw.print(" ["); pw.print(i); pw.print("]");
+ pw.print(" iface="); pw.print(iface[i]);
+ pw.print(" uid="); pw.print(uid[i]);
+ pw.print(" set="); pw.print(setToString(set[i]));
+ pw.print(" tag="); pw.print(tagToString(tag[i]));
+ pw.print(" metered="); pw.print(meteredToString(metered[i]));
+ pw.print(" roaming="); pw.print(roamingToString(roaming[i]));
+ pw.print(" defaultNetwork="); pw.print(defaultNetworkToString(defaultNetwork[i]));
+ pw.print(" rxBytes="); pw.print(rxBytes[i]);
+ pw.print(" rxPackets="); pw.print(rxPackets[i]);
+ pw.print(" txBytes="); pw.print(txBytes[i]);
+ pw.print(" txPackets="); pw.print(txPackets[i]);
+ pw.print(" operations="); pw.println(operations[i]);
+ }
+ }
+
+ /**
+ * Return text description of {@link #set} value.
+ */
+ public static String setToString(int set) {
+ switch (set) {
+ case SET_ALL:
+ return "ALL";
+ case SET_DEFAULT:
+ return "DEFAULT";
+ case SET_FOREGROUND:
+ return "FOREGROUND";
+ case SET_DBG_VPN_IN:
+ return "DBG_VPN_IN";
+ case SET_DBG_VPN_OUT:
+ return "DBG_VPN_OUT";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ /**
+ * Return text description of {@link #set} value.
+ */
+ public static String setToCheckinString(int set) {
+ switch (set) {
+ case SET_ALL:
+ return "all";
+ case SET_DEFAULT:
+ return "def";
+ case SET_FOREGROUND:
+ return "fg";
+ case SET_DBG_VPN_IN:
+ return "vpnin";
+ case SET_DBG_VPN_OUT:
+ return "vpnout";
+ default:
+ return "unk";
+ }
+ }
+
+ /**
+ * @return true if the querySet matches the dataSet.
+ */
+ public static boolean setMatches(int querySet, int dataSet) {
+ if (querySet == dataSet) {
+ return true;
+ }
+ // SET_ALL matches all non-debugging sets.
+ return querySet == SET_ALL && dataSet < SET_DEBUG_START;
+ }
+
+ /**
+ * Return text description of {@link #tag} value.
+ */
+ public static String tagToString(int tag) {
+ return "0x" + Integer.toHexString(tag);
+ }
+
+ /**
+ * Return text description of {@link #metered} value.
+ */
+ public static String meteredToString(int metered) {
+ switch (metered) {
+ case METERED_ALL:
+ return "ALL";
+ case METERED_NO:
+ return "NO";
+ case METERED_YES:
+ return "YES";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ /**
+ * Return text description of {@link #roaming} value.
+ */
+ public static String roamingToString(int roaming) {
+ switch (roaming) {
+ case ROAMING_ALL:
+ return "ALL";
+ case ROAMING_NO:
+ return "NO";
+ case ROAMING_YES:
+ return "YES";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ /**
+ * Return text description of {@link #defaultNetwork} value.
+ */
+ public static String defaultNetworkToString(int defaultNetwork) {
+ switch (defaultNetwork) {
+ case DEFAULT_NETWORK_ALL:
+ return "ALL";
+ case DEFAULT_NETWORK_NO:
+ return "NO";
+ case DEFAULT_NETWORK_YES:
+ return "YES";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ @Override
+ public String toString() {
+ final CharArrayWriter writer = new CharArrayWriter();
+ dump("", new PrintWriter(writer));
+ return writer.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @UnsupportedAppUsage
+ public static final @android.annotation.NonNull Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
+ @Override
+ public NetworkStats createFromParcel(Parcel in) {
+ return new NetworkStats(in);
+ }
+
+ @Override
+ public NetworkStats[] newArray(int size) {
+ return new NetworkStats[size];
+ }
+ };
+
+ public interface NonMonotonicObserver<C> {
+ public void foundNonMonotonic(
+ NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie);
+ public void foundNonMonotonic(
+ NetworkStats stats, int statsIndex, C cookie);
+ }
+
+ /**
+ * VPN accounting. Move some VPN's underlying traffic to other UIDs that use tun0 iface.
+ *
+ * This method should only be called on delta NetworkStats. Do not call this method on a
+ * snapshot {@link NetworkStats} object because the tunUid and/or the underlyingIface may
+ * change over time.
+ *
+ * This method performs adjustments for one active VPN package and one VPN iface at a time.
+ *
+ * It is possible for the VPN software to use multiple underlying networks. This method
+ * only migrates traffic for the primary underlying network.
+ *
+ * @param tunUid uid of the VPN application
+ * @param tunIface iface of the vpn tunnel
+ * @param underlyingIface the primary underlying network iface used by the VPN application
+ * @return true if it successfully adjusts the accounting for VPN, false otherwise
+ */
+ public boolean migrateTun(int tunUid, String tunIface, String underlyingIface) {
+ Entry tunIfaceTotal = new Entry();
+ Entry underlyingIfaceTotal = new Entry();
+
+ tunAdjustmentInit(tunUid, tunIface, underlyingIface, tunIfaceTotal, underlyingIfaceTotal);
+
+ // If tunIface < underlyingIface, it leaves the overhead traffic in the VPN app.
+ // If tunIface > underlyingIface, the VPN app doesn't get credit for data compression.
+ // Negative stats should be avoided.
+ Entry pool = tunGetPool(tunIfaceTotal, underlyingIfaceTotal);
+ if (pool.isEmpty()) {
+ return true;
+ }
+ Entry moved =
+ addTrafficToApplications(tunUid, tunIface, underlyingIface, tunIfaceTotal, pool);
+ deductTrafficFromVpnApp(tunUid, underlyingIface, moved);
+
+ if (!moved.isEmpty()) {
+ Slog.wtf(TAG, "Failed to deduct underlying network traffic from VPN package. Moved="
+ + moved);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Initializes the data used by the migrateTun() method.
+ *
+ * This is the first pass iteration which does the following work:
+ * (1) Adds up all the traffic through the tunUid's underlyingIface
+ * (both foreground and background).
+ * (2) Adds up all the traffic through tun0 excluding traffic from the vpn app itself.
+ */
+ private void tunAdjustmentInit(int tunUid, String tunIface, String underlyingIface,
+ Entry tunIfaceTotal, Entry underlyingIfaceTotal) {
+ Entry recycle = new Entry();
+ for (int i = 0; i < size; i++) {
+ getValues(i, recycle);
+ if (recycle.uid == UID_ALL) {
+ throw new IllegalStateException(
+ "Cannot adjust VPN accounting on an iface aggregated NetworkStats.");
+ } if (recycle.set == SET_DBG_VPN_IN || recycle.set == SET_DBG_VPN_OUT) {
+ throw new IllegalStateException(
+ "Cannot adjust VPN accounting on a NetworkStats containing SET_DBG_VPN_*");
+ }
+
+ if (recycle.uid == tunUid && recycle.tag == TAG_NONE
+ && Objects.equals(underlyingIface, recycle.iface)) {
+ underlyingIfaceTotal.add(recycle);
+ }
+
+ if (recycle.uid != tunUid && recycle.tag == TAG_NONE
+ && Objects.equals(tunIface, recycle.iface)) {
+ // Add up all tunIface traffic excluding traffic from the vpn app itself.
+ tunIfaceTotal.add(recycle);
+ }
+ }
+ }
+
+ private static Entry tunGetPool(Entry tunIfaceTotal, Entry underlyingIfaceTotal) {
+ Entry pool = new Entry();
+ pool.rxBytes = Math.min(tunIfaceTotal.rxBytes, underlyingIfaceTotal.rxBytes);
+ pool.rxPackets = Math.min(tunIfaceTotal.rxPackets, underlyingIfaceTotal.rxPackets);
+ pool.txBytes = Math.min(tunIfaceTotal.txBytes, underlyingIfaceTotal.txBytes);
+ pool.txPackets = Math.min(tunIfaceTotal.txPackets, underlyingIfaceTotal.txPackets);
+ pool.operations = Math.min(tunIfaceTotal.operations, underlyingIfaceTotal.operations);
+ return pool;
+ }
+
+ private Entry addTrafficToApplications(int tunUid, String tunIface, String underlyingIface,
+ Entry tunIfaceTotal, Entry pool) {
+ Entry moved = new Entry();
+ Entry tmpEntry = new Entry();
+ tmpEntry.iface = underlyingIface;
+ for (int i = 0; i < size; i++) {
+ // the vpn app is excluded from the redistribution but all moved traffic will be
+ // deducted from the vpn app (see deductTrafficFromVpnApp below).
+ if (Objects.equals(iface[i], tunIface) && uid[i] != tunUid) {
+ if (tunIfaceTotal.rxBytes > 0) {
+ tmpEntry.rxBytes = pool.rxBytes * rxBytes[i] / tunIfaceTotal.rxBytes;
+ } else {
+ tmpEntry.rxBytes = 0;
+ }
+ if (tunIfaceTotal.rxPackets > 0) {
+ tmpEntry.rxPackets = pool.rxPackets * rxPackets[i] / tunIfaceTotal.rxPackets;
+ } else {
+ tmpEntry.rxPackets = 0;
+ }
+ if (tunIfaceTotal.txBytes > 0) {
+ tmpEntry.txBytes = pool.txBytes * txBytes[i] / tunIfaceTotal.txBytes;
+ } else {
+ tmpEntry.txBytes = 0;
+ }
+ if (tunIfaceTotal.txPackets > 0) {
+ tmpEntry.txPackets = pool.txPackets * txPackets[i] / tunIfaceTotal.txPackets;
+ } else {
+ tmpEntry.txPackets = 0;
+ }
+ if (tunIfaceTotal.operations > 0) {
+ tmpEntry.operations =
+ pool.operations * operations[i] / tunIfaceTotal.operations;
+ } else {
+ tmpEntry.operations = 0;
+ }
+ tmpEntry.uid = uid[i];
+ tmpEntry.tag = tag[i];
+ tmpEntry.set = set[i];
+ tmpEntry.metered = metered[i];
+ tmpEntry.roaming = roaming[i];
+ tmpEntry.defaultNetwork = defaultNetwork[i];
+ combineValues(tmpEntry);
+ if (tag[i] == TAG_NONE) {
+ moved.add(tmpEntry);
+ // Add debug info
+ tmpEntry.set = SET_DBG_VPN_IN;
+ combineValues(tmpEntry);
+ }
+ }
+ }
+ return moved;
+ }
+
+ private void deductTrafficFromVpnApp(int tunUid, String underlyingIface, Entry moved) {
+ // Add debug info
+ moved.uid = tunUid;
+ moved.set = SET_DBG_VPN_OUT;
+ moved.tag = TAG_NONE;
+ moved.iface = underlyingIface;
+ moved.metered = METERED_ALL;
+ moved.roaming = ROAMING_ALL;
+ moved.defaultNetwork = DEFAULT_NETWORK_ALL;
+ combineValues(moved);
+
+ // Caveat: if the vpn software uses tag, the total tagged traffic may be greater than
+ // the TAG_NONE traffic.
+ //
+ // Relies on the fact that the underlying traffic only has state ROAMING_NO and METERED_NO,
+ // which should be the case as it comes directly from the /proc file. We only blend in the
+ // roaming data after applying these adjustments, by checking the NetworkIdentity of the
+ // underlying iface.
+ int idxVpnBackground = findIndex(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO);
+ if (idxVpnBackground != -1) {
+ tunSubtract(idxVpnBackground, this, moved);
+ }
+
+ int idxVpnForeground = findIndex(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO);
+ if (idxVpnForeground != -1) {
+ tunSubtract(idxVpnForeground, this, moved);
+ }
+ }
+
+ private static void tunSubtract(int i, NetworkStats left, Entry right) {
+ long rxBytes = Math.min(left.rxBytes[i], right.rxBytes);
+ left.rxBytes[i] -= rxBytes;
+ right.rxBytes -= rxBytes;
+
+ long rxPackets = Math.min(left.rxPackets[i], right.rxPackets);
+ left.rxPackets[i] -= rxPackets;
+ right.rxPackets -= rxPackets;
+
+ long txBytes = Math.min(left.txBytes[i], right.txBytes);
+ left.txBytes[i] -= txBytes;
+ right.txBytes -= txBytes;
+
+ long txPackets = Math.min(left.txPackets[i], right.txPackets);
+ left.txPackets[i] -= txPackets;
+ right.txPackets -= txPackets;
+ }
+}
diff --git a/android/net/NetworkStatsHistory.java b/android/net/NetworkStatsHistory.java
new file mode 100644
index 0000000..f61260e
--- /dev/null
+++ b/android/net/NetworkStatsHistory.java
@@ -0,0 +1,854 @@
+/*
+ * Copyright (C) 2011 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;
+
+import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static android.net.NetworkStatsHistory.DataStreamUtils.readFullLongArray;
+import static android.net.NetworkStatsHistory.DataStreamUtils.readVarLongArray;
+import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLongArray;
+import static android.net.NetworkStatsHistory.Entry.UNKNOWN;
+import static android.net.NetworkStatsHistory.ParcelUtils.readLongArray;
+import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+
+import static com.android.internal.util.ArrayUtils.total;
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.NetworkStatsHistoryBucketProto;
+import android.service.NetworkStatsHistoryProto;
+import android.util.MathUtils;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+import libcore.util.EmptyArray;
+
+import java.io.CharArrayWriter;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.ProtocolException;
+import java.util.Arrays;
+import java.util.Random;
+
+/**
+ * Collection of historical network statistics, recorded into equally-sized
+ * "buckets" in time. Internally it stores data in {@code long} series for more
+ * efficient persistence.
+ * <p>
+ * Each bucket is defined by a {@link #bucketStart} timestamp, and lasts for
+ * {@link #bucketDuration}. Internally assumes that {@link #bucketStart} is
+ * sorted at all times.
+ *
+ * @hide
+ */
+public class NetworkStatsHistory implements Parcelable {
+ private static final int VERSION_INIT = 1;
+ private static final int VERSION_ADD_PACKETS = 2;
+ private static final int VERSION_ADD_ACTIVE = 3;
+
+ public static final int FIELD_ACTIVE_TIME = 0x01;
+ public static final int FIELD_RX_BYTES = 0x02;
+ public static final int FIELD_RX_PACKETS = 0x04;
+ public static final int FIELD_TX_BYTES = 0x08;
+ public static final int FIELD_TX_PACKETS = 0x10;
+ public static final int FIELD_OPERATIONS = 0x20;
+
+ public static final int FIELD_ALL = 0xFFFFFFFF;
+
+ private long bucketDuration;
+ private int bucketCount;
+ private long[] bucketStart;
+ private long[] activeTime;
+ private long[] rxBytes;
+ private long[] rxPackets;
+ private long[] txBytes;
+ private long[] txPackets;
+ private long[] operations;
+ private long totalBytes;
+
+ public static class Entry {
+ public static final long UNKNOWN = -1;
+
+ @UnsupportedAppUsage
+ public long bucketDuration;
+ @UnsupportedAppUsage
+ public long bucketStart;
+ public long activeTime;
+ @UnsupportedAppUsage
+ public long rxBytes;
+ @UnsupportedAppUsage
+ public long rxPackets;
+ @UnsupportedAppUsage
+ public long txBytes;
+ @UnsupportedAppUsage
+ public long txPackets;
+ public long operations;
+ }
+
+ @UnsupportedAppUsage
+ public NetworkStatsHistory(long bucketDuration) {
+ this(bucketDuration, 10, FIELD_ALL);
+ }
+
+ public NetworkStatsHistory(long bucketDuration, int initialSize) {
+ this(bucketDuration, initialSize, FIELD_ALL);
+ }
+
+ public NetworkStatsHistory(long bucketDuration, int initialSize, int fields) {
+ this.bucketDuration = bucketDuration;
+ bucketStart = new long[initialSize];
+ if ((fields & FIELD_ACTIVE_TIME) != 0) activeTime = new long[initialSize];
+ if ((fields & FIELD_RX_BYTES) != 0) rxBytes = new long[initialSize];
+ if ((fields & FIELD_RX_PACKETS) != 0) rxPackets = new long[initialSize];
+ if ((fields & FIELD_TX_BYTES) != 0) txBytes = new long[initialSize];
+ if ((fields & FIELD_TX_PACKETS) != 0) txPackets = new long[initialSize];
+ if ((fields & FIELD_OPERATIONS) != 0) operations = new long[initialSize];
+ bucketCount = 0;
+ totalBytes = 0;
+ }
+
+ public NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration) {
+ this(bucketDuration, existing.estimateResizeBuckets(bucketDuration));
+ recordEntireHistory(existing);
+ }
+
+ @UnsupportedAppUsage
+ public NetworkStatsHistory(Parcel in) {
+ bucketDuration = in.readLong();
+ bucketStart = readLongArray(in);
+ activeTime = readLongArray(in);
+ rxBytes = readLongArray(in);
+ rxPackets = readLongArray(in);
+ txBytes = readLongArray(in);
+ txPackets = readLongArray(in);
+ operations = readLongArray(in);
+ bucketCount = bucketStart.length;
+ totalBytes = in.readLong();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(bucketDuration);
+ writeLongArray(out, bucketStart, bucketCount);
+ writeLongArray(out, activeTime, bucketCount);
+ writeLongArray(out, rxBytes, bucketCount);
+ writeLongArray(out, rxPackets, bucketCount);
+ writeLongArray(out, txBytes, bucketCount);
+ writeLongArray(out, txPackets, bucketCount);
+ writeLongArray(out, operations, bucketCount);
+ out.writeLong(totalBytes);
+ }
+
+ public NetworkStatsHistory(DataInputStream in) throws IOException {
+ final int version = in.readInt();
+ switch (version) {
+ case VERSION_INIT: {
+ bucketDuration = in.readLong();
+ bucketStart = readFullLongArray(in);
+ rxBytes = readFullLongArray(in);
+ rxPackets = new long[bucketStart.length];
+ txBytes = readFullLongArray(in);
+ txPackets = new long[bucketStart.length];
+ operations = new long[bucketStart.length];
+ bucketCount = bucketStart.length;
+ totalBytes = total(rxBytes) + total(txBytes);
+ break;
+ }
+ case VERSION_ADD_PACKETS:
+ case VERSION_ADD_ACTIVE: {
+ bucketDuration = in.readLong();
+ bucketStart = readVarLongArray(in);
+ activeTime = (version >= VERSION_ADD_ACTIVE) ? readVarLongArray(in)
+ : new long[bucketStart.length];
+ rxBytes = readVarLongArray(in);
+ rxPackets = readVarLongArray(in);
+ txBytes = readVarLongArray(in);
+ txPackets = readVarLongArray(in);
+ operations = readVarLongArray(in);
+ bucketCount = bucketStart.length;
+ totalBytes = total(rxBytes) + total(txBytes);
+ break;
+ }
+ default: {
+ throw new ProtocolException("unexpected version: " + version);
+ }
+ }
+
+ if (bucketStart.length != bucketCount || rxBytes.length != bucketCount
+ || rxPackets.length != bucketCount || txBytes.length != bucketCount
+ || txPackets.length != bucketCount || operations.length != bucketCount) {
+ throw new ProtocolException("Mismatched history lengths");
+ }
+ }
+
+ public void writeToStream(DataOutputStream out) throws IOException {
+ out.writeInt(VERSION_ADD_ACTIVE);
+ out.writeLong(bucketDuration);
+ writeVarLongArray(out, bucketStart, bucketCount);
+ writeVarLongArray(out, activeTime, bucketCount);
+ writeVarLongArray(out, rxBytes, bucketCount);
+ writeVarLongArray(out, rxPackets, bucketCount);
+ writeVarLongArray(out, txBytes, bucketCount);
+ writeVarLongArray(out, txPackets, bucketCount);
+ writeVarLongArray(out, operations, bucketCount);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @UnsupportedAppUsage
+ public int size() {
+ return bucketCount;
+ }
+
+ public long getBucketDuration() {
+ return bucketDuration;
+ }
+
+ @UnsupportedAppUsage
+ public long getStart() {
+ if (bucketCount > 0) {
+ return bucketStart[0];
+ } else {
+ return Long.MAX_VALUE;
+ }
+ }
+
+ @UnsupportedAppUsage
+ public long getEnd() {
+ if (bucketCount > 0) {
+ return bucketStart[bucketCount - 1] + bucketDuration;
+ } else {
+ return Long.MIN_VALUE;
+ }
+ }
+
+ /**
+ * Return total bytes represented by this history.
+ */
+ public long getTotalBytes() {
+ return totalBytes;
+ }
+
+ /**
+ * Return index of bucket that contains or is immediately before the
+ * requested time.
+ */
+ @UnsupportedAppUsage
+ public int getIndexBefore(long time) {
+ int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time);
+ if (index < 0) {
+ index = (~index) - 1;
+ } else {
+ index -= 1;
+ }
+ return MathUtils.constrain(index, 0, bucketCount - 1);
+ }
+
+ /**
+ * Return index of bucket that contains or is immediately after the
+ * requested time.
+ */
+ public int getIndexAfter(long time) {
+ int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time);
+ if (index < 0) {
+ index = ~index;
+ } else {
+ index += 1;
+ }
+ return MathUtils.constrain(index, 0, bucketCount - 1);
+ }
+
+ /**
+ * Return specific stats entry.
+ */
+ @UnsupportedAppUsage
+ public Entry getValues(int i, Entry recycle) {
+ final Entry entry = recycle != null ? recycle : new Entry();
+ entry.bucketStart = bucketStart[i];
+ entry.bucketDuration = bucketDuration;
+ entry.activeTime = getLong(activeTime, i, UNKNOWN);
+ entry.rxBytes = getLong(rxBytes, i, UNKNOWN);
+ entry.rxPackets = getLong(rxPackets, i, UNKNOWN);
+ entry.txBytes = getLong(txBytes, i, UNKNOWN);
+ entry.txPackets = getLong(txPackets, i, UNKNOWN);
+ entry.operations = getLong(operations, i, UNKNOWN);
+ return entry;
+ }
+
+ public void setValues(int i, Entry entry) {
+ // Unwind old values
+ if (rxBytes != null) totalBytes -= rxBytes[i];
+ if (txBytes != null) totalBytes -= txBytes[i];
+
+ bucketStart[i] = entry.bucketStart;
+ setLong(activeTime, i, entry.activeTime);
+ setLong(rxBytes, i, entry.rxBytes);
+ setLong(rxPackets, i, entry.rxPackets);
+ setLong(txBytes, i, entry.txBytes);
+ setLong(txPackets, i, entry.txPackets);
+ setLong(operations, i, entry.operations);
+
+ // Apply new values
+ if (rxBytes != null) totalBytes += rxBytes[i];
+ if (txBytes != null) totalBytes += txBytes[i];
+ }
+
+ /**
+ * Record that data traffic occurred in the given time range. Will
+ * distribute across internal buckets, creating new buckets as needed.
+ */
+ @Deprecated
+ public void recordData(long start, long end, long rxBytes, long txBytes) {
+ recordData(start, end, new NetworkStats.Entry(
+ IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, 0L, txBytes, 0L, 0L));
+ }
+
+ /**
+ * Record that data traffic occurred in the given time range. Will
+ * distribute across internal buckets, creating new buckets as needed.
+ */
+ public void recordData(long start, long end, NetworkStats.Entry entry) {
+ long rxBytes = entry.rxBytes;
+ long rxPackets = entry.rxPackets;
+ long txBytes = entry.txBytes;
+ long txPackets = entry.txPackets;
+ long operations = entry.operations;
+
+ if (entry.isNegative()) {
+ throw new IllegalArgumentException("tried recording negative data");
+ }
+ if (entry.isEmpty()) {
+ return;
+ }
+
+ // create any buckets needed by this range
+ ensureBuckets(start, end);
+
+ // distribute data usage into buckets
+ long duration = end - start;
+ final int startIndex = getIndexAfter(end);
+ for (int i = startIndex; i >= 0; i--) {
+ final long curStart = bucketStart[i];
+ final long curEnd = curStart + bucketDuration;
+
+ // bucket is older than record; we're finished
+ if (curEnd < start) break;
+ // bucket is newer than record; keep looking
+ if (curStart > end) continue;
+
+ final long overlap = Math.min(curEnd, end) - Math.max(curStart, start);
+ if (overlap <= 0) continue;
+
+ // integer math each time is faster than floating point
+ final long fracRxBytes = rxBytes * overlap / duration;
+ final long fracRxPackets = rxPackets * overlap / duration;
+ final long fracTxBytes = txBytes * overlap / duration;
+ final long fracTxPackets = txPackets * overlap / duration;
+ final long fracOperations = operations * overlap / duration;
+
+ addLong(activeTime, i, overlap);
+ addLong(this.rxBytes, i, fracRxBytes); rxBytes -= fracRxBytes;
+ addLong(this.rxPackets, i, fracRxPackets); rxPackets -= fracRxPackets;
+ addLong(this.txBytes, i, fracTxBytes); txBytes -= fracTxBytes;
+ addLong(this.txPackets, i, fracTxPackets); txPackets -= fracTxPackets;
+ addLong(this.operations, i, fracOperations); operations -= fracOperations;
+
+ duration -= overlap;
+ }
+
+ totalBytes += entry.rxBytes + entry.txBytes;
+ }
+
+ /**
+ * Record an entire {@link NetworkStatsHistory} into this history. Usually
+ * for combining together stats for external reporting.
+ */
+ @UnsupportedAppUsage
+ public void recordEntireHistory(NetworkStatsHistory input) {
+ recordHistory(input, Long.MIN_VALUE, Long.MAX_VALUE);
+ }
+
+ /**
+ * Record given {@link NetworkStatsHistory} into this history, copying only
+ * buckets that atomically occur in the inclusive time range. Doesn't
+ * interpolate across partial buckets.
+ */
+ public void recordHistory(NetworkStatsHistory input, long start, long end) {
+ final NetworkStats.Entry entry = new NetworkStats.Entry(
+ IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
+ for (int i = 0; i < input.bucketCount; i++) {
+ final long bucketStart = input.bucketStart[i];
+ final long bucketEnd = bucketStart + input.bucketDuration;
+
+ // skip when bucket is outside requested range
+ if (bucketStart < start || bucketEnd > end) continue;
+
+ entry.rxBytes = getLong(input.rxBytes, i, 0L);
+ entry.rxPackets = getLong(input.rxPackets, i, 0L);
+ entry.txBytes = getLong(input.txBytes, i, 0L);
+ entry.txPackets = getLong(input.txPackets, i, 0L);
+ entry.operations = getLong(input.operations, i, 0L);
+
+ recordData(bucketStart, bucketEnd, entry);
+ }
+ }
+
+ /**
+ * Ensure that buckets exist for given time range, creating as needed.
+ */
+ private void ensureBuckets(long start, long end) {
+ // normalize incoming range to bucket boundaries
+ start -= start % bucketDuration;
+ end += (bucketDuration - (end % bucketDuration)) % bucketDuration;
+
+ for (long now = start; now < end; now += bucketDuration) {
+ // try finding existing bucket
+ final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now);
+ if (index < 0) {
+ // bucket missing, create and insert
+ insertBucket(~index, now);
+ }
+ }
+ }
+
+ /**
+ * Insert new bucket at requested index and starting time.
+ */
+ private void insertBucket(int index, long start) {
+ // create more buckets when needed
+ if (bucketCount >= bucketStart.length) {
+ final int newLength = Math.max(bucketStart.length, 10) * 3 / 2;
+ bucketStart = Arrays.copyOf(bucketStart, newLength);
+ if (activeTime != null) activeTime = Arrays.copyOf(activeTime, newLength);
+ if (rxBytes != null) rxBytes = Arrays.copyOf(rxBytes, newLength);
+ if (rxPackets != null) rxPackets = Arrays.copyOf(rxPackets, newLength);
+ if (txBytes != null) txBytes = Arrays.copyOf(txBytes, newLength);
+ if (txPackets != null) txPackets = Arrays.copyOf(txPackets, newLength);
+ if (operations != null) operations = Arrays.copyOf(operations, newLength);
+ }
+
+ // create gap when inserting bucket in middle
+ if (index < bucketCount) {
+ final int dstPos = index + 1;
+ final int length = bucketCount - index;
+
+ System.arraycopy(bucketStart, index, bucketStart, dstPos, length);
+ if (activeTime != null) System.arraycopy(activeTime, index, activeTime, dstPos, length);
+ if (rxBytes != null) System.arraycopy(rxBytes, index, rxBytes, dstPos, length);
+ if (rxPackets != null) System.arraycopy(rxPackets, index, rxPackets, dstPos, length);
+ if (txBytes != null) System.arraycopy(txBytes, index, txBytes, dstPos, length);
+ if (txPackets != null) System.arraycopy(txPackets, index, txPackets, dstPos, length);
+ if (operations != null) System.arraycopy(operations, index, operations, dstPos, length);
+ }
+
+ bucketStart[index] = start;
+ setLong(activeTime, index, 0L);
+ setLong(rxBytes, index, 0L);
+ setLong(rxPackets, index, 0L);
+ setLong(txBytes, index, 0L);
+ setLong(txPackets, index, 0L);
+ setLong(operations, index, 0L);
+ bucketCount++;
+ }
+
+ /**
+ * Clear all data stored in this object.
+ */
+ public void clear() {
+ bucketStart = EmptyArray.LONG;
+ if (activeTime != null) activeTime = EmptyArray.LONG;
+ if (rxBytes != null) rxBytes = EmptyArray.LONG;
+ if (rxPackets != null) rxPackets = EmptyArray.LONG;
+ if (txBytes != null) txBytes = EmptyArray.LONG;
+ if (txPackets != null) txPackets = EmptyArray.LONG;
+ if (operations != null) operations = EmptyArray.LONG;
+ bucketCount = 0;
+ totalBytes = 0;
+ }
+
+ /**
+ * Remove buckets older than requested cutoff.
+ */
+ @Deprecated
+ public void removeBucketsBefore(long cutoff) {
+ int i;
+ for (i = 0; i < bucketCount; i++) {
+ final long curStart = bucketStart[i];
+ final long curEnd = curStart + bucketDuration;
+
+ // cutoff happens before or during this bucket; everything before
+ // this bucket should be removed.
+ if (curEnd > cutoff) break;
+ }
+
+ if (i > 0) {
+ final int length = bucketStart.length;
+ bucketStart = Arrays.copyOfRange(bucketStart, i, length);
+ if (activeTime != null) activeTime = Arrays.copyOfRange(activeTime, i, length);
+ if (rxBytes != null) rxBytes = Arrays.copyOfRange(rxBytes, i, length);
+ if (rxPackets != null) rxPackets = Arrays.copyOfRange(rxPackets, i, length);
+ if (txBytes != null) txBytes = Arrays.copyOfRange(txBytes, i, length);
+ if (txPackets != null) txPackets = Arrays.copyOfRange(txPackets, i, length);
+ if (operations != null) operations = Arrays.copyOfRange(operations, i, length);
+ bucketCount -= i;
+
+ // TODO: subtract removed values from totalBytes
+ }
+ }
+
+ /**
+ * Return interpolated data usage across the requested range. Interpolates
+ * across buckets, so values may be rounded slightly.
+ */
+ @UnsupportedAppUsage
+ public Entry getValues(long start, long end, Entry recycle) {
+ return getValues(start, end, Long.MAX_VALUE, recycle);
+ }
+
+ /**
+ * Return interpolated data usage across the requested range. Interpolates
+ * across buckets, so values may be rounded slightly.
+ */
+ @UnsupportedAppUsage
+ public Entry getValues(long start, long end, long now, Entry recycle) {
+ final Entry entry = recycle != null ? recycle : new Entry();
+ entry.bucketDuration = end - start;
+ entry.bucketStart = start;
+ entry.activeTime = activeTime != null ? 0 : UNKNOWN;
+ entry.rxBytes = rxBytes != null ? 0 : UNKNOWN;
+ entry.rxPackets = rxPackets != null ? 0 : UNKNOWN;
+ entry.txBytes = txBytes != null ? 0 : UNKNOWN;
+ entry.txPackets = txPackets != null ? 0 : UNKNOWN;
+ entry.operations = operations != null ? 0 : UNKNOWN;
+
+ final int startIndex = getIndexAfter(end);
+ for (int i = startIndex; i >= 0; i--) {
+ final long curStart = bucketStart[i];
+ final long curEnd = curStart + bucketDuration;
+
+ // bucket is older than request; we're finished
+ if (curEnd <= start) break;
+ // bucket is newer than request; keep looking
+ if (curStart >= end) continue;
+
+ // include full value for active buckets, otherwise only fractional
+ final boolean activeBucket = curStart < now && curEnd > now;
+ final long overlap;
+ if (activeBucket) {
+ overlap = bucketDuration;
+ } else {
+ final long overlapEnd = curEnd < end ? curEnd : end;
+ final long overlapStart = curStart > start ? curStart : start;
+ overlap = overlapEnd - overlapStart;
+ }
+ if (overlap <= 0) continue;
+
+ // integer math each time is faster than floating point
+ if (activeTime != null) entry.activeTime += activeTime[i] * overlap / bucketDuration;
+ if (rxBytes != null) entry.rxBytes += rxBytes[i] * overlap / bucketDuration;
+ if (rxPackets != null) entry.rxPackets += rxPackets[i] * overlap / bucketDuration;
+ if (txBytes != null) entry.txBytes += txBytes[i] * overlap / bucketDuration;
+ if (txPackets != null) entry.txPackets += txPackets[i] * overlap / bucketDuration;
+ if (operations != null) entry.operations += operations[i] * overlap / bucketDuration;
+ }
+ return entry;
+ }
+
+ /**
+ * @deprecated only for temporary testing
+ */
+ @Deprecated
+ public void generateRandom(long start, long end, long bytes) {
+ final Random r = new Random();
+
+ final float fractionRx = r.nextFloat();
+ final long rxBytes = (long) (bytes * fractionRx);
+ final long txBytes = (long) (bytes * (1 - fractionRx));
+
+ final long rxPackets = rxBytes / 1024;
+ final long txPackets = txBytes / 1024;
+ final long operations = rxBytes / 2048;
+
+ generateRandom(start, end, rxBytes, rxPackets, txBytes, txPackets, operations, r);
+ }
+
+ /**
+ * @deprecated only for temporary testing
+ */
+ @Deprecated
+ public void generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes,
+ long txPackets, long operations, Random r) {
+ ensureBuckets(start, end);
+
+ final NetworkStats.Entry entry = new NetworkStats.Entry(
+ IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
+ while (rxBytes > 1024 || rxPackets > 128 || txBytes > 1024 || txPackets > 128
+ || operations > 32) {
+ final long curStart = randomLong(r, start, end);
+ final long curEnd = curStart + randomLong(r, 0, (end - curStart) / 2);
+
+ entry.rxBytes = randomLong(r, 0, rxBytes);
+ entry.rxPackets = randomLong(r, 0, rxPackets);
+ entry.txBytes = randomLong(r, 0, txBytes);
+ entry.txPackets = randomLong(r, 0, txPackets);
+ entry.operations = randomLong(r, 0, operations);
+
+ rxBytes -= entry.rxBytes;
+ rxPackets -= entry.rxPackets;
+ txBytes -= entry.txBytes;
+ txPackets -= entry.txPackets;
+ operations -= entry.operations;
+
+ recordData(curStart, curEnd, entry);
+ }
+ }
+
+ public static long randomLong(Random r, long start, long end) {
+ return (long) (start + (r.nextFloat() * (end - start)));
+ }
+
+ /**
+ * Quickly determine if this history intersects with given window.
+ */
+ public boolean intersects(long start, long end) {
+ final long dataStart = getStart();
+ final long dataEnd = getEnd();
+ if (start >= dataStart && start <= dataEnd) return true;
+ if (end >= dataStart && end <= dataEnd) return true;
+ if (dataStart >= start && dataStart <= end) return true;
+ if (dataEnd >= start && dataEnd <= end) return true;
+ return false;
+ }
+
+ public void dump(IndentingPrintWriter pw, boolean fullHistory) {
+ pw.print("NetworkStatsHistory: bucketDuration=");
+ pw.println(bucketDuration / SECOND_IN_MILLIS);
+ pw.increaseIndent();
+
+ final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32);
+ if (start > 0) {
+ pw.print("(omitting "); pw.print(start); pw.println(" buckets)");
+ }
+
+ for (int i = start; i < bucketCount; i++) {
+ pw.print("st="); pw.print(bucketStart[i] / SECOND_IN_MILLIS);
+ if (rxBytes != null) { pw.print(" rb="); pw.print(rxBytes[i]); }
+ if (rxPackets != null) { pw.print(" rp="); pw.print(rxPackets[i]); }
+ if (txBytes != null) { pw.print(" tb="); pw.print(txBytes[i]); }
+ if (txPackets != null) { pw.print(" tp="); pw.print(txPackets[i]); }
+ if (operations != null) { pw.print(" op="); pw.print(operations[i]); }
+ pw.println();
+ }
+
+ pw.decreaseIndent();
+ }
+
+ public void dumpCheckin(PrintWriter pw) {
+ pw.print("d,");
+ pw.print(bucketDuration / SECOND_IN_MILLIS);
+ pw.println();
+
+ for (int i = 0; i < bucketCount; i++) {
+ pw.print("b,");
+ pw.print(bucketStart[i] / SECOND_IN_MILLIS); pw.print(',');
+ if (rxBytes != null) { pw.print(rxBytes[i]); } else { pw.print("*"); } pw.print(',');
+ if (rxPackets != null) { pw.print(rxPackets[i]); } else { pw.print("*"); } pw.print(',');
+ if (txBytes != null) { pw.print(txBytes[i]); } else { pw.print("*"); } pw.print(',');
+ if (txPackets != null) { pw.print(txPackets[i]); } else { pw.print("*"); } pw.print(',');
+ if (operations != null) { pw.print(operations[i]); } else { pw.print("*"); }
+ pw.println();
+ }
+ }
+
+ public void writeToProto(ProtoOutputStream proto, long tag) {
+ final long start = proto.start(tag);
+
+ proto.write(NetworkStatsHistoryProto.BUCKET_DURATION_MS, bucketDuration);
+
+ for (int i = 0; i < bucketCount; i++) {
+ final long startBucket = proto.start(NetworkStatsHistoryProto.BUCKETS);
+
+ proto.write(NetworkStatsHistoryBucketProto.BUCKET_START_MS, bucketStart[i]);
+ writeToProto(proto, NetworkStatsHistoryBucketProto.RX_BYTES, rxBytes, i);
+ writeToProto(proto, NetworkStatsHistoryBucketProto.RX_PACKETS, rxPackets, i);
+ writeToProto(proto, NetworkStatsHistoryBucketProto.TX_BYTES, txBytes, i);
+ writeToProto(proto, NetworkStatsHistoryBucketProto.TX_PACKETS, txPackets, i);
+ writeToProto(proto, NetworkStatsHistoryBucketProto.OPERATIONS, operations, i);
+
+ proto.end(startBucket);
+ }
+
+ proto.end(start);
+ }
+
+ private static void writeToProto(ProtoOutputStream proto, long tag, long[] array, int index) {
+ if (array != null) {
+ proto.write(tag, array[index]);
+ }
+ }
+
+ @Override
+ public String toString() {
+ final CharArrayWriter writer = new CharArrayWriter();
+ dump(new IndentingPrintWriter(writer, " "), false);
+ return writer.toString();
+ }
+
+ @UnsupportedAppUsage
+ public static final @android.annotation.NonNull Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() {
+ @Override
+ public NetworkStatsHistory createFromParcel(Parcel in) {
+ return new NetworkStatsHistory(in);
+ }
+
+ @Override
+ public NetworkStatsHistory[] newArray(int size) {
+ return new NetworkStatsHistory[size];
+ }
+ };
+
+ private static long getLong(long[] array, int i, long value) {
+ return array != null ? array[i] : value;
+ }
+
+ private static void setLong(long[] array, int i, long value) {
+ if (array != null) array[i] = value;
+ }
+
+ private static void addLong(long[] array, int i, long value) {
+ if (array != null) array[i] += value;
+ }
+
+ public int estimateResizeBuckets(long newBucketDuration) {
+ return (int) (size() * getBucketDuration() / newBucketDuration);
+ }
+
+ /**
+ * Utility methods for interacting with {@link DataInputStream} and
+ * {@link DataOutputStream}, mostly dealing with writing partial arrays.
+ */
+ public static class DataStreamUtils {
+ @Deprecated
+ public static long[] readFullLongArray(DataInputStream in) throws IOException {
+ final int size = in.readInt();
+ if (size < 0) throw new ProtocolException("negative array size");
+ final long[] values = new long[size];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = in.readLong();
+ }
+ return values;
+ }
+
+ /**
+ * Read variable-length {@link Long} using protobuf-style approach.
+ */
+ public static long readVarLong(DataInputStream in) throws IOException {
+ int shift = 0;
+ long result = 0;
+ while (shift < 64) {
+ byte b = in.readByte();
+ result |= (long) (b & 0x7F) << shift;
+ if ((b & 0x80) == 0)
+ return result;
+ shift += 7;
+ }
+ throw new ProtocolException("malformed long");
+ }
+
+ /**
+ * Write variable-length {@link Long} using protobuf-style approach.
+ */
+ public static void writeVarLong(DataOutputStream out, long value) throws IOException {
+ while (true) {
+ if ((value & ~0x7FL) == 0) {
+ out.writeByte((int) value);
+ return;
+ } else {
+ out.writeByte(((int) value & 0x7F) | 0x80);
+ value >>>= 7;
+ }
+ }
+ }
+
+ public static long[] readVarLongArray(DataInputStream in) throws IOException {
+ final int size = in.readInt();
+ if (size == -1) return null;
+ if (size < 0) throw new ProtocolException("negative array size");
+ final long[] values = new long[size];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = readVarLong(in);
+ }
+ return values;
+ }
+
+ public static void writeVarLongArray(DataOutputStream out, long[] values, int size)
+ throws IOException {
+ if (values == null) {
+ out.writeInt(-1);
+ return;
+ }
+ if (size > values.length) {
+ throw new IllegalArgumentException("size larger than length");
+ }
+ out.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ writeVarLong(out, values[i]);
+ }
+ }
+ }
+
+ /**
+ * Utility methods for interacting with {@link Parcel} structures, mostly
+ * dealing with writing partial arrays.
+ */
+ public static class ParcelUtils {
+ public static long[] readLongArray(Parcel in) {
+ final int size = in.readInt();
+ if (size == -1) return null;
+ final long[] values = new long[size];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = in.readLong();
+ }
+ return values;
+ }
+
+ public static void writeLongArray(Parcel out, long[] values, int size) {
+ if (values == null) {
+ out.writeInt(-1);
+ return;
+ }
+ if (size > values.length) {
+ throw new IllegalArgumentException("size larger than length");
+ }
+ out.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ out.writeLong(values[i]);
+ }
+ }
+ }
+
+}
diff --git a/android/net/NetworkTemplate.java b/android/net/NetworkTemplate.java
new file mode 100644
index 0000000..ae421a4
--- /dev/null
+++ b/android/net/NetworkTemplate.java
@@ -0,0 +1,541 @@
+/*
+ * Copyright (C) 2011 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;
+
+import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
+import static android.net.ConnectivityManager.TYPE_ETHERNET;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_PROXY;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
+import static android.net.ConnectivityManager.TYPE_WIMAX;
+import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
+import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
+import static android.net.NetworkStats.METERED_ALL;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.METERED_YES;
+import static android.net.NetworkStats.ROAMING_ALL;
+import static android.net.NetworkStats.ROAMING_NO;
+import static android.net.NetworkStats.ROAMING_YES;
+import static android.net.wifi.WifiInfo.removeDoubleQuotes;
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.BackupUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Predicate used to match {@link NetworkIdentity}, usually when collecting
+ * statistics. (It should probably have been named {@code NetworkPredicate}.)
+ *
+ * @hide
+ */
+public class NetworkTemplate implements Parcelable {
+ private static final String TAG = "NetworkTemplate";
+
+ /**
+ * Current Version of the Backup Serializer.
+ */
+ private static final int BACKUP_VERSION = 1;
+
+ public static final int MATCH_MOBILE = 1;
+ public static final int MATCH_WIFI = 4;
+ public static final int MATCH_ETHERNET = 5;
+ public static final int MATCH_MOBILE_WILDCARD = 6;
+ public static final int MATCH_WIFI_WILDCARD = 7;
+ public static final int MATCH_BLUETOOTH = 8;
+ public static final int MATCH_PROXY = 9;
+
+ private static boolean isKnownMatchRule(final int rule) {
+ switch (rule) {
+ case MATCH_MOBILE:
+ case MATCH_WIFI:
+ case MATCH_ETHERNET:
+ case MATCH_MOBILE_WILDCARD:
+ case MATCH_WIFI_WILDCARD:
+ case MATCH_BLUETOOTH:
+ case MATCH_PROXY:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ private static boolean sForceAllNetworkTypes = false;
+
+ /**
+ * Results in matching against all mobile network types.
+ *
+ * <p>See {@link #matchesMobile} and {@link matchesMobileWildcard}.
+ */
+ @VisibleForTesting
+ public static void forceAllNetworkTypes() {
+ sForceAllNetworkTypes = true;
+ }
+
+ /** Resets the affect of {@link #forceAllNetworkTypes}. */
+ @VisibleForTesting
+ public static void resetForceAllNetworkTypes() {
+ sForceAllNetworkTypes = false;
+ }
+
+ /**
+ * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with
+ * the given IMSI.
+ */
+ @UnsupportedAppUsage
+ public static NetworkTemplate buildTemplateMobileAll(String subscriberId) {
+ return new NetworkTemplate(MATCH_MOBILE, subscriberId, null);
+ }
+
+ /**
+ * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks,
+ * regardless of IMSI.
+ */
+ @UnsupportedAppUsage
+ public static NetworkTemplate buildTemplateMobileWildcard() {
+ return new NetworkTemplate(MATCH_MOBILE_WILDCARD, null, null);
+ }
+
+ /**
+ * Template to match all {@link ConnectivityManager#TYPE_WIFI} networks,
+ * regardless of SSID.
+ */
+ @UnsupportedAppUsage
+ public static NetworkTemplate buildTemplateWifiWildcard() {
+ return new NetworkTemplate(MATCH_WIFI_WILDCARD, null, null);
+ }
+
+ @Deprecated
+ @UnsupportedAppUsage
+ public static NetworkTemplate buildTemplateWifi() {
+ return buildTemplateWifiWildcard();
+ }
+
+ /**
+ * Template to match {@link ConnectivityManager#TYPE_WIFI} networks with the
+ * given SSID.
+ */
+ public static NetworkTemplate buildTemplateWifi(String networkId) {
+ return new NetworkTemplate(MATCH_WIFI, null, networkId);
+ }
+
+ /**
+ * Template to combine all {@link ConnectivityManager#TYPE_ETHERNET} style
+ * networks together.
+ */
+ @UnsupportedAppUsage
+ public static NetworkTemplate buildTemplateEthernet() {
+ return new NetworkTemplate(MATCH_ETHERNET, null, null);
+ }
+
+ /**
+ * Template to combine all {@link ConnectivityManager#TYPE_BLUETOOTH} style
+ * networks together.
+ */
+ public static NetworkTemplate buildTemplateBluetooth() {
+ return new NetworkTemplate(MATCH_BLUETOOTH, null, null);
+ }
+
+ /**
+ * Template to combine all {@link ConnectivityManager#TYPE_PROXY} style
+ * networks together.
+ */
+ public static NetworkTemplate buildTemplateProxy() {
+ return new NetworkTemplate(MATCH_PROXY, null, null);
+ }
+
+ private final int mMatchRule;
+ private final String mSubscriberId;
+
+ /**
+ * Ugh, templates are designed to target a single subscriber, but we might
+ * need to match several "merged" subscribers. These are the subscribers
+ * that should be considered to match this template.
+ * <p>
+ * Since the merge set is dynamic, it should <em>not</em> be persisted or
+ * used for determining equality.
+ */
+ private final String[] mMatchSubscriberIds;
+
+ private final String mNetworkId;
+
+ // Matches for the NetworkStats constants METERED_*, ROAMING_* and DEFAULT_NETWORK_*.
+ private final int mMetered;
+ private final int mRoaming;
+ private final int mDefaultNetwork;
+
+ @UnsupportedAppUsage
+ public NetworkTemplate(int matchRule, String subscriberId, String networkId) {
+ this(matchRule, subscriberId, new String[] { subscriberId }, networkId);
+ }
+
+ public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
+ String networkId) {
+ this(matchRule, subscriberId, matchSubscriberIds, networkId, METERED_ALL, ROAMING_ALL,
+ DEFAULT_NETWORK_ALL);
+ }
+
+ public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
+ String networkId, int metered, int roaming, int defaultNetwork) {
+ mMatchRule = matchRule;
+ mSubscriberId = subscriberId;
+ mMatchSubscriberIds = matchSubscriberIds;
+ mNetworkId = networkId;
+ mMetered = metered;
+ mRoaming = roaming;
+ mDefaultNetwork = defaultNetwork;
+
+ if (!isKnownMatchRule(matchRule)) {
+ Log.e(TAG, "Unknown network template rule " + matchRule
+ + " will not match any identity.");
+ }
+ }
+
+ private NetworkTemplate(Parcel in) {
+ mMatchRule = in.readInt();
+ mSubscriberId = in.readString();
+ mMatchSubscriberIds = in.createStringArray();
+ mNetworkId = in.readString();
+ mMetered = in.readInt();
+ mRoaming = in.readInt();
+ mDefaultNetwork = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mMatchRule);
+ dest.writeString(mSubscriberId);
+ dest.writeStringArray(mMatchSubscriberIds);
+ dest.writeString(mNetworkId);
+ dest.writeInt(mMetered);
+ dest.writeInt(mRoaming);
+ dest.writeInt(mDefaultNetwork);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder("NetworkTemplate: ");
+ builder.append("matchRule=").append(getMatchRuleName(mMatchRule));
+ if (mSubscriberId != null) {
+ builder.append(", subscriberId=").append(
+ NetworkIdentity.scrubSubscriberId(mSubscriberId));
+ }
+ if (mMatchSubscriberIds != null) {
+ builder.append(", matchSubscriberIds=").append(
+ Arrays.toString(NetworkIdentity.scrubSubscriberId(mMatchSubscriberIds)));
+ }
+ if (mNetworkId != null) {
+ builder.append(", networkId=").append(mNetworkId);
+ }
+ if (mMetered != METERED_ALL) {
+ builder.append(", metered=").append(NetworkStats.meteredToString(mMetered));
+ }
+ if (mRoaming != ROAMING_ALL) {
+ builder.append(", roaming=").append(NetworkStats.roamingToString(mRoaming));
+ }
+ if (mDefaultNetwork != DEFAULT_NETWORK_ALL) {
+ builder.append(", defaultNetwork=").append(NetworkStats.defaultNetworkToString(
+ mDefaultNetwork));
+ }
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mMatchRule, mSubscriberId, mNetworkId, mMetered, mRoaming,
+ mDefaultNetwork);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof NetworkTemplate) {
+ final NetworkTemplate other = (NetworkTemplate) obj;
+ return mMatchRule == other.mMatchRule
+ && Objects.equals(mSubscriberId, other.mSubscriberId)
+ && Objects.equals(mNetworkId, other.mNetworkId)
+ && mMetered == other.mMetered
+ && mRoaming == other.mRoaming
+ && mDefaultNetwork == other.mDefaultNetwork;
+ }
+ return false;
+ }
+
+ public boolean isMatchRuleMobile() {
+ switch (mMatchRule) {
+ case MATCH_MOBILE:
+ case MATCH_MOBILE_WILDCARD:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public boolean isPersistable() {
+ switch (mMatchRule) {
+ case MATCH_MOBILE_WILDCARD:
+ case MATCH_WIFI_WILDCARD:
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ @UnsupportedAppUsage
+ public int getMatchRule() {
+ return mMatchRule;
+ }
+
+ @UnsupportedAppUsage
+ public String getSubscriberId() {
+ return mSubscriberId;
+ }
+
+ public String getNetworkId() {
+ return mNetworkId;
+ }
+
+ /**
+ * Test if given {@link NetworkIdentity} matches this template.
+ */
+ public boolean matches(NetworkIdentity ident) {
+ if (!matchesMetered(ident)) return false;
+ if (!matchesRoaming(ident)) return false;
+ if (!matchesDefaultNetwork(ident)) return false;
+
+ switch (mMatchRule) {
+ case MATCH_MOBILE:
+ return matchesMobile(ident);
+ case MATCH_WIFI:
+ return matchesWifi(ident);
+ case MATCH_ETHERNET:
+ return matchesEthernet(ident);
+ case MATCH_MOBILE_WILDCARD:
+ return matchesMobileWildcard(ident);
+ case MATCH_WIFI_WILDCARD:
+ return matchesWifiWildcard(ident);
+ case MATCH_BLUETOOTH:
+ return matchesBluetooth(ident);
+ case MATCH_PROXY:
+ return matchesProxy(ident);
+ default:
+ // We have no idea what kind of network template we are, so we
+ // just claim not to match anything.
+ return false;
+ }
+ }
+
+ private boolean matchesMetered(NetworkIdentity ident) {
+ return (mMetered == METERED_ALL)
+ || (mMetered == METERED_YES && ident.mMetered)
+ || (mMetered == METERED_NO && !ident.mMetered);
+ }
+
+ private boolean matchesRoaming(NetworkIdentity ident) {
+ return (mRoaming == ROAMING_ALL)
+ || (mRoaming == ROAMING_YES && ident.mRoaming)
+ || (mRoaming == ROAMING_NO && !ident.mRoaming);
+ }
+
+ private boolean matchesDefaultNetwork(NetworkIdentity ident) {
+ return (mDefaultNetwork == DEFAULT_NETWORK_ALL)
+ || (mDefaultNetwork == DEFAULT_NETWORK_YES && ident.mDefaultNetwork)
+ || (mDefaultNetwork == DEFAULT_NETWORK_NO && !ident.mDefaultNetwork);
+ }
+
+ public boolean matchesSubscriberId(String subscriberId) {
+ return ArrayUtils.contains(mMatchSubscriberIds, subscriberId);
+ }
+
+ /**
+ * Check if mobile network with matching IMSI.
+ */
+ private boolean matchesMobile(NetworkIdentity ident) {
+ if (ident.mType == TYPE_WIMAX) {
+ // TODO: consider matching against WiMAX subscriber identity
+ return true;
+ } else {
+ return (sForceAllNetworkTypes || (ident.mType == TYPE_MOBILE && ident.mMetered))
+ && !ArrayUtils.isEmpty(mMatchSubscriberIds)
+ && ArrayUtils.contains(mMatchSubscriberIds, ident.mSubscriberId);
+ }
+ }
+
+ /**
+ * Check if matches Wi-Fi network template.
+ */
+ private boolean matchesWifi(NetworkIdentity ident) {
+ switch (ident.mType) {
+ case TYPE_WIFI:
+ return Objects.equals(
+ removeDoubleQuotes(mNetworkId), removeDoubleQuotes(ident.mNetworkId));
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Check if matches Ethernet network template.
+ */
+ private boolean matchesEthernet(NetworkIdentity ident) {
+ if (ident.mType == TYPE_ETHERNET) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean matchesMobileWildcard(NetworkIdentity ident) {
+ if (ident.mType == TYPE_WIMAX) {
+ return true;
+ } else {
+ return sForceAllNetworkTypes || (ident.mType == TYPE_MOBILE && ident.mMetered);
+ }
+ }
+
+ private boolean matchesWifiWildcard(NetworkIdentity ident) {
+ switch (ident.mType) {
+ case TYPE_WIFI:
+ case TYPE_WIFI_P2P:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Check if matches Bluetooth network template.
+ */
+ private boolean matchesBluetooth(NetworkIdentity ident) {
+ if (ident.mType == TYPE_BLUETOOTH) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Check if matches Proxy network template.
+ */
+ private boolean matchesProxy(NetworkIdentity ident) {
+ return ident.mType == TYPE_PROXY;
+ }
+
+ private static String getMatchRuleName(int matchRule) {
+ switch (matchRule) {
+ case MATCH_MOBILE:
+ return "MOBILE";
+ case MATCH_WIFI:
+ return "WIFI";
+ case MATCH_ETHERNET:
+ return "ETHERNET";
+ case MATCH_MOBILE_WILDCARD:
+ return "MOBILE_WILDCARD";
+ case MATCH_WIFI_WILDCARD:
+ return "WIFI_WILDCARD";
+ case MATCH_BLUETOOTH:
+ return "BLUETOOTH";
+ case MATCH_PROXY:
+ return "PROXY";
+ default:
+ return "UNKNOWN(" + matchRule + ")";
+ }
+ }
+
+ /**
+ * Examine the given template and normalize if it refers to a "merged"
+ * mobile subscriber. We pick the "lowest" merged subscriber as the primary
+ * for key purposes, and expand the template to match all other merged
+ * subscribers.
+ * <p>
+ * For example, given an incoming template matching B, and the currently
+ * active merge set [A,B], we'd return a new template that primarily matches
+ * A, but also matches B.
+ */
+ @UnsupportedAppUsage
+ public static NetworkTemplate normalize(NetworkTemplate template, String[] merged) {
+ if (template.isMatchRuleMobile() && ArrayUtils.contains(merged, template.mSubscriberId)) {
+ // Requested template subscriber is part of the merge group; return
+ // a template that matches all merged subscribers.
+ return new NetworkTemplate(template.mMatchRule, merged[0], merged,
+ template.mNetworkId);
+ } else {
+ return template;
+ }
+ }
+
+ @UnsupportedAppUsage
+ public static final @android.annotation.NonNull Creator<NetworkTemplate> CREATOR = new Creator<NetworkTemplate>() {
+ @Override
+ public NetworkTemplate createFromParcel(Parcel in) {
+ return new NetworkTemplate(in);
+ }
+
+ @Override
+ public NetworkTemplate[] newArray(int size) {
+ return new NetworkTemplate[size];
+ }
+ };
+
+ public byte[] getBytesForBackup() throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream out = new DataOutputStream(baos);
+
+ out.writeInt(BACKUP_VERSION);
+
+ out.writeInt(mMatchRule);
+ BackupUtils.writeString(out, mSubscriberId);
+ BackupUtils.writeString(out, mNetworkId);
+
+ return baos.toByteArray();
+ }
+
+ public static NetworkTemplate getNetworkTemplateFromBackup(DataInputStream in)
+ throws IOException, BackupUtils.BadVersionException {
+ int version = in.readInt();
+ if (version < 1 || version > BACKUP_VERSION) {
+ throw new BackupUtils.BadVersionException("Unknown Backup Serialization Version");
+ }
+
+ int matchRule = in.readInt();
+ String subscriberId = BackupUtils.readString(in);
+ String networkId = BackupUtils.readString(in);
+
+ if (!isKnownMatchRule(matchRule)) {
+ throw new BackupUtils.BadVersionException(
+ "Restored network template contains unknown match rule " + matchRule);
+ }
+
+ return new NetworkTemplate(matchRule, subscriberId, networkId);
+ }
+}
diff --git a/android/net/NetworkUtils.java b/android/net/NetworkUtils.java
new file mode 100644
index 0000000..d0f54b4
--- /dev/null
+++ b/android/net/NetworkUtils.java
@@ -0,0 +1,488 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+
+import android.annotation.NonNull;
+import android.annotation.UnsupportedAppUsage;
+import android.net.shared.Inet4AddressUtils;
+import android.os.Build;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+import android.util.Pair;
+
+import java.io.FileDescriptor;
+import java.math.BigInteger;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Collection;
+import java.util.Locale;
+import java.util.TreeSet;
+
+/**
+ * Native methods for managing network interfaces.
+ *
+ * {@hide}
+ */
+public class NetworkUtils {
+
+ private static final String TAG = "NetworkUtils";
+
+ /**
+ * Attaches a socket filter that drops all of incoming packets.
+ * @param fd the socket's {@link FileDescriptor}.
+ */
+ public static native void attachDropAllBPFFilter(FileDescriptor fd) throws SocketException;
+
+ /**
+ * Detaches a socket filter.
+ * @param fd the socket's {@link FileDescriptor}.
+ */
+ public static native void detachBPFFilter(FileDescriptor fd) throws SocketException;
+
+ /**
+ * Configures a socket for receiving ICMPv6 router solicitations and sending advertisements.
+ * @param fd the socket's {@link FileDescriptor}.
+ * @param ifIndex the interface index.
+ */
+ public native static void setupRaSocket(FileDescriptor fd, int ifIndex) throws SocketException;
+
+ /**
+ * Binds the current process to the network designated by {@code netId}. All sockets created
+ * in the future (and not explicitly bound via a bound {@link SocketFactory} (see
+ * {@link Network#getSocketFactory}) will be bound to this network. Note that if this
+ * {@code Network} ever disconnects all sockets created in this way will cease to work. This
+ * is by design so an application doesn't accidentally use sockets it thinks are still bound to
+ * a particular {@code Network}. Passing NETID_UNSET clears the binding.
+ */
+ public native static boolean bindProcessToNetwork(int netId);
+
+ /**
+ * Return the netId last passed to {@link #bindProcessToNetwork}, or NETID_UNSET if
+ * {@link #unbindProcessToNetwork} has been called since {@link #bindProcessToNetwork}.
+ */
+ public native static int getBoundNetworkForProcess();
+
+ /**
+ * Binds host resolutions performed by this process to the network designated by {@code netId}.
+ * {@link #bindProcessToNetwork} takes precedence over this setting. Passing NETID_UNSET clears
+ * the binding.
+ *
+ * @deprecated This is strictly for legacy usage to support startUsingNetworkFeature().
+ */
+ @Deprecated
+ public native static boolean bindProcessToNetworkForHostResolution(int netId);
+
+ /**
+ * Explicitly binds {@code socketfd} to the network designated by {@code netId}. This
+ * overrides any binding via {@link #bindProcessToNetwork}.
+ * @return 0 on success or negative errno on failure.
+ */
+ public native static int bindSocketToNetwork(int socketfd, int netId);
+
+ /**
+ * Protect {@code fd} from VPN connections. After protecting, data sent through
+ * this socket will go directly to the underlying network, so its traffic will not be
+ * forwarded through the VPN.
+ */
+ @UnsupportedAppUsage
+ public static boolean protectFromVpn(FileDescriptor fd) {
+ return protectFromVpn(fd.getInt$());
+ }
+
+ /**
+ * Protect {@code socketfd} from VPN connections. After protecting, data sent through
+ * this socket will go directly to the underlying network, so its traffic will not be
+ * forwarded through the VPN.
+ */
+ public native static boolean protectFromVpn(int socketfd);
+
+ /**
+ * Determine if {@code uid} can access network designated by {@code netId}.
+ * @return {@code true} if {@code uid} can access network, {@code false} otherwise.
+ */
+ public native static boolean queryUserAccess(int uid, int netId);
+
+ /**
+ * DNS resolver series jni method.
+ * Issue the query {@code msg} on the network designated by {@code netId}.
+ * {@code flags} is an additional config to control actual querying behavior.
+ * @return a file descriptor to watch for read events
+ */
+ public static native FileDescriptor resNetworkSend(
+ int netId, byte[] msg, int msglen, int flags) throws ErrnoException;
+
+ /**
+ * DNS resolver series jni method.
+ * Look up the {@code nsClass} {@code nsType} Resource Record (RR) associated
+ * with Domain Name {@code dname} on the network designated by {@code netId}.
+ * {@code flags} is an additional config to control actual querying behavior.
+ * @return a file descriptor to watch for read events
+ */
+ public static native FileDescriptor resNetworkQuery(
+ int netId, String dname, int nsClass, int nsType, int flags) throws ErrnoException;
+
+ /**
+ * DNS resolver series jni method.
+ * Read a result for the query associated with the {@code fd}.
+ * @return DnsResponse containing blob answer and rcode
+ */
+ public static native DnsResolver.DnsResponse resNetworkResult(FileDescriptor fd)
+ throws ErrnoException;
+
+ /**
+ * DNS resolver series jni method.
+ * Attempts to cancel the in-progress query associated with the {@code fd}.
+ */
+ public static native void resNetworkCancel(FileDescriptor fd);
+
+ /**
+ * DNS resolver series jni method.
+ * Attempts to get network which resolver will use if no network is explicitly selected.
+ */
+ public static native Network getDnsNetwork() throws ErrnoException;
+
+ /**
+ * Get the tcp repair window associated with the {@code fd}.
+ *
+ * @param fd the tcp socket's {@link FileDescriptor}.
+ * @return a {@link TcpRepairWindow} object indicates tcp window size.
+ */
+ public static native TcpRepairWindow getTcpRepairWindow(FileDescriptor fd)
+ throws ErrnoException;
+
+ /**
+ * @see Inet4AddressUtils#intToInet4AddressHTL(int)
+ * @deprecated Use either {@link Inet4AddressUtils#intToInet4AddressHTH(int)}
+ * or {@link Inet4AddressUtils#intToInet4AddressHTL(int)}
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public static InetAddress intToInetAddress(int hostAddress) {
+ return Inet4AddressUtils.intToInet4AddressHTL(hostAddress);
+ }
+
+ /**
+ * @see Inet4AddressUtils#inet4AddressToIntHTL(Inet4Address)
+ * @deprecated Use either {@link Inet4AddressUtils#inet4AddressToIntHTH(Inet4Address)}
+ * or {@link Inet4AddressUtils#inet4AddressToIntHTL(Inet4Address)}
+ */
+ @Deprecated
+ public static int inetAddressToInt(Inet4Address inetAddr)
+ throws IllegalArgumentException {
+ return Inet4AddressUtils.inet4AddressToIntHTL(inetAddr);
+ }
+
+ /**
+ * @see Inet4AddressUtils#prefixLengthToV4NetmaskIntHTL(int)
+ * @deprecated Use either {@link Inet4AddressUtils#prefixLengthToV4NetmaskIntHTH(int)}
+ * or {@link Inet4AddressUtils#prefixLengthToV4NetmaskIntHTL(int)}
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public static int prefixLengthToNetmaskInt(int prefixLength)
+ throws IllegalArgumentException {
+ return Inet4AddressUtils.prefixLengthToV4NetmaskIntHTL(prefixLength);
+ }
+
+ /**
+ * Convert a IPv4 netmask integer to a prefix length
+ * @param netmask as an integer (0xff000000 for a /8 subnet)
+ * @return the network prefix length
+ */
+ public static int netmaskIntToPrefixLength(int netmask) {
+ return Integer.bitCount(netmask);
+ }
+
+ /**
+ * Convert an IPv4 netmask to a prefix length, checking that the netmask is contiguous.
+ * @param netmask as a {@code Inet4Address}.
+ * @return the network prefix length
+ * @throws IllegalArgumentException the specified netmask was not contiguous.
+ * @hide
+ * @deprecated use {@link Inet4AddressUtils#netmaskToPrefixLength(Inet4Address)}
+ */
+ @UnsupportedAppUsage
+ @Deprecated
+ public static int netmaskToPrefixLength(Inet4Address netmask) {
+ // This is only here because some apps seem to be using it (@UnsupportedAppUsage).
+ return Inet4AddressUtils.netmaskToPrefixLength(netmask);
+ }
+
+
+ /**
+ * Create an InetAddress from a string where the string must be a standard
+ * representation of a V4 or V6 address. Avoids doing a DNS lookup on failure
+ * but it will throw an IllegalArgumentException in that case.
+ * @param addrString
+ * @return the InetAddress
+ * @hide
+ * @deprecated Use {@link InetAddresses#parseNumericAddress(String)}, if possible.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ @Deprecated
+ public static InetAddress numericToInetAddress(String addrString)
+ throws IllegalArgumentException {
+ return InetAddress.parseNumericAddress(addrString);
+ }
+
+ /**
+ * Masks a raw IP address byte array with the specified prefix length.
+ */
+ public static void maskRawAddress(byte[] array, int prefixLength) {
+ if (prefixLength < 0 || prefixLength > array.length * 8) {
+ throw new RuntimeException("IP address with " + array.length +
+ " bytes has invalid prefix length " + prefixLength);
+ }
+
+ int offset = prefixLength / 8;
+ int remainder = prefixLength % 8;
+ byte mask = (byte)(0xFF << (8 - remainder));
+
+ if (offset < array.length) array[offset] = (byte)(array[offset] & mask);
+
+ offset++;
+
+ for (; offset < array.length; offset++) {
+ array[offset] = 0;
+ }
+ }
+
+ /**
+ * Get InetAddress masked with prefixLength. Will never return null.
+ * @param address the IP address to mask with
+ * @param prefixLength the prefixLength used to mask the IP
+ */
+ public static InetAddress getNetworkPart(InetAddress address, int prefixLength) {
+ byte[] array = address.getAddress();
+ maskRawAddress(array, prefixLength);
+
+ InetAddress netPart = null;
+ try {
+ netPart = InetAddress.getByAddress(array);
+ } catch (UnknownHostException e) {
+ throw new RuntimeException("getNetworkPart error - " + e.toString());
+ }
+ return netPart;
+ }
+
+ /**
+ * Returns the implicit netmask of an IPv4 address, as was the custom before 1993.
+ */
+ @UnsupportedAppUsage
+ public static int getImplicitNetmask(Inet4Address address) {
+ // Only here because it seems to be used by apps
+ return Inet4AddressUtils.getImplicitNetmask(address);
+ }
+
+ /**
+ * Utility method to parse strings such as "192.0.2.5/24" or "2001:db8::cafe:d00d/64".
+ * @hide
+ */
+ public static Pair<InetAddress, Integer> parseIpAndMask(String ipAndMaskString) {
+ InetAddress address = null;
+ int prefixLength = -1;
+ try {
+ String[] pieces = ipAndMaskString.split("/", 2);
+ prefixLength = Integer.parseInt(pieces[1]);
+ address = InetAddress.parseNumericAddress(pieces[0]);
+ } catch (NullPointerException e) { // Null string.
+ } catch (ArrayIndexOutOfBoundsException e) { // No prefix length.
+ } catch (NumberFormatException e) { // Non-numeric prefix.
+ } catch (IllegalArgumentException e) { // Invalid IP address.
+ }
+
+ if (address == null || prefixLength == -1) {
+ throw new IllegalArgumentException("Invalid IP address and mask " + ipAndMaskString);
+ }
+
+ return new Pair<InetAddress, Integer>(address, prefixLength);
+ }
+
+ /**
+ * Check if IP address type is consistent between two InetAddress.
+ * @return true if both are the same type. False otherwise.
+ */
+ public static boolean addressTypeMatches(InetAddress left, InetAddress right) {
+ return (((left instanceof Inet4Address) && (right instanceof Inet4Address)) ||
+ ((left instanceof Inet6Address) && (right instanceof Inet6Address)));
+ }
+
+ /**
+ * Convert a 32 char hex string into a Inet6Address.
+ * throws a runtime exception if the string isn't 32 chars, isn't hex or can't be
+ * made into an Inet6Address
+ * @param addrHexString a 32 character hex string representing an IPv6 addr
+ * @return addr an InetAddress representation for the string
+ */
+ public static InetAddress hexToInet6Address(String addrHexString)
+ throws IllegalArgumentException {
+ try {
+ return numericToInetAddress(String.format(Locale.US, "%s:%s:%s:%s:%s:%s:%s:%s",
+ addrHexString.substring(0,4), addrHexString.substring(4,8),
+ addrHexString.substring(8,12), addrHexString.substring(12,16),
+ addrHexString.substring(16,20), addrHexString.substring(20,24),
+ addrHexString.substring(24,28), addrHexString.substring(28,32)));
+ } catch (Exception e) {
+ Log.e("NetworkUtils", "error in hexToInet6Address(" + addrHexString + "): " + e);
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Create a string array of host addresses from a collection of InetAddresses
+ * @param addrs a Collection of InetAddresses
+ * @return an array of Strings containing their host addresses
+ */
+ public static String[] makeStrings(Collection<InetAddress> addrs) {
+ String[] result = new String[addrs.size()];
+ int i = 0;
+ for (InetAddress addr : addrs) {
+ result[i++] = addr.getHostAddress();
+ }
+ return result;
+ }
+
+ /**
+ * Trim leading zeros from IPv4 address strings
+ * Our base libraries will interpret that as octel..
+ * Must leave non v4 addresses and host names alone.
+ * For example, 192.168.000.010 -> 192.168.0.10
+ * TODO - fix base libraries and remove this function
+ * @param addr a string representing an ip addr
+ * @return a string propertly trimmed
+ */
+ @UnsupportedAppUsage
+ public static String trimV4AddrZeros(String addr) {
+ if (addr == null) return null;
+ String[] octets = addr.split("\\.");
+ if (octets.length != 4) return addr;
+ StringBuilder builder = new StringBuilder(16);
+ String result = null;
+ for (int i = 0; i < 4; i++) {
+ try {
+ if (octets[i].length() > 3) return addr;
+ builder.append(Integer.parseInt(octets[i]));
+ } catch (NumberFormatException e) {
+ return addr;
+ }
+ if (i < 3) builder.append('.');
+ }
+ result = builder.toString();
+ return result;
+ }
+
+ /**
+ * Returns a prefix set without overlaps.
+ *
+ * This expects the src set to be sorted from shorter to longer. Results are undefined
+ * failing this condition. The returned prefix set is sorted in the same order as the
+ * passed set, with the same comparator.
+ */
+ private static TreeSet<IpPrefix> deduplicatePrefixSet(final TreeSet<IpPrefix> src) {
+ final TreeSet<IpPrefix> dst = new TreeSet<>(src.comparator());
+ // Prefixes match addresses that share their upper part up to their length, therefore
+ // the only kind of possible overlap in two prefixes is strict inclusion of the longer
+ // (more restrictive) in the shorter (including equivalence if they have the same
+ // length).
+ // Because prefixes in the src set are sorted from shorter to longer, deduplicating
+ // is done by simply iterating in order, and not adding any longer prefix that is
+ // already covered by a shorter one.
+ newPrefixes:
+ for (IpPrefix newPrefix : src) {
+ for (IpPrefix existingPrefix : dst) {
+ if (existingPrefix.containsPrefix(newPrefix)) {
+ continue newPrefixes;
+ }
+ }
+ dst.add(newPrefix);
+ }
+ return dst;
+ }
+
+ /**
+ * Returns how many IPv4 addresses match any of the prefixes in the passed ordered set.
+ *
+ * Obviously this returns an integral value between 0 and 2**32.
+ * The behavior is undefined if any of the prefixes is not an IPv4 prefix or if the
+ * set is not ordered smallest prefix to longer prefix.
+ *
+ * @param prefixes the set of prefixes, ordered by length
+ */
+ public static long routedIPv4AddressCount(final TreeSet<IpPrefix> prefixes) {
+ long routedIPCount = 0;
+ for (final IpPrefix prefix : deduplicatePrefixSet(prefixes)) {
+ if (!prefix.isIPv4()) {
+ Log.wtf(TAG, "Non-IPv4 prefix in routedIPv4AddressCount");
+ }
+ int rank = 32 - prefix.getPrefixLength();
+ routedIPCount += 1L << rank;
+ }
+ return routedIPCount;
+ }
+
+ /**
+ * Returns how many IPv6 addresses match any of the prefixes in the passed ordered set.
+ *
+ * This returns a BigInteger between 0 and 2**128.
+ * The behavior is undefined if any of the prefixes is not an IPv6 prefix or if the
+ * set is not ordered smallest prefix to longer prefix.
+ */
+ public static BigInteger routedIPv6AddressCount(final TreeSet<IpPrefix> prefixes) {
+ BigInteger routedIPCount = BigInteger.ZERO;
+ for (final IpPrefix prefix : deduplicatePrefixSet(prefixes)) {
+ if (!prefix.isIPv6()) {
+ Log.wtf(TAG, "Non-IPv6 prefix in routedIPv6AddressCount");
+ }
+ int rank = 128 - prefix.getPrefixLength();
+ routedIPCount = routedIPCount.add(BigInteger.ONE.shiftLeft(rank));
+ }
+ return routedIPCount;
+ }
+
+ private static final int[] ADDRESS_FAMILIES = new int[] {AF_INET, AF_INET6};
+
+ /**
+ * Returns true if the hostname is weakly validated.
+ * @param hostname Name of host to validate.
+ * @return True if it's a valid-ish hostname.
+ *
+ * @hide
+ */
+ public static boolean isWeaklyValidatedHostname(@NonNull String hostname) {
+ // TODO(b/34953048): Use a validation method that permits more accurate,
+ // but still inexpensive, checking of likely valid DNS hostnames.
+ final String weakHostnameRegex = "^[a-zA-Z0-9_.-]+$";
+ if (!hostname.matches(weakHostnameRegex)) {
+ return false;
+ }
+
+ for (int address_family : ADDRESS_FAMILIES) {
+ if (Os.inet_pton(address_family, hostname) != null) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/android/net/NetworkWatchlistManager.java b/android/net/NetworkWatchlistManager.java
new file mode 100644
index 0000000..49047d3
--- /dev/null
+++ b/android/net/NetworkWatchlistManager.java
@@ -0,0 +1,101 @@
+/*
+ * 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 android.net;
+
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.net.INetworkWatchlistManager;
+import com.android.internal.util.Preconditions;
+
+/**
+ * Class that manage network watchlist in system.
+ * @hide
+ */
+@SystemService(Context.NETWORK_WATCHLIST_SERVICE)
+public class NetworkWatchlistManager {
+
+ private static final String TAG = "NetworkWatchlistManager";
+ private static final String SHARED_MEMORY_TAG = "NETWORK_WATCHLIST_SHARED_MEMORY";
+
+ private final Context mContext;
+ private final INetworkWatchlistManager mNetworkWatchlistManager;
+
+ /**
+ * @hide
+ */
+ public NetworkWatchlistManager(Context context, INetworkWatchlistManager manager) {
+ mContext = context;
+ mNetworkWatchlistManager = manager;
+ }
+
+ /**
+ * @hide
+ */
+ public NetworkWatchlistManager(Context context) {
+ mContext = Preconditions.checkNotNull(context, "missing context");
+ mNetworkWatchlistManager = (INetworkWatchlistManager)
+ INetworkWatchlistManager.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_WATCHLIST_SERVICE));
+ }
+
+ /**
+ * Report network watchlist records if necessary.
+ *
+ * Watchlist report process will summarize records into a single report, then the
+ * report will be processed by differential privacy framework and stored on disk.
+ *
+ * @hide
+ */
+ public void reportWatchlistIfNecessary() {
+ try {
+ mNetworkWatchlistManager.reportWatchlistIfNecessary();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Cannot report records", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Reload network watchlist.
+ *
+ * @hide
+ */
+ public void reloadWatchlist() {
+ try {
+ mNetworkWatchlistManager.reloadWatchlist();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to reload watchlist");
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get Network Watchlist config file hash.
+ */
+ public byte[] getWatchlistConfigHash() {
+ try {
+ return mNetworkWatchlistManager.getWatchlistConfigHash();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to get watchlist config hash");
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/android/net/PacProxySelector.java b/android/net/PacProxySelector.java
new file mode 100644
index 0000000..85bf79a
--- /dev/null
+++ b/android/net/PacProxySelector.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2013 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;
+
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.net.IProxyService;
+import com.google.android.collect.Lists;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
+import java.net.Proxy;
+import java.net.Proxy.Type;
+import java.net.ProxySelector;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+
+/**
+ * @hide
+ */
+public class PacProxySelector extends ProxySelector {
+ private static final String TAG = "PacProxySelector";
+ public static final String PROXY_SERVICE = "com.android.net.IProxyService";
+ private static final String SOCKS = "SOCKS ";
+ private static final String PROXY = "PROXY ";
+
+ private IProxyService mProxyService;
+ private final List<Proxy> mDefaultList;
+
+ public PacProxySelector() {
+ mProxyService = IProxyService.Stub.asInterface(
+ ServiceManager.getService(PROXY_SERVICE));
+ if (mProxyService == null) {
+ // Added because of b10267814 where mako is restarting.
+ Log.e(TAG, "PacManager: no proxy service");
+ }
+ mDefaultList = Lists.newArrayList(java.net.Proxy.NO_PROXY);
+ }
+
+ @Override
+ public List<Proxy> select(URI uri) {
+ if (mProxyService == null) {
+ mProxyService = IProxyService.Stub.asInterface(
+ ServiceManager.getService(PROXY_SERVICE));
+ }
+ if (mProxyService == null) {
+ Log.e(TAG, "select: no proxy service return NO_PROXY");
+ return Lists.newArrayList(java.net.Proxy.NO_PROXY);
+ }
+ String response = null;
+ String urlString;
+ try {
+ // Strip path and username/password from URI so it's not visible to PAC script. The
+ // path often contains credentials the app does not want exposed to a potentially
+ // malicious PAC script.
+ if (!"http".equalsIgnoreCase(uri.getScheme())) {
+ uri = new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), "/", null, null);
+ }
+ urlString = uri.toURL().toString();
+ } catch (URISyntaxException e) {
+ urlString = uri.getHost();
+ } catch (MalformedURLException e) {
+ urlString = uri.getHost();
+ }
+ try {
+ response = mProxyService.resolvePacFile(uri.getHost(), urlString);
+ } catch (Exception e) {
+ Log.e(TAG, "Error resolving PAC File", e);
+ }
+ if (response == null) {
+ return mDefaultList;
+ }
+
+ return parseResponse(response);
+ }
+
+ private static List<Proxy> parseResponse(String response) {
+ String[] split = response.split(";");
+ List<Proxy> ret = Lists.newArrayList();
+ for (String s : split) {
+ String trimmed = s.trim();
+ if (trimmed.equals("DIRECT")) {
+ ret.add(java.net.Proxy.NO_PROXY);
+ } else if (trimmed.startsWith(PROXY)) {
+ Proxy proxy = proxyFromHostPort(Type.HTTP, trimmed.substring(PROXY.length()));
+ if (proxy != null) {
+ ret.add(proxy);
+ }
+ } else if (trimmed.startsWith(SOCKS)) {
+ Proxy proxy = proxyFromHostPort(Type.SOCKS, trimmed.substring(SOCKS.length()));
+ if (proxy != null) {
+ ret.add(proxy);
+ }
+ }
+ }
+ if (ret.size() == 0) {
+ ret.add(java.net.Proxy.NO_PROXY);
+ }
+ return ret;
+ }
+
+ private static Proxy proxyFromHostPort(Proxy.Type type, String hostPortString) {
+ try {
+ String[] hostPort = hostPortString.split(":");
+ String host = hostPort[0];
+ int port = Integer.parseInt(hostPort[1]);
+ return new Proxy(type, InetSocketAddress.createUnresolved(host, port));
+ } catch (NumberFormatException|ArrayIndexOutOfBoundsException e) {
+ Log.d(TAG, "Unable to parse proxy " + hostPortString + " " + e);
+ return null;
+ }
+ }
+
+ @Override
+ public void connectFailed(URI uri, SocketAddress address, IOException failure) {
+
+ }
+
+}
diff --git a/android/net/ParseException.java b/android/net/ParseException.java
new file mode 100644
index 0000000..bcfdd7e
--- /dev/null
+++ b/android/net/ParseException.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2006 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;
+
+import android.annotation.NonNull;
+
+/**
+ * Thrown when parsing failed.
+ */
+// See non-public class {@link WebAddress}.
+public class ParseException extends RuntimeException {
+ public String response;
+
+ ParseException(@NonNull String response) {
+ super(response);
+ this.response = response;
+ }
+
+ ParseException(@NonNull String response, @NonNull Throwable cause) {
+ super(response, cause);
+ this.response = response;
+ }
+}
diff --git a/android/net/PrivateDnsConnectivityChecker.java b/android/net/PrivateDnsConnectivityChecker.java
new file mode 100644
index 0000000..cfd458c
--- /dev/null
+++ b/android/net/PrivateDnsConnectivityChecker.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * Class for testing connectivity to DNS-over-TLS servers.
+ * {@hide}
+ */
+public class PrivateDnsConnectivityChecker {
+ private static final String TAG = "NetworkUtils";
+
+ private static final int PRIVATE_DNS_PORT = 853;
+ private static final int CONNECTION_TIMEOUT_MS = 5000;
+
+ private PrivateDnsConnectivityChecker() { }
+
+ /**
+ * checks that a provided host can perform a TLS handshake on port 853.
+ * @param hostname host to connect to.
+ */
+ public static boolean canConnectToPrivateDnsServer(@NonNull String hostname) {
+ final SocketFactory factory = SSLSocketFactory.getDefault();
+ TrafficStats.setThreadStatsTag(TrafficStats.TAG_SYSTEM_APP);
+
+ try (SSLSocket socket = (SSLSocket) factory.createSocket()) {
+ socket.setSoTimeout(CONNECTION_TIMEOUT_MS);
+ socket.connect(new InetSocketAddress(hostname, PRIVATE_DNS_PORT));
+ if (!socket.isConnected()) {
+ Log.w(TAG, String.format("Connection to %s failed.", hostname));
+ return false;
+ }
+ socket.startHandshake();
+ Log.w(TAG, String.format("TLS handshake to %s succeeded.", hostname));
+ return true;
+ } catch (IOException e) {
+ Log.w(TAG, String.format("TLS handshake to %s failed.", hostname), e);
+ return false;
+ }
+ }
+}
diff --git a/android/net/Proxy.java b/android/net/Proxy.java
new file mode 100644
index 0000000..4600942
--- /dev/null
+++ b/android/net/Proxy.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2007 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;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.net.InetSocketAddress;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A convenience class for accessing the user and default proxy
+ * settings.
+ */
+public final class Proxy {
+
+ private static final String TAG = "Proxy";
+
+ private static final ProxySelector sDefaultProxySelector;
+
+ /**
+ * Used to notify an app that's caching the proxy that either the default
+ * connection has changed or any connection's proxy has changed. The new
+ * proxy should be queried using {@link ConnectivityManager#getDefaultProxy()}.
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String PROXY_CHANGE_ACTION = "android.intent.action.PROXY_CHANGE";
+ /**
+ * Intent extra included with {@link #PROXY_CHANGE_ACTION} intents.
+ * It describes the new proxy being used (as a {@link ProxyInfo} object).
+ * @deprecated Because {@code PROXY_CHANGE_ACTION} is sent whenever the proxy
+ * for any network on the system changes, applications should always use
+ * {@link ConnectivityManager#getDefaultProxy()} or
+ * {@link ConnectivityManager#getLinkProperties(Network)}.{@link LinkProperties#getHttpProxy()}
+ * to get the proxy for the Network(s) they are using.
+ */
+ @Deprecated
+ public static final String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO";
+
+ /** @hide */
+ public static final int PROXY_VALID = 0;
+ /** @hide */
+ public static final int PROXY_HOSTNAME_EMPTY = 1;
+ /** @hide */
+ public static final int PROXY_HOSTNAME_INVALID = 2;
+ /** @hide */
+ public static final int PROXY_PORT_EMPTY = 3;
+ /** @hide */
+ public static final int PROXY_PORT_INVALID = 4;
+ /** @hide */
+ public static final int PROXY_EXCLLIST_INVALID = 5;
+
+ private static ConnectivityManager sConnectivityManager = null;
+
+ // Hostname / IP REGEX validation
+ // Matches blank input, ips, and domain names
+ private static final String NAME_IP_REGEX =
+ "[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*(\\.[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*)*";
+
+ private static final String HOSTNAME_REGEXP = "^$|^" + NAME_IP_REGEX + "$";
+
+ private static final Pattern HOSTNAME_PATTERN;
+
+ private static final String EXCL_REGEX =
+ "[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*(\\.[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*)*";
+
+ private static final String EXCLLIST_REGEXP = "^$|^" + EXCL_REGEX + "(," + EXCL_REGEX + ")*$";
+
+ private static final Pattern EXCLLIST_PATTERN;
+
+ static {
+ HOSTNAME_PATTERN = Pattern.compile(HOSTNAME_REGEXP);
+ EXCLLIST_PATTERN = Pattern.compile(EXCLLIST_REGEXP);
+ sDefaultProxySelector = ProxySelector.getDefault();
+ }
+
+ /**
+ * Return the proxy object to be used for the URL given as parameter.
+ * @param ctx A Context used to get the settings for the proxy host.
+ * @param url A URL to be accessed. Used to evaluate exclusion list.
+ * @return Proxy (java.net) object containing the host name. If the
+ * user did not set a hostname it returns the default host.
+ * A null value means that no host is to be used.
+ * {@hide}
+ */
+ @UnsupportedAppUsage
+ public static final java.net.Proxy getProxy(Context ctx, String url) {
+ String host = "";
+ if ((url != null) && !isLocalHost(host)) {
+ URI uri = URI.create(url);
+ ProxySelector proxySelector = ProxySelector.getDefault();
+
+ List<java.net.Proxy> proxyList = proxySelector.select(uri);
+
+ if (proxyList.size() > 0) {
+ return proxyList.get(0);
+ }
+ }
+ return java.net.Proxy.NO_PROXY;
+ }
+
+
+ /**
+ * Return the proxy host set by the user.
+ * @param ctx A Context used to get the settings for the proxy host.
+ * @return String containing the host name. If the user did not set a host
+ * name it returns the default host. A null value means that no
+ * host is to be used.
+ * @deprecated Use standard java vm proxy values to find the host, port
+ * and exclusion list. This call ignores the exclusion list.
+ */
+ @Deprecated
+ public static final String getHost(Context ctx) {
+ java.net.Proxy proxy = getProxy(ctx, null);
+ if (proxy == java.net.Proxy.NO_PROXY) return null;
+ try {
+ return ((InetSocketAddress)(proxy.address())).getHostName();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Return the proxy port set by the user.
+ * @param ctx A Context used to get the settings for the proxy port.
+ * @return The port number to use or -1 if no proxy is to be used.
+ * @deprecated Use standard java vm proxy values to find the host, port
+ * and exclusion list. This call ignores the exclusion list.
+ */
+ @Deprecated
+ public static final int getPort(Context ctx) {
+ java.net.Proxy proxy = getProxy(ctx, null);
+ if (proxy == java.net.Proxy.NO_PROXY) return -1;
+ try {
+ return ((InetSocketAddress)(proxy.address())).getPort();
+ } catch (Exception e) {
+ return -1;
+ }
+ }
+
+ /**
+ * Return the default proxy host specified by the carrier.
+ * @return String containing the host name or null if there is no proxy for
+ * this carrier.
+ * @deprecated Use standard java vm proxy values to find the host, port and
+ * exclusion list. This call ignores the exclusion list and no
+ * longer reports only mobile-data apn-based proxy values.
+ */
+ @Deprecated
+ public static final String getDefaultHost() {
+ String host = System.getProperty("http.proxyHost");
+ if (TextUtils.isEmpty(host)) return null;
+ return host;
+ }
+
+ /**
+ * Return the default proxy port specified by the carrier.
+ * @return The port number to be used with the proxy host or -1 if there is
+ * no proxy for this carrier.
+ * @deprecated Use standard java vm proxy values to find the host, port and
+ * exclusion list. This call ignores the exclusion list and no
+ * longer reports only mobile-data apn-based proxy values.
+ */
+ @Deprecated
+ public static final int getDefaultPort() {
+ if (getDefaultHost() == null) return -1;
+ try {
+ return Integer.parseInt(System.getProperty("http.proxyPort"));
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ }
+
+ private static final boolean isLocalHost(String host) {
+ if (host == null) {
+ return false;
+ }
+ try {
+ if (host != null) {
+ if (host.equalsIgnoreCase("localhost")) {
+ return true;
+ }
+ if (NetworkUtils.numericToInetAddress(host).isLoopbackAddress()) {
+ return true;
+ }
+ }
+ } catch (IllegalArgumentException iex) {
+ }
+ return false;
+ }
+
+ /**
+ * Validate syntax of hostname, port and exclusion list entries
+ * {@hide}
+ */
+ public static int validate(String hostname, String port, String exclList) {
+ Matcher match = HOSTNAME_PATTERN.matcher(hostname);
+ Matcher listMatch = EXCLLIST_PATTERN.matcher(exclList);
+
+ if (!match.matches()) return PROXY_HOSTNAME_INVALID;
+
+ if (!listMatch.matches()) return PROXY_EXCLLIST_INVALID;
+
+ if (hostname.length() > 0 && port.length() == 0) return PROXY_PORT_EMPTY;
+
+ if (port.length() > 0) {
+ if (hostname.length() == 0) return PROXY_HOSTNAME_EMPTY;
+ int portVal = -1;
+ try {
+ portVal = Integer.parseInt(port);
+ } catch (NumberFormatException ex) {
+ return PROXY_PORT_INVALID;
+ }
+ if (portVal <= 0 || portVal > 0xFFFF) return PROXY_PORT_INVALID;
+ }
+ return PROXY_VALID;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public static final void setHttpProxySystemProperty(ProxyInfo p) {
+ String host = null;
+ String port = null;
+ String exclList = null;
+ Uri pacFileUrl = Uri.EMPTY;
+ if (p != null) {
+ host = p.getHost();
+ port = Integer.toString(p.getPort());
+ exclList = p.getExclusionListAsString();
+ pacFileUrl = p.getPacFileUrl();
+ }
+ setHttpProxySystemProperty(host, port, exclList, pacFileUrl);
+ }
+
+ /** @hide */
+ public static final void setHttpProxySystemProperty(String host, String port, String exclList,
+ Uri pacFileUrl) {
+ if (exclList != null) exclList = exclList.replace(",", "|");
+ if (false) Log.d(TAG, "setHttpProxySystemProperty :"+host+":"+port+" - "+exclList);
+ if (host != null) {
+ System.setProperty("http.proxyHost", host);
+ System.setProperty("https.proxyHost", host);
+ } else {
+ System.clearProperty("http.proxyHost");
+ System.clearProperty("https.proxyHost");
+ }
+ if (port != null) {
+ System.setProperty("http.proxyPort", port);
+ System.setProperty("https.proxyPort", port);
+ } else {
+ System.clearProperty("http.proxyPort");
+ System.clearProperty("https.proxyPort");
+ }
+ if (exclList != null) {
+ System.setProperty("http.nonProxyHosts", exclList);
+ System.setProperty("https.nonProxyHosts", exclList);
+ } else {
+ System.clearProperty("http.nonProxyHosts");
+ System.clearProperty("https.nonProxyHosts");
+ }
+ if (!Uri.EMPTY.equals(pacFileUrl)) {
+ ProxySelector.setDefault(new PacProxySelector());
+ } else {
+ ProxySelector.setDefault(sDefaultProxySelector);
+ }
+ }
+}
diff --git a/android/net/ProxyInfo.java b/android/net/ProxyInfo.java
new file mode 100644
index 0000000..807c467
--- /dev/null
+++ b/android/net/ProxyInfo.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2010 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;
+
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.net.InetSocketAddress;
+import java.net.URLConnection;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Describes a proxy configuration.
+ *
+ * Proxy configurations are already integrated within the {@code java.net} and
+ * Apache HTTP stack. So {@link URLConnection} and Apache's {@code HttpClient} will use
+ * them automatically.
+ *
+ * Other HTTP stacks will need to obtain the proxy info from
+ * {@link Proxy#PROXY_CHANGE_ACTION} broadcast as the extra {@link Proxy#EXTRA_PROXY_INFO}.
+ */
+public class ProxyInfo implements Parcelable {
+
+ private final String mHost;
+ private final int mPort;
+ private final String mExclusionList;
+ private final String[] mParsedExclusionList;
+ private final Uri mPacFileUrl;
+
+ /**
+ *@hide
+ */
+ public static final String LOCAL_EXCL_LIST = "";
+ /**
+ *@hide
+ */
+ public static final int LOCAL_PORT = -1;
+ /**
+ *@hide
+ */
+ public static final String LOCAL_HOST = "localhost";
+
+ /**
+ * Constructs a {@link ProxyInfo} object that points at a Direct proxy
+ * on the specified host and port.
+ */
+ public static ProxyInfo buildDirectProxy(String host, int port) {
+ return new ProxyInfo(host, port, null);
+ }
+
+ /**
+ * Constructs a {@link ProxyInfo} object that points at a Direct proxy
+ * on the specified host and port.
+ *
+ * The proxy will not be used to access any host in exclusion list, exclList.
+ *
+ * @param exclList Hosts to exclude using the proxy on connections for. These
+ * hosts can use wildcards such as *.example.com.
+ */
+ public static ProxyInfo buildDirectProxy(String host, int port, List<String> exclList) {
+ String[] array = exclList.toArray(new String[exclList.size()]);
+ return new ProxyInfo(host, port, TextUtils.join(",", array), array);
+ }
+
+ /**
+ * Construct a {@link ProxyInfo} that will download and run the PAC script
+ * at the specified URL.
+ */
+ public static ProxyInfo buildPacProxy(Uri pacUri) {
+ return new ProxyInfo(pacUri);
+ }
+
+ /**
+ * Create a ProxyProperties that points at a HTTP Proxy.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public ProxyInfo(String host, int port, String exclList) {
+ mHost = host;
+ mPort = port;
+ mExclusionList = exclList;
+ mParsedExclusionList = parseExclusionList(mExclusionList);
+ mPacFileUrl = Uri.EMPTY;
+ }
+
+ /**
+ * Create a ProxyProperties that points at a PAC URL.
+ * @hide
+ */
+ public ProxyInfo(Uri pacFileUrl) {
+ mHost = LOCAL_HOST;
+ mPort = LOCAL_PORT;
+ mExclusionList = LOCAL_EXCL_LIST;
+ mParsedExclusionList = parseExclusionList(mExclusionList);
+ if (pacFileUrl == null) {
+ throw new NullPointerException();
+ }
+ mPacFileUrl = pacFileUrl;
+ }
+
+ /**
+ * Create a ProxyProperties that points at a PAC URL.
+ * @hide
+ */
+ public ProxyInfo(String pacFileUrl) {
+ mHost = LOCAL_HOST;
+ mPort = LOCAL_PORT;
+ mExclusionList = LOCAL_EXCL_LIST;
+ mParsedExclusionList = parseExclusionList(mExclusionList);
+ mPacFileUrl = Uri.parse(pacFileUrl);
+ }
+
+ /**
+ * Only used in PacManager after Local Proxy is bound.
+ * @hide
+ */
+ public ProxyInfo(Uri pacFileUrl, int localProxyPort) {
+ mHost = LOCAL_HOST;
+ mPort = localProxyPort;
+ mExclusionList = LOCAL_EXCL_LIST;
+ mParsedExclusionList = parseExclusionList(mExclusionList);
+ if (pacFileUrl == null) {
+ throw new NullPointerException();
+ }
+ mPacFileUrl = pacFileUrl;
+ }
+
+ private static String[] parseExclusionList(String exclusionList) {
+ if (exclusionList == null) {
+ return new String[0];
+ } else {
+ return exclusionList.toLowerCase(Locale.ROOT).split(",");
+ }
+ }
+
+ private ProxyInfo(String host, int port, String exclList, String[] parsedExclList) {
+ mHost = host;
+ mPort = port;
+ mExclusionList = exclList;
+ mParsedExclusionList = parsedExclList;
+ mPacFileUrl = Uri.EMPTY;
+ }
+
+ // copy constructor instead of clone
+ /**
+ * @hide
+ */
+ public ProxyInfo(ProxyInfo source) {
+ if (source != null) {
+ mHost = source.getHost();
+ mPort = source.getPort();
+ mPacFileUrl = source.mPacFileUrl;
+ mExclusionList = source.getExclusionListAsString();
+ mParsedExclusionList = source.mParsedExclusionList;
+ } else {
+ mHost = null;
+ mPort = 0;
+ mExclusionList = null;
+ mParsedExclusionList = null;
+ mPacFileUrl = Uri.EMPTY;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public InetSocketAddress getSocketAddress() {
+ InetSocketAddress inetSocketAddress = null;
+ try {
+ inetSocketAddress = new InetSocketAddress(mHost, mPort);
+ } catch (IllegalArgumentException e) { }
+ return inetSocketAddress;
+ }
+
+ /**
+ * Returns the URL of the current PAC script or null if there is
+ * no PAC script.
+ */
+ public Uri getPacFileUrl() {
+ return mPacFileUrl;
+ }
+
+ /**
+ * When configured to use a Direct Proxy this returns the host
+ * of the proxy.
+ */
+ public String getHost() {
+ return mHost;
+ }
+
+ /**
+ * When configured to use a Direct Proxy this returns the port
+ * of the proxy
+ */
+ public int getPort() {
+ return mPort;
+ }
+
+ /**
+ * When configured to use a Direct Proxy this returns the list
+ * of hosts for which the proxy is ignored.
+ */
+ public String[] getExclusionList() {
+ return mParsedExclusionList;
+ }
+
+ /**
+ * comma separated
+ * @hide
+ */
+ public String getExclusionListAsString() {
+ return mExclusionList;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isValid() {
+ if (!Uri.EMPTY.equals(mPacFileUrl)) return true;
+ return Proxy.PROXY_VALID == Proxy.validate(mHost == null ? "" : mHost,
+ mPort == 0 ? "" : Integer.toString(mPort),
+ mExclusionList == null ? "" : mExclusionList);
+ }
+
+ /**
+ * @hide
+ */
+ public java.net.Proxy makeProxy() {
+ java.net.Proxy proxy = java.net.Proxy.NO_PROXY;
+ if (mHost != null) {
+ try {
+ InetSocketAddress inetSocketAddress = new InetSocketAddress(mHost, mPort);
+ proxy = new java.net.Proxy(java.net.Proxy.Type.HTTP, inetSocketAddress);
+ } catch (IllegalArgumentException e) {
+ }
+ }
+ return proxy;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ if (!Uri.EMPTY.equals(mPacFileUrl)) {
+ sb.append("PAC Script: ");
+ sb.append(mPacFileUrl);
+ }
+ if (mHost != null) {
+ sb.append("[");
+ sb.append(mHost);
+ sb.append("] ");
+ sb.append(Integer.toString(mPort));
+ if (mExclusionList != null) {
+ sb.append(" xl=").append(mExclusionList);
+ }
+ } else {
+ sb.append("[ProxyProperties.mHost == null]");
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ProxyInfo)) return false;
+ ProxyInfo p = (ProxyInfo)o;
+ // If PAC URL is present in either then they must be equal.
+ // Other parameters will only be for fall back.
+ if (!Uri.EMPTY.equals(mPacFileUrl)) {
+ return mPacFileUrl.equals(p.getPacFileUrl()) && mPort == p.mPort;
+ }
+ if (!Uri.EMPTY.equals(p.mPacFileUrl)) {
+ return false;
+ }
+ if (mExclusionList != null && !mExclusionList.equals(p.getExclusionListAsString())) {
+ return false;
+ }
+ if (mHost != null && p.getHost() != null && mHost.equals(p.getHost()) == false) {
+ return false;
+ }
+ if (mHost != null && p.mHost == null) return false;
+ if (mHost == null && p.mHost != null) return false;
+ if (mPort != p.mPort) return false;
+ return true;
+ }
+
+ /**
+ * Implement the Parcelable interface
+ * @hide
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ /*
+ * generate hashcode based on significant fields
+ */
+ public int hashCode() {
+ return ((null == mHost) ? 0 : mHost.hashCode())
+ + ((null == mExclusionList) ? 0 : mExclusionList.hashCode())
+ + mPort;
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ * @hide
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ if (!Uri.EMPTY.equals(mPacFileUrl)) {
+ dest.writeByte((byte)1);
+ mPacFileUrl.writeToParcel(dest, 0);
+ dest.writeInt(mPort);
+ return;
+ } else {
+ dest.writeByte((byte)0);
+ }
+ if (mHost != null) {
+ dest.writeByte((byte)1);
+ dest.writeString(mHost);
+ dest.writeInt(mPort);
+ } else {
+ dest.writeByte((byte)0);
+ }
+ dest.writeString(mExclusionList);
+ dest.writeStringArray(mParsedExclusionList);
+ }
+
+ public static final @android.annotation.NonNull Creator<ProxyInfo> CREATOR =
+ new Creator<ProxyInfo>() {
+ public ProxyInfo createFromParcel(Parcel in) {
+ String host = null;
+ int port = 0;
+ if (in.readByte() != 0) {
+ Uri url = Uri.CREATOR.createFromParcel(in);
+ int localPort = in.readInt();
+ return new ProxyInfo(url, localPort);
+ }
+ if (in.readByte() != 0) {
+ host = in.readString();
+ port = in.readInt();
+ }
+ String exclList = in.readString();
+ String[] parsedExclList = in.readStringArray();
+ ProxyInfo proxyProperties = new ProxyInfo(host, port, exclList, parsedExclList);
+ return proxyProperties;
+ }
+
+ public ProxyInfo[] newArray(int size) {
+ return new ProxyInfo[size];
+ }
+ };
+}
diff --git a/android/net/RouteInfo.java b/android/net/RouteInfo.java
new file mode 100644
index 0000000..52d3fc4
--- /dev/null
+++ b/android/net/RouteInfo.java
@@ -0,0 +1,551 @@
+/*
+ * Copyright (C) 2011 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;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Collection;
+import java.util.Objects;
+
+/**
+ * Represents a network route.
+ * <p>
+ * This is used both to describe static network configuration and live network
+ * configuration information.
+ *
+ * A route contains three pieces of information:
+ * <ul>
+ * <li>a destination {@link IpPrefix} specifying the network destinations covered by this route.
+ * If this is {@code null} it indicates a default route of the address family (IPv4 or IPv6)
+ * implied by the gateway IP address.
+ * <li>a gateway {@link InetAddress} indicating the next hop to use. If this is {@code null} it
+ * indicates a directly-connected route.
+ * <li>an interface (which may be unspecified).
+ * </ul>
+ * Either the destination or the gateway may be {@code null}, but not both. If the
+ * destination and gateway are both specified, they must be of the same address family
+ * (IPv4 or IPv6).
+ */
+public final class RouteInfo implements Parcelable {
+ /** @hide */
+ @IntDef(value = {
+ RTN_UNICAST,
+ RTN_UNREACHABLE,
+ RTN_THROW,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RouteType {}
+
+ /**
+ * The IP destination address for this route.
+ */
+ @NonNull
+ private final IpPrefix mDestination;
+
+ /**
+ * The gateway address for this route.
+ */
+ @UnsupportedAppUsage
+ @Nullable
+ private final InetAddress mGateway;
+
+ /**
+ * The interface for this route.
+ */
+ @Nullable
+ private final String mInterface;
+
+
+ /** Unicast route. @hide */
+ @SystemApi
+ @TestApi
+ public static final int RTN_UNICAST = 1;
+
+ /** Unreachable route. @hide */
+ @SystemApi
+ @TestApi
+ public static final int RTN_UNREACHABLE = 7;
+
+ /** Throw route. @hide */
+ @SystemApi
+ @TestApi
+ public static final int RTN_THROW = 9;
+
+ /**
+ * The type of this route; one of the RTN_xxx constants above.
+ */
+ private final int mType;
+
+ // Derived data members.
+ // TODO: remove these.
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private final boolean mIsHost;
+ private final boolean mHasGateway;
+
+ /**
+ * Constructs a RouteInfo object.
+ *
+ * If destination is null, then gateway must be specified and the
+ * constructed route is either the IPv4 default route <code>0.0.0.0</code>
+ * if the gateway is an instance of {@link Inet4Address}, or the IPv6 default
+ * route <code>::/0</code> if gateway is an instance of
+ * {@link Inet6Address}.
+ * <p>
+ * destination and gateway may not both be null.
+ *
+ * @param destination the destination prefix
+ * @param gateway the IP address to route packets through
+ * @param iface the interface name to send packets on
+ * @param type the type of this route
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway,
+ @Nullable String iface, @RouteType int type) {
+ switch (type) {
+ case RTN_UNICAST:
+ case RTN_UNREACHABLE:
+ case RTN_THROW:
+ // TODO: It would be nice to ensure that route types that don't have nexthops or
+ // interfaces, such as unreachable or throw, can't be created if an interface or
+ // a gateway is specified. This is a bit too complicated to do at the moment
+ // because:
+ //
+ // - LinkProperties sets the interface on routes added to it, and modifies the
+ // interfaces of all the routes when its interface name changes.
+ // - Even when the gateway is null, we store a non-null gateway here.
+ //
+ // For now, we just rely on the code that sets routes to do things properly.
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown route type " + type);
+ }
+
+ if (destination == null) {
+ if (gateway != null) {
+ if (gateway instanceof Inet4Address) {
+ destination = new IpPrefix(Inet4Address.ANY, 0);
+ } else {
+ destination = new IpPrefix(Inet6Address.ANY, 0);
+ }
+ } else {
+ // no destination, no gateway. invalid.
+ throw new IllegalArgumentException("Invalid arguments passed in: " + gateway + "," +
+ destination);
+ }
+ }
+ // TODO: set mGateway to null if there is no gateway. This is more correct, saves space, and
+ // matches the documented behaviour. Before we can do this we need to fix all callers (e.g.,
+ // ConnectivityService) to stop doing things like r.getGateway().equals(), ... .
+ if (gateway == null) {
+ if (destination.getAddress() instanceof Inet4Address) {
+ gateway = Inet4Address.ANY;
+ } else {
+ gateway = Inet6Address.ANY;
+ }
+ }
+ mHasGateway = (!gateway.isAnyLocalAddress());
+
+ if ((destination.getAddress() instanceof Inet4Address &&
+ (gateway instanceof Inet4Address == false)) ||
+ (destination.getAddress() instanceof Inet6Address &&
+ (gateway instanceof Inet6Address == false))) {
+ throw new IllegalArgumentException("address family mismatch in RouteInfo constructor");
+ }
+ mDestination = destination; // IpPrefix objects are immutable.
+ mGateway = gateway; // InetAddress objects are immutable.
+ mInterface = iface; // Strings are immutable.
+ mType = type;
+ mIsHost = isHost();
+ }
+
+ /**
+ * Constructs a {@code RouteInfo} object.
+ *
+ * If destination is null, then gateway must be specified and the
+ * constructed route is either the IPv4 default route <code>0.0.0.0</code>
+ * if the gateway is an instance of {@link Inet4Address}, or the IPv6 default
+ * route <code>::/0</code> if gateway is an instance of {@link Inet6Address}.
+ * <p>
+ * Destination and gateway may not both be null.
+ *
+ * @param destination the destination address and prefix in an {@link IpPrefix}
+ * @param gateway the {@link InetAddress} to route packets through
+ * @param iface the interface name to send packets on
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway,
+ @Nullable String iface) {
+ this(destination, gateway, iface, RTN_UNICAST);
+ }
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public RouteInfo(@Nullable LinkAddress destination, @Nullable InetAddress gateway,
+ @Nullable String iface) {
+ this(destination == null ? null :
+ new IpPrefix(destination.getAddress(), destination.getPrefixLength()),
+ gateway, iface);
+ }
+
+ /**
+ * Constructs a {@code RouteInfo} object.
+ *
+ * If destination is null, then gateway must be specified and the
+ * constructed route is either the IPv4 default route <code>0.0.0.0</code>
+ * if the gateway is an instance of {@link Inet4Address}, or the IPv6 default
+ * route <code>::/0</code> if gateway is an instance of {@link Inet6Address}.
+ * <p>
+ * Destination and gateway may not both be null.
+ *
+ * @param destination the destination address and prefix in an {@link IpPrefix}
+ * @param gateway the {@link InetAddress} to route packets through
+ *
+ * @hide
+ */
+ public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway) {
+ this(destination, gateway, null);
+ }
+
+ /**
+ * @hide
+ *
+ * TODO: Remove this.
+ */
+ @UnsupportedAppUsage
+ public RouteInfo(@Nullable LinkAddress destination, @Nullable InetAddress gateway) {
+ this(destination, gateway, null);
+ }
+
+ /**
+ * Constructs a default {@code RouteInfo} object.
+ *
+ * @param gateway the {@link InetAddress} to route packets through
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public RouteInfo(@NonNull InetAddress gateway) {
+ this((IpPrefix) null, gateway, null);
+ }
+
+ /**
+ * Constructs a {@code RouteInfo} object representing a direct connected subnet.
+ *
+ * @param destination the {@link IpPrefix} describing the address and prefix
+ * length of the subnet.
+ *
+ * @hide
+ */
+ public RouteInfo(@NonNull IpPrefix destination) {
+ this(destination, null, null);
+ }
+
+ /**
+ * @hide
+ */
+ public RouteInfo(@NonNull LinkAddress destination) {
+ this(destination, null, null);
+ }
+
+ /**
+ * @hide
+ */
+ public RouteInfo(@NonNull IpPrefix destination, @RouteType int type) {
+ this(destination, null, null, type);
+ }
+
+ /**
+ * @hide
+ */
+ public static RouteInfo makeHostRoute(@NonNull InetAddress host, @Nullable String iface) {
+ return makeHostRoute(host, null, iface);
+ }
+
+ /**
+ * @hide
+ */
+ public static RouteInfo makeHostRoute(@Nullable InetAddress host, @Nullable InetAddress gateway,
+ @Nullable String iface) {
+ if (host == null) return null;
+
+ if (host instanceof Inet4Address) {
+ return new RouteInfo(new IpPrefix(host, 32), gateway, iface);
+ } else {
+ return new RouteInfo(new IpPrefix(host, 128), gateway, iface);
+ }
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private boolean isHost() {
+ return (mDestination.getAddress() instanceof Inet4Address &&
+ mDestination.getPrefixLength() == 32) ||
+ (mDestination.getAddress() instanceof Inet6Address &&
+ mDestination.getPrefixLength() == 128);
+ }
+
+ /**
+ * Retrieves the destination address and prefix length in the form of an {@link IpPrefix}.
+ *
+ * @return {@link IpPrefix} specifying the destination. This is never {@code null}.
+ */
+ @NonNull
+ public IpPrefix getDestination() {
+ return mDestination;
+ }
+
+ /**
+ * TODO: Convert callers to use IpPrefix and then remove.
+ * @hide
+ */
+ @NonNull
+ public LinkAddress getDestinationLinkAddress() {
+ return new LinkAddress(mDestination.getAddress(), mDestination.getPrefixLength());
+ }
+
+ /**
+ * Retrieves the gateway or next hop {@link InetAddress} for this route.
+ *
+ * @return {@link InetAddress} specifying the gateway or next hop. This may be
+ * {@code null} for a directly-connected route."
+ */
+ @Nullable
+ public InetAddress getGateway() {
+ return mGateway;
+ }
+
+ /**
+ * Retrieves the interface used for this route if specified, else {@code null}.
+ *
+ * @return The name of the interface used for this route.
+ */
+ @Nullable
+ public String getInterface() {
+ return mInterface;
+ }
+
+ /**
+ * Retrieves the type of this route.
+ *
+ * @return The type of this route; one of the {@code RTN_xxx} constants defined in this class.
+ *
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ @RouteType
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Indicates if this route is a default route (ie, has no destination specified).
+ *
+ * @return {@code true} if the destination has a prefix length of 0.
+ */
+ public boolean isDefaultRoute() {
+ return mType == RTN_UNICAST && mDestination.getPrefixLength() == 0;
+ }
+
+ /**
+ * Indicates if this route is an IPv4 default route.
+ * @hide
+ */
+ public boolean isIPv4Default() {
+ return isDefaultRoute() && mDestination.getAddress() instanceof Inet4Address;
+ }
+
+ /**
+ * Indicates if this route is an IPv6 default route.
+ * @hide
+ */
+ public boolean isIPv6Default() {
+ return isDefaultRoute() && mDestination.getAddress() instanceof Inet6Address;
+ }
+
+ /**
+ * Indicates if this route is a host route (ie, matches only a single host address).
+ *
+ * @return {@code true} if the destination has a prefix length of 32 or 128 for IPv4 or IPv6,
+ * respectively.
+ * @hide
+ */
+ public boolean isHostRoute() {
+ return mIsHost;
+ }
+
+ /**
+ * Indicates if this route has a next hop ({@code true}) or is directly-connected
+ * ({@code false}).
+ *
+ * @return {@code true} if a gateway is specified
+ */
+ public boolean hasGateway() {
+ return mHasGateway;
+ }
+
+ /**
+ * Determines whether the destination and prefix of this route includes the specified
+ * address.
+ *
+ * @param destination A {@link InetAddress} to test to see if it would match this route.
+ * @return {@code true} if the destination and prefix length cover the given address.
+ */
+ public boolean matches(InetAddress destination) {
+ return mDestination.contains(destination);
+ }
+
+ /**
+ * Find the route from a Collection of routes that best matches a given address.
+ * May return null if no routes are applicable.
+ * @param routes a Collection of RouteInfos to chose from
+ * @param dest the InetAddress your trying to get to
+ * @return the RouteInfo from the Collection that best fits the given address
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @Nullable
+ public static RouteInfo selectBestRoute(Collection<RouteInfo> routes, InetAddress dest) {
+ if ((routes == null) || (dest == null)) return null;
+
+ RouteInfo bestRoute = null;
+ // pick a longest prefix match under same address type
+ for (RouteInfo route : routes) {
+ if (NetworkUtils.addressTypeMatches(route.mDestination.getAddress(), dest)) {
+ if ((bestRoute != null) &&
+ (bestRoute.mDestination.getPrefixLength() >=
+ route.mDestination.getPrefixLength())) {
+ continue;
+ }
+ if (route.matches(dest)) bestRoute = route;
+ }
+ }
+ return bestRoute;
+ }
+
+ /**
+ * Returns a human-readable description of this object.
+ */
+ public String toString() {
+ String val = "";
+ if (mDestination != null) val = mDestination.toString();
+ if (mType == RTN_UNREACHABLE) {
+ val += " unreachable";
+ } else if (mType == RTN_THROW) {
+ val += " throw";
+ } else {
+ val += " ->";
+ if (mGateway != null) val += " " + mGateway.getHostAddress();
+ if (mInterface != null) val += " " + mInterface;
+ if (mType != RTN_UNICAST) {
+ val += " unknown type " + mType;
+ }
+ }
+ return val;
+ }
+
+ /**
+ * Compares this RouteInfo object against the specified object and indicates if they are equal.
+ * @return {@code true} if the objects are equal, {@code false} otherwise.
+ */
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+
+ if (!(obj instanceof RouteInfo)) return false;
+
+ RouteInfo target = (RouteInfo) obj;
+
+ return Objects.equals(mDestination, target.getDestination()) &&
+ Objects.equals(mGateway, target.getGateway()) &&
+ Objects.equals(mInterface, target.getInterface()) &&
+ mType == target.getType();
+ }
+
+ /**
+ * Returns a hashcode for this <code>RouteInfo</code> object.
+ */
+ public int hashCode() {
+ return (mDestination.hashCode() * 41)
+ + (mGateway == null ? 0 :mGateway.hashCode() * 47)
+ + (mInterface == null ? 0 :mInterface.hashCode() * 67)
+ + (mType * 71);
+ }
+
+ /**
+ * Implement the Parcelable interface
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Implement the Parcelable interface
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mDestination, flags);
+ byte[] gatewayBytes = (mGateway == null) ? null : mGateway.getAddress();
+ dest.writeByteArray(gatewayBytes);
+ dest.writeString(mInterface);
+ dest.writeInt(mType);
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ */
+ public static final @android.annotation.NonNull Creator<RouteInfo> CREATOR =
+ new Creator<RouteInfo>() {
+ public RouteInfo createFromParcel(Parcel in) {
+ IpPrefix dest = in.readParcelable(null);
+
+ InetAddress gateway = null;
+ byte[] addr = in.createByteArray();
+ try {
+ gateway = InetAddress.getByAddress(addr);
+ } catch (UnknownHostException e) {}
+
+ String iface = in.readString();
+ int type = in.readInt();
+
+ return new RouteInfo(dest, gateway, iface, type);
+ }
+
+ public RouteInfo[] newArray(int size) {
+ return new RouteInfo[size];
+ }
+ };
+}
diff --git a/android/net/RssiCurve.java b/android/net/RssiCurve.java
new file mode 100644
index 0000000..a173b0c
--- /dev/null
+++ b/android/net/RssiCurve.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2014 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;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * A curve defining the network score over a range of RSSI values.
+ *
+ * <p>For each RSSI bucket, the score may be any byte. Scores have no absolute meaning and are only
+ * considered relative to other scores assigned by the same scorer. Networks with no score are
+ * treated equivalently to a network with score {@link Byte#MIN_VALUE}, and will not be used.
+ *
+ * <p>For example, consider a curve starting at -110 dBm with a bucket width of 10 and the
+ * following buckets: {@code [-20, -10, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]}.
+ * This represents a linear curve between -110 dBm and 30 dBm. It scores progressively higher at
+ * stronger signal strengths.
+ *
+ * <p>A network can be assigned a fixed score independent of RSSI by setting
+ * {@link #rssiBuckets} to a one-byte array whose element is the fixed score. {@link #start}
+ * should be set to the lowest RSSI value at which this fixed score should apply, and
+ * {@link #bucketWidth} should be set such that {@code start + bucketWidth} is equal to the
+ * highest RSSI value at which this fixed score should apply.
+ *
+ * <p>Note that RSSI values below -110 dBm or above 30 dBm are unlikely to cause any difference
+ * in connectivity behavior from those endpoints. That is, the connectivity framework will treat
+ * a network with a -120 dBm signal exactly as it would treat one with a -110 dBm signal.
+ * Therefore, graphs which specify scores outside this range may be truncated to this range by
+ * the system.
+ *
+ * @see ScoredNetwork
+ * @hide
+ */
+@SystemApi
+public class RssiCurve implements Parcelable {
+ private static final int DEFAULT_ACTIVE_NETWORK_RSSI_BOOST = 25;
+
+ /** The starting dBm of the curve. */
+ public final int start;
+
+ /** The width of each RSSI bucket, in dBm. */
+ public final int bucketWidth;
+
+ /** The score for each RSSI bucket. */
+ public final byte[] rssiBuckets;
+
+ /**
+ * The RSSI boost to give this network when active, in dBm.
+ *
+ * <p>When the system is connected to this network, it will pretend that the network has this
+ * much higher of an RSSI. This is to avoid switching networks when another network has only a
+ * slightly higher score.
+ */
+ public final int activeNetworkRssiBoost;
+
+ /**
+ * Construct a new {@link RssiCurve}.
+ *
+ * @param start the starting dBm of the curve.
+ * @param bucketWidth the width of each RSSI bucket, in dBm.
+ * @param rssiBuckets the score for each RSSI bucket.
+ */
+ public RssiCurve(int start, int bucketWidth, byte[] rssiBuckets) {
+ this(start, bucketWidth, rssiBuckets, DEFAULT_ACTIVE_NETWORK_RSSI_BOOST);
+ }
+
+ /**
+ * Construct a new {@link RssiCurve}.
+ *
+ * @param start the starting dBm of the curve.
+ * @param bucketWidth the width of each RSSI bucket, in dBm.
+ * @param rssiBuckets the score for each RSSI bucket.
+ * @param activeNetworkRssiBoost the RSSI boost to apply when this network is active, in dBm.
+ */
+ public RssiCurve(int start, int bucketWidth, byte[] rssiBuckets, int activeNetworkRssiBoost) {
+ this.start = start;
+ this.bucketWidth = bucketWidth;
+ if (rssiBuckets == null || rssiBuckets.length == 0) {
+ throw new IllegalArgumentException("rssiBuckets must be at least one element large.");
+ }
+ this.rssiBuckets = rssiBuckets;
+ this.activeNetworkRssiBoost = activeNetworkRssiBoost;
+ }
+
+ private RssiCurve(Parcel in) {
+ start = in.readInt();
+ bucketWidth = in.readInt();
+ int bucketCount = in.readInt();
+ rssiBuckets = new byte[bucketCount];
+ in.readByteArray(rssiBuckets);
+ activeNetworkRssiBoost = in.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(start);
+ out.writeInt(bucketWidth);
+ out.writeInt(rssiBuckets.length);
+ out.writeByteArray(rssiBuckets);
+ out.writeInt(activeNetworkRssiBoost);
+ }
+
+ /**
+ * Lookup the score for a given RSSI value.
+ *
+ * @param rssi The RSSI to lookup. If the RSSI falls below the start of the curve, the score at
+ * the start of the curve will be returned. If it falls after the end of the curve, the
+ * score at the end of the curve will be returned.
+ * @return the score for the given RSSI.
+ */
+ public byte lookupScore(int rssi) {
+ return lookupScore(rssi, false /* isActiveNetwork */);
+ }
+
+ /**
+ * Lookup the score for a given RSSI value.
+ *
+ * @param rssi The RSSI to lookup. If the RSSI falls below the start of the curve, the score at
+ * the start of the curve will be returned. If it falls after the end of the curve, the
+ * score at the end of the curve will be returned.
+ * @param isActiveNetwork Whether this network is currently active.
+ * @return the score for the given RSSI.
+ */
+ public byte lookupScore(int rssi, boolean isActiveNetwork) {
+ if (isActiveNetwork) {
+ rssi += activeNetworkRssiBoost;
+ }
+
+ int index = (rssi - start) / bucketWidth;
+
+ // Snap the index to the closest bucket if it falls outside the curve.
+ if (index < 0) {
+ index = 0;
+ } else if (index > rssiBuckets.length - 1) {
+ index = rssiBuckets.length - 1;
+ }
+
+ return rssiBuckets[index];
+ }
+
+ /**
+ * Determine if two RSSI curves are defined in the same way.
+ *
+ * <p>Note that two curves can be equivalent but defined differently, e.g. if one bucket in one
+ * curve is split into two buckets in another. For the purpose of this method, these curves are
+ * not considered equal to each other.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ RssiCurve rssiCurve = (RssiCurve) o;
+
+ return start == rssiCurve.start &&
+ bucketWidth == rssiCurve.bucketWidth &&
+ Arrays.equals(rssiBuckets, rssiCurve.rssiBuckets) &&
+ activeNetworkRssiBoost == rssiCurve.activeNetworkRssiBoost;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(start, bucketWidth, activeNetworkRssiBoost) ^ Arrays.hashCode(rssiBuckets);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("RssiCurve[start=")
+ .append(start)
+ .append(",bucketWidth=")
+ .append(bucketWidth)
+ .append(",activeNetworkRssiBoost=")
+ .append(activeNetworkRssiBoost);
+
+ sb.append(",buckets=");
+ for (int i = 0; i < rssiBuckets.length; i++) {
+ sb.append(rssiBuckets[i]);
+ if (i < rssiBuckets.length - 1) {
+ sb.append(",");
+ }
+ }
+ sb.append("]");
+
+ return sb.toString();
+ }
+
+ public static final @android.annotation.NonNull Creator<RssiCurve> CREATOR =
+ new Creator<RssiCurve>() {
+ @Override
+ public RssiCurve createFromParcel(Parcel in) {
+ return new RssiCurve(in);
+ }
+
+ @Override
+ public RssiCurve[] newArray(int size) {
+ return new RssiCurve[size];
+ }
+ };
+}
diff --git a/android/net/SSLCertificateSocketFactory.java b/android/net/SSLCertificateSocketFactory.java
new file mode 100644
index 0000000..95d66bb
--- /dev/null
+++ b/android/net/SSLCertificateSocketFactory.java
@@ -0,0 +1,617 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.RoSystemProperties;
+import com.android.org.conscrypt.ClientSessionContext;
+import com.android.org.conscrypt.OpenSSLSocketImpl;
+import com.android.org.conscrypt.SSLClientSessionCache;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+import javax.net.SocketFactory;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * SSLSocketFactory implementation with several extra features:
+ *
+ * <ul>
+ * <li>Timeout specification for SSL handshake operations
+ * <li>Hostname verification in most cases (see WARNINGs below)
+ * <li>Optional SSL session caching with {@link SSLSessionCache}
+ * <li>Optionally bypass all SSL certificate checks
+ * </ul>
+ *
+ * The handshake timeout does not apply to actual TCP socket connection.
+ * If you want a connection timeout as well, use {@link #createSocket()}
+ * and {@link Socket#connect(java.net.SocketAddress, int)}, after which you
+ * must verify the identity of the server you are connected to.
+ *
+ * <p class="caution"><b>Most {@link SSLSocketFactory} implementations do not
+ * verify the server's identity, allowing man-in-the-middle attacks.</b>
+ * This implementation does check the server's certificate hostname, but only
+ * for createSocket variants that specify a hostname. When using methods that
+ * use {@link InetAddress} or which return an unconnected socket, you MUST
+ * verify the server's identity yourself to ensure a secure connection.
+ *
+ * Refer to
+ * <a href="https://developer.android.com/training/articles/security-gms-provider.html">
+ * Updating Your Security Provider to Protect Against SSL Exploits</a>
+ * for further information.</p>
+ *
+ * <p>The recommended way to verify the server's identity is to use
+ * {@link HttpsURLConnection#getDefaultHostnameVerifier()} to get a
+ * {@link HostnameVerifier} to verify the certificate hostname.
+ *
+ * <p><b>Warning</b>: Some methods on this class return connected sockets and some return
+ * unconnected sockets. For the methods that return connected sockets, setting
+ * connection- or handshake-related properties on those sockets will have no effect.
+ *
+ * <p>On development devices, "setprop socket.relaxsslcheck yes" bypasses all
+ * SSL certificate and hostname checks for testing purposes. This setting
+ * requires root access.
+ *
+ * @deprecated This class has less error-prone replacements using standard APIs. To create an
+ * {@code SSLSocket}, obtain an {@link SSLSocketFactory} from {@link SSLSocketFactory#getDefault()}
+ * or {@link javax.net.ssl.SSLContext#getSocketFactory()}. To verify hostnames, pass
+ * {@code "HTTPS"} to
+ * {@link javax.net.ssl.SSLParameters#setEndpointIdentificationAlgorithm(String)}. To enable ALPN,
+ * use {@link javax.net.ssl.SSLParameters#setApplicationProtocols(String[])}. To enable SNI,
+ * use {@link javax.net.ssl.SSLParameters#setServerNames(java.util.List)}.
+ */
+@Deprecated
+public class SSLCertificateSocketFactory extends SSLSocketFactory {
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private static final String TAG = "SSLCertificateSocketFactory";
+
+ @UnsupportedAppUsage
+ private static final TrustManager[] INSECURE_TRUST_MANAGER = new TrustManager[] {
+ new X509TrustManager() {
+ public X509Certificate[] getAcceptedIssuers() { return null; }
+ public void checkClientTrusted(X509Certificate[] certs, String authType) { }
+ public void checkServerTrusted(X509Certificate[] certs, String authType) { }
+ }
+ };
+
+ @UnsupportedAppUsage
+ private SSLSocketFactory mInsecureFactory = null;
+ @UnsupportedAppUsage
+ private SSLSocketFactory mSecureFactory = null;
+ @UnsupportedAppUsage
+ private TrustManager[] mTrustManagers = null;
+ @UnsupportedAppUsage
+ private KeyManager[] mKeyManagers = null;
+ @UnsupportedAppUsage
+ private byte[] mNpnProtocols = null;
+ @UnsupportedAppUsage
+ private byte[] mAlpnProtocols = null;
+ @UnsupportedAppUsage
+ private PrivateKey mChannelIdPrivateKey = null;
+
+ @UnsupportedAppUsage
+ private final int mHandshakeTimeoutMillis;
+ @UnsupportedAppUsage
+ private final SSLClientSessionCache mSessionCache;
+ @UnsupportedAppUsage
+ private final boolean mSecure;
+
+ /** @deprecated Use {@link #getDefault(int)} instead. */
+ @Deprecated
+ public SSLCertificateSocketFactory(int handshakeTimeoutMillis) {
+ this(handshakeTimeoutMillis, null, true);
+ }
+
+ @UnsupportedAppUsage
+ private SSLCertificateSocketFactory(
+ int handshakeTimeoutMillis, SSLSessionCache cache, boolean secure) {
+ mHandshakeTimeoutMillis = handshakeTimeoutMillis;
+ mSessionCache = cache == null ? null : cache.mSessionCache;
+ mSecure = secure;
+ }
+
+ /**
+ * Returns a new socket factory instance with an optional handshake timeout.
+ *
+ * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0
+ * for none. The socket timeout is reset to 0 after the handshake.
+ * @return a new SSLSocketFactory with the specified parameters
+ */
+ public static SocketFactory getDefault(int handshakeTimeoutMillis) {
+ return new SSLCertificateSocketFactory(handshakeTimeoutMillis, null, true);
+ }
+
+ /**
+ * Returns a new socket factory instance with an optional handshake timeout
+ * and SSL session cache.
+ *
+ * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0
+ * for none. The socket timeout is reset to 0 after the handshake.
+ * @param cache The {@link SSLSessionCache} to use, or null for no cache.
+ * @return a new SSLSocketFactory with the specified parameters
+ */
+ public static SSLSocketFactory getDefault(int handshakeTimeoutMillis, SSLSessionCache cache) {
+ return new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, true);
+ }
+
+ /**
+ * Returns a new instance of a socket factory with all SSL security checks
+ * disabled, using an optional handshake timeout and SSL session cache.
+ *
+ * <p class="caution"><b>Warning:</b> Sockets created using this factory
+ * are vulnerable to man-in-the-middle attacks!</p>
+ *
+ * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0
+ * for none. The socket timeout is reset to 0 after the handshake.
+ * @param cache The {@link SSLSessionCache} to use, or null for no cache.
+ * @return an insecure SSLSocketFactory with the specified parameters
+ */
+ public static SSLSocketFactory getInsecure(int handshakeTimeoutMillis, SSLSessionCache cache) {
+ return new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, false);
+ }
+
+ /**
+ * Returns a socket factory (also named SSLSocketFactory, but in a different
+ * namespace) for use with the Apache HTTP stack.
+ *
+ * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0
+ * for none. The socket timeout is reset to 0 after the handshake.
+ * @param cache The {@link SSLSessionCache} to use, or null for no cache.
+ * @return a new SocketFactory with the specified parameters
+ *
+ * @deprecated Use {@link #getDefault()} along with a {@link javax.net.ssl.HttpsURLConnection}
+ * instead. The Apache HTTP client is no longer maintained and may be removed in a future
+ * release. Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
+ * for further details.
+ *
+ * @removed
+ */
+ @Deprecated
+ public static org.apache.http.conn.ssl.SSLSocketFactory getHttpSocketFactory(
+ int handshakeTimeoutMillis, SSLSessionCache cache) {
+ return new org.apache.http.conn.ssl.SSLSocketFactory(
+ new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, true));
+ }
+
+ /**
+ * Verify the hostname of the certificate used by the other end of a connected socket using the
+ * {@link HostnameVerifier} obtained from {@code
+ * HttpsURLConnection.getDefaultHostnameVerifier()}. You MUST call this if you did not supply a
+ * hostname to {@link #createSocket()}. It is harmless to call this method redundantly if the
+ * hostname has already been verified.
+ *
+ * <p>Wildcard certificates are allowed to verify any matching hostname, so
+ * "foo.bar.example.com" is verified if the peer has a certificate for "*.example.com".
+ *
+ * @param socket An SSL socket which has been connected to a server
+ * @param hostname The expected hostname of the remote server
+ * @throws IOException if something goes wrong handshaking with the server
+ * @throws SSLPeerUnverifiedException if the server cannot prove its identity
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static void verifyHostname(Socket socket, String hostname) throws IOException {
+ if (!(socket instanceof SSLSocket)) {
+ throw new IllegalArgumentException("Attempt to verify non-SSL socket");
+ }
+
+ if (!isSslCheckRelaxed()) {
+ // The code at the start of OpenSSLSocketImpl.startHandshake()
+ // ensures that the call is idempotent, so we can safely call it.
+ SSLSocket ssl = (SSLSocket) socket;
+ ssl.startHandshake();
+
+ SSLSession session = ssl.getSession();
+ if (session == null) {
+ throw new SSLException("Cannot verify SSL socket without session");
+ }
+ if (!HttpsURLConnection.getDefaultHostnameVerifier().verify(hostname, session)) {
+ throw new SSLPeerUnverifiedException("Cannot verify hostname: " + hostname);
+ }
+ }
+ }
+
+ @UnsupportedAppUsage
+ private SSLSocketFactory makeSocketFactory(
+ KeyManager[] keyManagers, TrustManager[] trustManagers) {
+ try {
+ SSLContext sslContext = SSLContext.getInstance("TLS", "AndroidOpenSSL");
+ sslContext.init(keyManagers, trustManagers, null);
+ ((ClientSessionContext) sslContext.getClientSessionContext())
+ .setPersistentCache(mSessionCache);
+ return sslContext.getSocketFactory();
+ } catch (KeyManagementException | NoSuchAlgorithmException | NoSuchProviderException e) {
+ Log.wtf(TAG, e);
+ return (SSLSocketFactory) SSLSocketFactory.getDefault(); // Fallback
+ }
+ }
+
+ @UnsupportedAppUsage
+ private static boolean isSslCheckRelaxed() {
+ return RoSystemProperties.DEBUGGABLE &&
+ SystemProperties.getBoolean("socket.relaxsslcheck", false);
+ }
+
+ @UnsupportedAppUsage
+ private synchronized SSLSocketFactory getDelegate() {
+ // Relax the SSL check if instructed (for this factory, or systemwide)
+ if (!mSecure || isSslCheckRelaxed()) {
+ if (mInsecureFactory == null) {
+ if (mSecure) {
+ Log.w(TAG, "*** BYPASSING SSL SECURITY CHECKS (socket.relaxsslcheck=yes) ***");
+ } else {
+ Log.w(TAG, "Bypassing SSL security checks at caller's request");
+ }
+ mInsecureFactory = makeSocketFactory(mKeyManagers, INSECURE_TRUST_MANAGER);
+ }
+ return mInsecureFactory;
+ } else {
+ if (mSecureFactory == null) {
+ mSecureFactory = makeSocketFactory(mKeyManagers, mTrustManagers);
+ }
+ return mSecureFactory;
+ }
+ }
+
+ /**
+ * Sets the {@link TrustManager}s to be used for connections made by this factory.
+ */
+ public void setTrustManagers(TrustManager[] trustManager) {
+ mTrustManagers = trustManager;
+
+ // Clear out all cached secure factories since configurations have changed.
+ mSecureFactory = null;
+ // Note - insecure factories only ever use the INSECURE_TRUST_MANAGER so they need not
+ // be cleared out here.
+ }
+
+ /**
+ * Sets the <a href="http://technotes.googlecode.com/git/nextprotoneg.html">Next
+ * Protocol Negotiation (NPN)</a> protocols that this peer is interested in.
+ *
+ * <p>For servers this is the sequence of protocols to advertise as
+ * supported, in order of preference. This list is sent unencrypted to
+ * all clients that support NPN.
+ *
+ * <p>For clients this is a list of supported protocols to match against the
+ * server's list. If there is no protocol supported by both client and
+ * server then the first protocol in the client's list will be selected.
+ * The order of the client's protocols is otherwise insignificant.
+ *
+ * @param npnProtocols a non-empty list of protocol byte arrays. All arrays
+ * must be non-empty and of length less than 256.
+ */
+ public void setNpnProtocols(byte[][] npnProtocols) {
+ this.mNpnProtocols = toLengthPrefixedList(npnProtocols);
+ }
+
+ /**
+ * Sets the
+ * <a href="http://tools.ietf.org/html/draft-ietf-tls-applayerprotoneg-01">
+ * Application Layer Protocol Negotiation (ALPN)</a> protocols that this peer
+ * is interested in.
+ *
+ * <p>For servers this is the sequence of protocols to advertise as
+ * supported, in order of preference. This list is sent unencrypted to
+ * all clients that support ALPN.
+ *
+ * <p>For clients this is a list of supported protocols to match against the
+ * server's list. If there is no protocol supported by both client and
+ * server then the first protocol in the client's list will be selected.
+ * The order of the client's protocols is otherwise insignificant.
+ *
+ * @param protocols a non-empty list of protocol byte arrays. All arrays
+ * must be non-empty and of length less than 256.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setAlpnProtocols(byte[][] protocols) {
+ this.mAlpnProtocols = toLengthPrefixedList(protocols);
+ }
+
+ /**
+ * Returns an array containing the concatenation of length-prefixed byte
+ * strings.
+ * @hide
+ */
+ @VisibleForTesting
+ public static byte[] toLengthPrefixedList(byte[]... items) {
+ if (items.length == 0) {
+ throw new IllegalArgumentException("items.length == 0");
+ }
+ int totalLength = 0;
+ for (byte[] s : items) {
+ if (s.length == 0 || s.length > 255) {
+ throw new IllegalArgumentException("s.length == 0 || s.length > 255: " + s.length);
+ }
+ totalLength += 1 + s.length;
+ }
+ byte[] result = new byte[totalLength];
+ int pos = 0;
+ for (byte[] s : items) {
+ result[pos++] = (byte) s.length;
+ for (byte b : s) {
+ result[pos++] = b;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns the <a href="http://technotes.googlecode.com/git/nextprotoneg.html">Next
+ * Protocol Negotiation (NPN)</a> protocol selected by client and server, or
+ * null if no protocol was negotiated.
+ *
+ * @param socket a socket created by this factory.
+ * @throws IllegalArgumentException if the socket was not created by this factory.
+ */
+ public byte[] getNpnSelectedProtocol(Socket socket) {
+ return castToOpenSSLSocket(socket).getNpnSelectedProtocol();
+ }
+
+ /**
+ * Returns the
+ * <a href="http://tools.ietf.org/html/draft-ietf-tls-applayerprotoneg-01">Application
+ * Layer Protocol Negotiation (ALPN)</a> protocol selected by client and server, or null
+ * if no protocol was negotiated.
+ *
+ * @param socket a socket created by this factory.
+ * @throws IllegalArgumentException if the socket was not created by this factory.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public byte[] getAlpnSelectedProtocol(Socket socket) {
+ return castToOpenSSLSocket(socket).getAlpnSelectedProtocol();
+ }
+
+ /**
+ * Sets the {@link KeyManager}s to be used for connections made by this factory.
+ */
+ public void setKeyManagers(KeyManager[] keyManagers) {
+ mKeyManagers = keyManagers;
+
+ // Clear out any existing cached factories since configurations have changed.
+ mSecureFactory = null;
+ mInsecureFactory = null;
+ }
+
+ /**
+ * Sets the private key to be used for TLS Channel ID by connections made by this
+ * factory.
+ *
+ * @param privateKey private key (enables TLS Channel ID) or {@code null} for no key (disables
+ * TLS Channel ID). The private key has to be an Elliptic Curve (EC) key based on the
+ * NIST P-256 curve (aka SECG secp256r1 or ANSI X9.62 prime256v1).
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public void setChannelIdPrivateKey(PrivateKey privateKey) {
+ mChannelIdPrivateKey = privateKey;
+ }
+
+ /**
+ * Enables <a href="http://tools.ietf.org/html/rfc5077#section-3.2">session ticket</a>
+ * support on the given socket.
+ *
+ * @param socket a socket created by this factory
+ * @param useSessionTickets {@code true} to enable session ticket support on this socket.
+ * @throws IllegalArgumentException if the socket was not created by this factory.
+ */
+ public void setUseSessionTickets(Socket socket, boolean useSessionTickets) {
+ castToOpenSSLSocket(socket).setUseSessionTickets(useSessionTickets);
+ }
+
+ /**
+ * Turns on <a href="http://tools.ietf.org/html/rfc6066#section-3">Server
+ * Name Indication (SNI)</a> on a given socket.
+ *
+ * @param socket a socket created by this factory.
+ * @param hostName the desired SNI hostname, null to disable.
+ * @throws IllegalArgumentException if the socket was not created by this factory.
+ */
+ public void setHostname(Socket socket, String hostName) {
+ castToOpenSSLSocket(socket).setHostname(hostName);
+ }
+
+ /**
+ * Sets this socket's SO_SNDTIMEO write timeout in milliseconds.
+ * Use 0 for no timeout.
+ * To take effect, this option must be set before the blocking method was called.
+ *
+ * @param socket a socket created by this factory.
+ * @param timeout the desired write timeout in milliseconds.
+ * @throws IllegalArgumentException if the socket was not created by this factory.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setSoWriteTimeout(Socket socket, int writeTimeoutMilliseconds)
+ throws SocketException {
+ castToOpenSSLSocket(socket).setSoWriteTimeout(writeTimeoutMilliseconds);
+ }
+
+ @UnsupportedAppUsage
+ private static OpenSSLSocketImpl castToOpenSSLSocket(Socket socket) {
+ if (!(socket instanceof OpenSSLSocketImpl)) {
+ throw new IllegalArgumentException("Socket not created by this factory: "
+ + socket);
+ }
+
+ return (OpenSSLSocketImpl) socket;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>By default, this method returns a <i>connected</i> socket and verifies the peer's
+ * certificate hostname after connecting using the {@link HostnameVerifier} obtained from
+ * {@code HttpsURLConnection.getDefaultHostnameVerifier()}; if this instance was created with
+ * {@link #getInsecure(int, SSLSessionCache)}, it returns a socket that is <i>not connected</i>
+ * instead.
+ */
+ @Override
+ public Socket createSocket(Socket k, String host, int port, boolean close) throws IOException {
+ OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(k, host, port, close);
+ s.setNpnProtocols(mNpnProtocols);
+ s.setAlpnProtocols(mAlpnProtocols);
+ s.setHandshakeTimeout(mHandshakeTimeoutMillis);
+ s.setChannelIdPrivateKey(mChannelIdPrivateKey);
+ if (mSecure) {
+ verifyHostname(s, host);
+ }
+ return s;
+ }
+
+ /**
+ * Creates a new socket which is <i>not connected</i> to any remote host.
+ * You must use {@link Socket#connect} to connect the socket.
+ *
+ * <p class="caution"><b>Warning:</b> Hostname verification is not performed
+ * with this method. You MUST verify the server's identity after connecting
+ * the socket to avoid man-in-the-middle attacks.</p>
+ */
+ @Override
+ public Socket createSocket() throws IOException {
+ OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket();
+ s.setNpnProtocols(mNpnProtocols);
+ s.setAlpnProtocols(mAlpnProtocols);
+ s.setHandshakeTimeout(mHandshakeTimeoutMillis);
+ s.setChannelIdPrivateKey(mChannelIdPrivateKey);
+ return s;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This method returns a socket that is <i>not connected</i>.
+ *
+ * <p class="caution"><b>Warning:</b> Hostname verification is not performed
+ * with this method. You MUST verify the server's identity after connecting
+ * the socket to avoid man-in-the-middle attacks.</p>
+ */
+ @Override
+ public Socket createSocket(InetAddress addr, int port, InetAddress localAddr, int localPort)
+ throws IOException {
+ OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(
+ addr, port, localAddr, localPort);
+ s.setNpnProtocols(mNpnProtocols);
+ s.setAlpnProtocols(mAlpnProtocols);
+ s.setHandshakeTimeout(mHandshakeTimeoutMillis);
+ s.setChannelIdPrivateKey(mChannelIdPrivateKey);
+ return s;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This method returns a socket that is <i>not connected</i>.
+ *
+ * <p class="caution"><b>Warning:</b> Hostname verification is not performed
+ * with this method. You MUST verify the server's identity after connecting
+ * the socket to avoid man-in-the-middle attacks.</p>
+ */
+ @Override
+ public Socket createSocket(InetAddress addr, int port) throws IOException {
+ OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(addr, port);
+ s.setNpnProtocols(mNpnProtocols);
+ s.setAlpnProtocols(mAlpnProtocols);
+ s.setHandshakeTimeout(mHandshakeTimeoutMillis);
+ s.setChannelIdPrivateKey(mChannelIdPrivateKey);
+ return s;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>By default, this method returns a <i>connected</i> socket and verifies the peer's
+ * certificate hostname after connecting using the {@link HostnameVerifier} obtained from
+ * {@code HttpsURLConnection.getDefaultHostnameVerifier()}; if this instance was created with
+ * {@link #getInsecure(int, SSLSessionCache)}, it returns a socket that is <i>not connected</i>
+ * instead.
+ */
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localAddr, int localPort)
+ throws IOException {
+ OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(
+ host, port, localAddr, localPort);
+ s.setNpnProtocols(mNpnProtocols);
+ s.setAlpnProtocols(mAlpnProtocols);
+ s.setHandshakeTimeout(mHandshakeTimeoutMillis);
+ s.setChannelIdPrivateKey(mChannelIdPrivateKey);
+ if (mSecure) {
+ verifyHostname(s, host);
+ }
+ return s;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>By default, this method returns a <i>connected</i> socket and verifies the peer's
+ * certificate hostname after connecting using the {@link HostnameVerifier} obtained from
+ * {@code HttpsURLConnection.getDefaultHostnameVerifier()}; if this instance was created with
+ * {@link #getInsecure(int, SSLSessionCache)}, it returns a socket that is <i>not connected</i>
+ * instead.
+ */
+ @Override
+ public Socket createSocket(String host, int port) throws IOException {
+ OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(host, port);
+ s.setNpnProtocols(mNpnProtocols);
+ s.setAlpnProtocols(mAlpnProtocols);
+ s.setHandshakeTimeout(mHandshakeTimeoutMillis);
+ s.setChannelIdPrivateKey(mChannelIdPrivateKey);
+ if (mSecure) {
+ verifyHostname(s, host);
+ }
+ return s;
+ }
+
+ @Override
+ public String[] getDefaultCipherSuites() {
+ return getDelegate().getDefaultCipherSuites();
+ }
+
+ @Override
+ public String[] getSupportedCipherSuites() {
+ return getDelegate().getSupportedCipherSuites();
+ }
+}
diff --git a/android/net/SSLSessionCache.java b/android/net/SSLSessionCache.java
new file mode 100644
index 0000000..9667e82
--- /dev/null
+++ b/android/net/SSLSessionCache.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2010 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;
+
+import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.org.conscrypt.ClientSessionContext;
+import com.android.org.conscrypt.FileClientSessionCache;
+import com.android.org.conscrypt.SSLClientSessionCache;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSessionContext;
+
+/**
+ * File-based cache of established SSL sessions. When re-establishing a
+ * connection to the same server, using an SSL session cache can save some time,
+ * power, and bandwidth by skipping directly to an encrypted stream.
+ * This is a persistent cache which can span executions of the application.
+ *
+ * @see SSLCertificateSocketFactory
+ */
+public final class SSLSessionCache {
+ private static final String TAG = "SSLSessionCache";
+ @UnsupportedAppUsage
+ /* package */ final SSLClientSessionCache mSessionCache;
+
+ /**
+ * Installs a {@link SSLSessionCache} on a {@link SSLContext}. The cache will
+ * be used on all socket factories created by this context (including factories
+ * created before this call).
+ *
+ * @param cache the cache instance to install, or {@code null} to uninstall any
+ * existing cache.
+ * @param context the context to install it on.
+ * @throws IllegalArgumentException if the context does not support a session
+ * cache.
+ *
+ * @hide candidate for public API
+ */
+ public static void install(SSLSessionCache cache, SSLContext context) {
+ SSLSessionContext clientContext = context.getClientSessionContext();
+ if (clientContext instanceof ClientSessionContext) {
+ ((ClientSessionContext) clientContext).setPersistentCache(
+ cache == null ? null : cache.mSessionCache);
+ } else {
+ throw new IllegalArgumentException("Incompatible SSLContext: " + context);
+ }
+ }
+
+ /**
+ * NOTE: This needs to be Object (and not SSLClientSessionCache) because apps
+ * that build directly against the framework (and not the SDK) might not declare
+ * a dependency on conscrypt. Javac will then has fail while resolving constructors.
+ *
+ * @hide For unit test use only
+ */
+ public SSLSessionCache(Object cache) {
+ mSessionCache = (SSLClientSessionCache) cache;
+ }
+
+ /**
+ * Create a session cache using the specified directory.
+ * Individual session entries will be files within the directory.
+ * Multiple instances for the same directory share data internally.
+ *
+ * @param dir to store session files in (created if necessary)
+ * @throws IOException if the cache can't be opened
+ */
+ public SSLSessionCache(File dir) throws IOException {
+ mSessionCache = FileClientSessionCache.usingDirectory(dir);
+ }
+
+ /**
+ * Create a session cache at the default location for this app.
+ * Multiple instances share data internally.
+ *
+ * @param context for the application
+ */
+ public SSLSessionCache(Context context) {
+ File dir = context.getDir("sslcache", Context.MODE_PRIVATE);
+ SSLClientSessionCache cache = null;
+ try {
+ cache = FileClientSessionCache.usingDirectory(dir);
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to create SSL session cache in " + dir, e);
+ }
+ mSessionCache = cache;
+ }
+}
diff --git a/android/net/ScoredNetwork.java b/android/net/ScoredNetwork.java
new file mode 100644
index 0000000..effc1aa
--- /dev/null
+++ b/android/net/ScoredNetwork.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2014 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;
+
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A network identifier along with a score for the quality of that network.
+ *
+ * @hide
+ */
+@SystemApi
+public class ScoredNetwork implements Parcelable {
+
+ /**
+ * Key used with the {@link #attributes} bundle to define the badging curve.
+ *
+ * <p>The badging curve is a {@link RssiCurve} used to map different RSSI values to {@link
+ * NetworkBadging.Badging} enums.
+ */
+ public static final String ATTRIBUTES_KEY_BADGING_CURVE =
+ "android.net.attributes.key.BADGING_CURVE";
+ /**
+ * Extra used with {@link #attributes} to specify whether the
+ * network is believed to have a captive portal.
+ * <p>
+ * This data may be used, for example, to display a visual indicator
+ * in a network selection list.
+ * <p>
+ * Note that the this extra conveys the possible presence of a
+ * captive portal, not its state or the user's ability to open
+ * the portal.
+ * <p>
+ * If no value is associated with this key then it's unknown.
+ */
+ public static final String ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL =
+ "android.net.attributes.key.HAS_CAPTIVE_PORTAL";
+
+ /**
+ * Key used with the {@link #attributes} bundle to define the rankingScoreOffset int value.
+ *
+ * <p>The rankingScoreOffset is used when calculating the ranking score used to rank networks
+ * against one another. See {@link #calculateRankingScore} for more information.
+ */
+ public static final String ATTRIBUTES_KEY_RANKING_SCORE_OFFSET =
+ "android.net.attributes.key.RANKING_SCORE_OFFSET";
+
+ /** A {@link NetworkKey} uniquely identifying this network. */
+ public final NetworkKey networkKey;
+
+ /**
+ * The {@link RssiCurve} representing the scores for this network based on the RSSI.
+ *
+ * <p>This field is optional and may be set to null to indicate that no score is available for
+ * this network at this time. Such networks, along with networks for which the scorer has not
+ * responded, are always prioritized below scored networks, regardless of the score.
+ */
+ public final RssiCurve rssiCurve;
+
+ /**
+ * A boolean value that indicates whether or not the network is believed to be metered.
+ *
+ * <p>A network can be classified as metered if the user would be
+ * sensitive to heavy data usage on that connection due to monetary costs,
+ * data limitations or battery/performance issues. A typical example would
+ * be a wifi connection where the user would be charged for usage.
+ */
+ public final boolean meteredHint;
+
+ /**
+ * An additional collection of optional attributes set by
+ * the Network Recommendation Provider.
+ *
+ * @see #ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL
+ * @see #ATTRIBUTES_KEY_RANKING_SCORE_OFFSET
+ */
+ @Nullable
+ public final Bundle attributes;
+
+ /**
+ * Construct a new {@link ScoredNetwork}.
+ *
+ * @param networkKey the {@link NetworkKey} uniquely identifying this network.
+ * @param rssiCurve the {@link RssiCurve} representing the scores for this network based on the
+ * RSSI. This field is optional, and may be skipped to represent a network which the scorer
+ * has opted not to score at this time. Passing a null value here is strongly preferred to
+ * not returning any {@link ScoredNetwork} for a given {@link NetworkKey} because it
+ * indicates to the system not to request scores for this network in the future, although
+ * the scorer may choose to issue an out-of-band update at any time.
+ */
+ public ScoredNetwork(NetworkKey networkKey, RssiCurve rssiCurve) {
+ this(networkKey, rssiCurve, false /* meteredHint */);
+ }
+
+ /**
+ * Construct a new {@link ScoredNetwork}.
+ *
+ * @param networkKey the {@link NetworkKey} uniquely identifying this network.
+ * @param rssiCurve the {@link RssiCurve} representing the scores for this network based on the
+ * RSSI. This field is optional, and may be skipped to represent a network which the scorer
+ * has opted not to score at this time. Passing a null value here is strongly preferred to
+ * not returning any {@link ScoredNetwork} for a given {@link NetworkKey} because it
+ * indicates to the system not to request scores for this network in the future, although
+ * the scorer may choose to issue an out-of-band update at any time.
+ * @param meteredHint A boolean value indicating whether or not the network is believed to be
+ * metered.
+ */
+ public ScoredNetwork(NetworkKey networkKey, RssiCurve rssiCurve, boolean meteredHint) {
+ this(networkKey, rssiCurve, meteredHint, null /* attributes */);
+ }
+
+ /**
+ * Construct a new {@link ScoredNetwork}.
+ *
+ * @param networkKey the {@link NetworkKey} uniquely identifying this network
+ * @param rssiCurve the {@link RssiCurve} representing the scores for this network based on the
+ * RSSI. This field is optional, and may be skipped to represent a network which the scorer
+ * has opted not to score at this time. Passing a null value here is strongly preferred to
+ * not returning any {@link ScoredNetwork} for a given {@link NetworkKey} because it
+ * indicates to the system not to request scores for this network in the future, although
+ * the scorer may choose to issue an out-of-band update at any time.
+ * @param meteredHint a boolean value indicating whether or not the network is believed to be
+ * metered
+ * @param attributes optional provider specific attributes
+ */
+ public ScoredNetwork(NetworkKey networkKey, RssiCurve rssiCurve, boolean meteredHint,
+ @Nullable Bundle attributes) {
+ this.networkKey = networkKey;
+ this.rssiCurve = rssiCurve;
+ this.meteredHint = meteredHint;
+ this.attributes = attributes;
+ }
+
+ private ScoredNetwork(Parcel in) {
+ networkKey = NetworkKey.CREATOR.createFromParcel(in);
+ if (in.readByte() == 1) {
+ rssiCurve = RssiCurve.CREATOR.createFromParcel(in);
+ } else {
+ rssiCurve = null;
+ }
+ meteredHint = (in.readByte() == 1);
+ attributes = in.readBundle();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ networkKey.writeToParcel(out, flags);
+ if (rssiCurve != null) {
+ out.writeByte((byte) 1);
+ rssiCurve.writeToParcel(out, flags);
+ } else {
+ out.writeByte((byte) 0);
+ }
+ out.writeByte((byte) (meteredHint ? 1 : 0));
+ out.writeBundle(attributes);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ ScoredNetwork that = (ScoredNetwork) o;
+
+ return Objects.equals(networkKey, that.networkKey)
+ && Objects.equals(rssiCurve, that.rssiCurve)
+ && Objects.equals(meteredHint, that.meteredHint)
+ && bundleEquals(attributes, that.attributes);
+ }
+
+ private boolean bundleEquals(Bundle bundle1, Bundle bundle2) {
+ if (bundle1 == bundle2) {
+ return true;
+ }
+ if (bundle1 == null || bundle2 == null) {
+ return false;
+ }
+ if (bundle1.size() != bundle2.size()) {
+ return false;
+ }
+ Set<String> keys = bundle1.keySet();
+ for (String key : keys) {
+ Object value1 = bundle1.get(key);
+ Object value2 = bundle2.get(key);
+ if (!Objects.equals(value1, value2)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(networkKey, rssiCurve, meteredHint, attributes);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder out = new StringBuilder(
+ "ScoredNetwork{" +
+ "networkKey=" + networkKey +
+ ", rssiCurve=" + rssiCurve +
+ ", meteredHint=" + meteredHint);
+ // calling isEmpty will unparcel the bundle so its contents can be converted to a string
+ if (attributes != null && !attributes.isEmpty()) {
+ out.append(", attributes=" + attributes);
+ }
+ out.append('}');
+ return out.toString();
+ }
+
+ /**
+ * Returns true if a ranking score can be calculated for this network.
+ *
+ * @hide
+ */
+ public boolean hasRankingScore() {
+ return (rssiCurve != null)
+ || (attributes != null
+ && attributes.containsKey(ATTRIBUTES_KEY_RANKING_SCORE_OFFSET));
+ }
+
+ /**
+ * Returns a ranking score for a given RSSI which can be used to comparatively
+ * rank networks.
+ *
+ * <p>The score obtained by the rssiCurve is bitshifted left by 8 bits to expand it to an
+ * integer and then the offset is added. If the addition operation overflows or underflows,
+ * Integer.MAX_VALUE and Integer.MIN_VALUE will be returned respectively.
+ *
+ * <p>{@link #hasRankingScore} should be called first to ensure this network is capable
+ * of returning a ranking score.
+ *
+ * @throws UnsupportedOperationException if there is no RssiCurve and no rankingScoreOffset
+ * for this network (hasRankingScore returns false).
+ *
+ * @hide
+ */
+ public int calculateRankingScore(int rssi) throws UnsupportedOperationException {
+ if (!hasRankingScore()) {
+ throw new UnsupportedOperationException(
+ "Either rssiCurve or rankingScoreOffset is required to calculate the "
+ + "ranking score");
+ }
+
+ int offset = 0;
+ if (attributes != null) {
+ offset += attributes.getInt(ATTRIBUTES_KEY_RANKING_SCORE_OFFSET, 0 /* default */);
+ }
+
+ int score = (rssiCurve == null) ? 0 : rssiCurve.lookupScore(rssi) << Byte.SIZE;
+
+ try {
+ return Math.addExact(score, offset);
+ } catch (ArithmeticException e) {
+ return (score < 0) ? Integer.MIN_VALUE : Integer.MAX_VALUE;
+ }
+ }
+
+ /**
+ * Return the {@link NetworkBadging.Badging} enum for this network for the given RSSI, derived from the
+ * badging curve.
+ *
+ * <p>If no badging curve is present, {@link #BADGE_NONE} will be returned.
+ *
+ * @param rssi The rssi level for which the badge should be calculated
+ */
+ @NetworkBadging.Badging
+ public int calculateBadge(int rssi) {
+ if (attributes != null && attributes.containsKey(ATTRIBUTES_KEY_BADGING_CURVE)) {
+ RssiCurve badgingCurve =
+ attributes.getParcelable(ATTRIBUTES_KEY_BADGING_CURVE);
+ return badgingCurve.lookupScore(rssi);
+ }
+
+ return NetworkBadging.BADGING_NONE;
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<ScoredNetwork> CREATOR =
+ new Parcelable.Creator<ScoredNetwork>() {
+ @Override
+ public ScoredNetwork createFromParcel(Parcel in) {
+ return new ScoredNetwork(in);
+ }
+
+ @Override
+ public ScoredNetwork[] newArray(int size) {
+ return new ScoredNetwork[size];
+ }
+ };
+}
diff --git a/android/net/SntpClient.java b/android/net/SntpClient.java
new file mode 100644
index 0000000..a55d9d0
--- /dev/null
+++ b/android/net/SntpClient.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.internal.util.TrafficStatsConstants;
+
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.util.Arrays;
+
+/**
+ * {@hide}
+ *
+ * Simple SNTP client class for retrieving network time.
+ *
+ * Sample usage:
+ * <pre>SntpClient client = new SntpClient();
+ * if (client.requestTime("time.foo.com")) {
+ * long now = client.getNtpTime() + SystemClock.elapsedRealtime() - client.getNtpTimeReference();
+ * }
+ * </pre>
+ */
+public class SntpClient {
+ private static final String TAG = "SntpClient";
+ private static final boolean DBG = true;
+
+ private static final int REFERENCE_TIME_OFFSET = 16;
+ private static final int ORIGINATE_TIME_OFFSET = 24;
+ private static final int RECEIVE_TIME_OFFSET = 32;
+ private static final int TRANSMIT_TIME_OFFSET = 40;
+ private static final int NTP_PACKET_SIZE = 48;
+
+ private static final int NTP_PORT = 123;
+ private static final int NTP_MODE_CLIENT = 3;
+ private static final int NTP_MODE_SERVER = 4;
+ private static final int NTP_MODE_BROADCAST = 5;
+ private static final int NTP_VERSION = 3;
+
+ private static final int NTP_LEAP_NOSYNC = 3;
+ private static final int NTP_STRATUM_DEATH = 0;
+ private static final int NTP_STRATUM_MAX = 15;
+
+ // Number of seconds between Jan 1, 1900 and Jan 1, 1970
+ // 70 years plus 17 leap days
+ private static final long OFFSET_1900_TO_1970 = ((365L * 70L) + 17L) * 24L * 60L * 60L;
+
+ // system time computed from NTP server response
+ private long mNtpTime;
+
+ // value of SystemClock.elapsedRealtime() corresponding to mNtpTime
+ private long mNtpTimeReference;
+
+ // round trip time in milliseconds
+ private long mRoundTripTime;
+
+ private static class InvalidServerReplyException extends Exception {
+ public InvalidServerReplyException(String message) {
+ super(message);
+ }
+ }
+
+ /**
+ * Sends an SNTP request to the given host and processes the response.
+ *
+ * @param host host name of the server.
+ * @param timeout network timeout in milliseconds.
+ * @param network network over which to send the request.
+ * @return true if the transaction was successful.
+ */
+ public boolean requestTime(String host, int timeout, Network network) {
+ final Network networkForResolv = network.getPrivateDnsBypassingCopy();
+ InetAddress address = null;
+ try {
+ address = networkForResolv.getByName(host);
+ } catch (Exception e) {
+ EventLogTags.writeNtpFailure(host, e.toString());
+ if (DBG) Log.d(TAG, "request time failed: " + e);
+ return false;
+ }
+ return requestTime(address, NTP_PORT, timeout, networkForResolv);
+ }
+
+ public boolean requestTime(InetAddress address, int port, int timeout, Network network) {
+ DatagramSocket socket = null;
+ final int oldTag = TrafficStats.getAndSetThreadStatsTag(
+ TrafficStatsConstants.TAG_SYSTEM_NTP);
+ try {
+ socket = new DatagramSocket();
+ network.bindSocket(socket);
+ socket.setSoTimeout(timeout);
+ byte[] buffer = new byte[NTP_PACKET_SIZE];
+ DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, port);
+
+ // set mode = 3 (client) and version = 3
+ // mode is in low 3 bits of first byte
+ // version is in bits 3-5 of first byte
+ buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3);
+
+ // get current time and write it to the request packet
+ final long requestTime = System.currentTimeMillis();
+ final long requestTicks = SystemClock.elapsedRealtime();
+ writeTimeStamp(buffer, TRANSMIT_TIME_OFFSET, requestTime);
+
+ socket.send(request);
+
+ // read the response
+ DatagramPacket response = new DatagramPacket(buffer, buffer.length);
+ socket.receive(response);
+ final long responseTicks = SystemClock.elapsedRealtime();
+ final long responseTime = requestTime + (responseTicks - requestTicks);
+
+ // extract the results
+ final byte leap = (byte) ((buffer[0] >> 6) & 0x3);
+ final byte mode = (byte) (buffer[0] & 0x7);
+ final int stratum = (int) (buffer[1] & 0xff);
+ final long originateTime = readTimeStamp(buffer, ORIGINATE_TIME_OFFSET);
+ final long receiveTime = readTimeStamp(buffer, RECEIVE_TIME_OFFSET);
+ final long transmitTime = readTimeStamp(buffer, TRANSMIT_TIME_OFFSET);
+
+ /* do sanity check according to RFC */
+ // TODO: validate originateTime == requestTime.
+ checkValidServerReply(leap, mode, stratum, transmitTime);
+
+ long roundTripTime = responseTicks - requestTicks - (transmitTime - receiveTime);
+ // receiveTime = originateTime + transit + skew
+ // responseTime = transmitTime + transit - skew
+ // clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2
+ // = ((originateTime + transit + skew - originateTime) +
+ // (transmitTime - (transmitTime + transit - skew)))/2
+ // = ((transit + skew) + (transmitTime - transmitTime - transit + skew))/2
+ // = (transit + skew - transit + skew)/2
+ // = (2 * skew)/2 = skew
+ long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2;
+ EventLogTags.writeNtpSuccess(address.toString(), roundTripTime, clockOffset);
+ if (DBG) {
+ Log.d(TAG, "round trip: " + roundTripTime + "ms, " +
+ "clock offset: " + clockOffset + "ms");
+ }
+
+ // save our results - use the times on this side of the network latency
+ // (response rather than request time)
+ mNtpTime = responseTime + clockOffset;
+ mNtpTimeReference = responseTicks;
+ mRoundTripTime = roundTripTime;
+ } catch (Exception e) {
+ EventLogTags.writeNtpFailure(address.toString(), e.toString());
+ if (DBG) Log.d(TAG, "request time failed: " + e);
+ return false;
+ } finally {
+ if (socket != null) {
+ socket.close();
+ }
+ TrafficStats.setThreadStatsTag(oldTag);
+ }
+
+ return true;
+ }
+
+ @Deprecated
+ @UnsupportedAppUsage
+ public boolean requestTime(String host, int timeout) {
+ Log.w(TAG, "Shame on you for calling the hidden API requestTime()!");
+ return false;
+ }
+
+ /**
+ * Returns the time computed from the NTP transaction.
+ *
+ * @return time value computed from NTP server response.
+ */
+ @UnsupportedAppUsage
+ public long getNtpTime() {
+ return mNtpTime;
+ }
+
+ /**
+ * Returns the reference clock value (value of SystemClock.elapsedRealtime())
+ * corresponding to the NTP time.
+ *
+ * @return reference clock corresponding to the NTP time.
+ */
+ @UnsupportedAppUsage
+ public long getNtpTimeReference() {
+ return mNtpTimeReference;
+ }
+
+ /**
+ * Returns the round trip time of the NTP transaction
+ *
+ * @return round trip time in milliseconds.
+ */
+ @UnsupportedAppUsage
+ public long getRoundTripTime() {
+ return mRoundTripTime;
+ }
+
+ private static void checkValidServerReply(
+ byte leap, byte mode, int stratum, long transmitTime)
+ throws InvalidServerReplyException {
+ if (leap == NTP_LEAP_NOSYNC) {
+ throw new InvalidServerReplyException("unsynchronized server");
+ }
+ if ((mode != NTP_MODE_SERVER) && (mode != NTP_MODE_BROADCAST)) {
+ throw new InvalidServerReplyException("untrusted mode: " + mode);
+ }
+ if ((stratum == NTP_STRATUM_DEATH) || (stratum > NTP_STRATUM_MAX)) {
+ throw new InvalidServerReplyException("untrusted stratum: " + stratum);
+ }
+ if (transmitTime == 0) {
+ throw new InvalidServerReplyException("zero transmitTime");
+ }
+ }
+
+ /**
+ * Reads an unsigned 32 bit big endian number from the given offset in the buffer.
+ */
+ private long read32(byte[] buffer, int offset) {
+ byte b0 = buffer[offset];
+ byte b1 = buffer[offset+1];
+ byte b2 = buffer[offset+2];
+ byte b3 = buffer[offset+3];
+
+ // convert signed bytes to unsigned values
+ int i0 = ((b0 & 0x80) == 0x80 ? (b0 & 0x7F) + 0x80 : b0);
+ int i1 = ((b1 & 0x80) == 0x80 ? (b1 & 0x7F) + 0x80 : b1);
+ int i2 = ((b2 & 0x80) == 0x80 ? (b2 & 0x7F) + 0x80 : b2);
+ int i3 = ((b3 & 0x80) == 0x80 ? (b3 & 0x7F) + 0x80 : b3);
+
+ return ((long)i0 << 24) + ((long)i1 << 16) + ((long)i2 << 8) + (long)i3;
+ }
+
+ /**
+ * Reads the NTP time stamp at the given offset in the buffer and returns
+ * it as a system time (milliseconds since January 1, 1970).
+ */
+ private long readTimeStamp(byte[] buffer, int offset) {
+ long seconds = read32(buffer, offset);
+ long fraction = read32(buffer, offset + 4);
+ // Special case: zero means zero.
+ if (seconds == 0 && fraction == 0) {
+ return 0;
+ }
+ return ((seconds - OFFSET_1900_TO_1970) * 1000) + ((fraction * 1000L) / 0x100000000L);
+ }
+
+ /**
+ * Writes system time (milliseconds since January 1, 1970) as an NTP time stamp
+ * at the given offset in the buffer.
+ */
+ private void writeTimeStamp(byte[] buffer, int offset, long time) {
+ // Special case: zero means zero.
+ if (time == 0) {
+ Arrays.fill(buffer, offset, offset + 8, (byte) 0x00);
+ return;
+ }
+
+ long seconds = time / 1000L;
+ long milliseconds = time - seconds * 1000L;
+ seconds += OFFSET_1900_TO_1970;
+
+ // write seconds in big endian format
+ buffer[offset++] = (byte)(seconds >> 24);
+ buffer[offset++] = (byte)(seconds >> 16);
+ buffer[offset++] = (byte)(seconds >> 8);
+ buffer[offset++] = (byte)(seconds >> 0);
+
+ long fraction = milliseconds * 0x100000000L / 1000L;
+ // write fraction in big endian format
+ buffer[offset++] = (byte)(fraction >> 24);
+ buffer[offset++] = (byte)(fraction >> 16);
+ buffer[offset++] = (byte)(fraction >> 8);
+ // low order bits should be random data
+ buffer[offset++] = (byte)(Math.random() * 255.0);
+ }
+}
diff --git a/android/net/SocketKeepalive.java b/android/net/SocketKeepalive.java
new file mode 100644
index 0000000..ec73866
--- /dev/null
+++ b/android/net/SocketKeepalive.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * Allows applications to request that the system periodically send specific packets on their
+ * behalf, using hardware offload to save battery power.
+ *
+ * To request that the system send keepalives, call one of the methods that return a
+ * {@link SocketKeepalive} object, such as {@link ConnectivityManager#createSocketKeepalive},
+ * passing in a non-null callback. If the {@link SocketKeepalive} is successfully
+ * started, the callback's {@code onStarted} method will be called. If an error occurs,
+ * {@code onError} will be called, specifying one of the {@code ERROR_*} constants in this
+ * class.
+ *
+ * To stop an existing keepalive, call {@link SocketKeepalive#stop}. The system will call
+ * {@link SocketKeepalive.Callback#onStopped} if the operation was successful or
+ * {@link SocketKeepalive.Callback#onError} if an error occurred.
+ *
+ * For cellular, the device MUST support at least 1 keepalive slot.
+ *
+ * For WiFi, the device SHOULD support keepalive offload. If it does not, it MUST reply with
+ * {@link SocketKeepalive.Callback#onError} with {@code ERROR_UNSUPPORTED} to any keepalive offload
+ * request. If it does, it MUST support at least 3 concurrent keepalive slots.
+ */
+public abstract class SocketKeepalive implements AutoCloseable {
+ static final String TAG = "SocketKeepalive";
+
+ /** @hide */
+ public static final int SUCCESS = 0;
+
+ /** @hide */
+ public static final int NO_KEEPALIVE = -1;
+
+ /** @hide */
+ public static final int DATA_RECEIVED = -2;
+
+ /** @hide */
+ public static final int BINDER_DIED = -10;
+
+ /** The specified {@code Network} is not connected. */
+ public static final int ERROR_INVALID_NETWORK = -20;
+ /** The specified IP addresses are invalid. For example, the specified source IP address is
+ * not configured on the specified {@code Network}. */
+ public static final int ERROR_INVALID_IP_ADDRESS = -21;
+ /** The requested port is invalid. */
+ public static final int ERROR_INVALID_PORT = -22;
+ /** The packet length is invalid (e.g., too long). */
+ public static final int ERROR_INVALID_LENGTH = -23;
+ /** The packet transmission interval is invalid (e.g., too short). */
+ public static final int ERROR_INVALID_INTERVAL = -24;
+ /** The target socket is invalid. */
+ public static final int ERROR_INVALID_SOCKET = -25;
+ /** The target socket is not idle. */
+ public static final int ERROR_SOCKET_NOT_IDLE = -26;
+
+ /** The device does not support this request. */
+ public static final int ERROR_UNSUPPORTED = -30;
+ /** @hide TODO: delete when telephony code has been updated. */
+ public static final int ERROR_HARDWARE_UNSUPPORTED = ERROR_UNSUPPORTED;
+ /** The hardware returned an error. */
+ public static final int ERROR_HARDWARE_ERROR = -31;
+ /** The limitation of resource is reached. */
+ public static final int ERROR_INSUFFICIENT_RESOURCES = -32;
+
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "ERROR_" }, value = {
+ ERROR_INVALID_NETWORK,
+ ERROR_INVALID_IP_ADDRESS,
+ ERROR_INVALID_PORT,
+ ERROR_INVALID_LENGTH,
+ ERROR_INVALID_INTERVAL,
+ ERROR_INVALID_SOCKET,
+ ERROR_SOCKET_NOT_IDLE
+ })
+ public @interface ErrorCode {}
+
+ /**
+ * The minimum interval in seconds between keepalive packet transmissions.
+ *
+ * @hide
+ **/
+ public static final int MIN_INTERVAL_SEC = 10;
+
+ /**
+ * The maximum interval in seconds between keepalive packet transmissions.
+ *
+ * @hide
+ **/
+ public static final int MAX_INTERVAL_SEC = 3600;
+
+ /**
+ * An exception that embarks an error code.
+ * @hide
+ */
+ public static class ErrorCodeException extends Exception {
+ public final int error;
+ public ErrorCodeException(final int error, final Throwable e) {
+ super(e);
+ this.error = error;
+ }
+ public ErrorCodeException(final int error) {
+ this.error = error;
+ }
+ }
+
+ /**
+ * This socket is invalid.
+ * See the error code for details, and the optional cause.
+ * @hide
+ */
+ public static class InvalidSocketException extends ErrorCodeException {
+ public InvalidSocketException(final int error, final Throwable e) {
+ super(error, e);
+ }
+ public InvalidSocketException(final int error) {
+ super(error);
+ }
+ }
+
+ /**
+ * This packet is invalid.
+ * See the error code for details.
+ * @hide
+ */
+ public static class InvalidPacketException extends ErrorCodeException {
+ public InvalidPacketException(final int error) {
+ super(error);
+ }
+ }
+
+ @NonNull final IConnectivityManager mService;
+ @NonNull final Network mNetwork;
+ @NonNull final ParcelFileDescriptor mPfd;
+ @NonNull final Executor mExecutor;
+ @NonNull final ISocketKeepaliveCallback mCallback;
+ // TODO: remove slot since mCallback could be used to identify which keepalive to stop.
+ @Nullable Integer mSlot;
+
+ SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network,
+ @NonNull ParcelFileDescriptor pfd,
+ @NonNull Executor executor, @NonNull Callback callback) {
+ mService = service;
+ mNetwork = network;
+ mPfd = pfd;
+ mExecutor = executor;
+ mCallback = new ISocketKeepaliveCallback.Stub() {
+ @Override
+ public void onStarted(int slot) {
+ Binder.withCleanCallingIdentity(() ->
+ mExecutor.execute(() -> {
+ mSlot = slot;
+ callback.onStarted();
+ }));
+ }
+
+ @Override
+ public void onStopped() {
+ Binder.withCleanCallingIdentity(() ->
+ executor.execute(() -> {
+ mSlot = null;
+ callback.onStopped();
+ }));
+ }
+
+ @Override
+ public void onError(int error) {
+ Binder.withCleanCallingIdentity(() ->
+ executor.execute(() -> {
+ mSlot = null;
+ callback.onError(error);
+ }));
+ }
+
+ @Override
+ public void onDataReceived() {
+ Binder.withCleanCallingIdentity(() ->
+ executor.execute(() -> {
+ mSlot = null;
+ callback.onDataReceived();
+ }));
+ }
+ };
+ }
+
+ /**
+ * Request that keepalive be started with the given {@code intervalSec}. See
+ * {@link SocketKeepalive}. If the remote binder dies, or the binder call throws an exception
+ * when invoking start or stop of the {@link SocketKeepalive}, a {@link RemoteException} will be
+ * thrown into the {@code executor}. This is typically not important to catch because the remote
+ * party is the system, so if it is not in shape to communicate through binder the system is
+ * probably going down anyway. If the caller cares regardless, it can use a custom
+ * {@link Executor} to catch the {@link RemoteException}.
+ *
+ * @param intervalSec The target interval in seconds between keepalive packet transmissions.
+ * The interval should be between 10 seconds and 3600 seconds, otherwise
+ * {@link #ERROR_INVALID_INTERVAL} will be returned.
+ */
+ public final void start(@IntRange(from = MIN_INTERVAL_SEC, to = MAX_INTERVAL_SEC)
+ int intervalSec) {
+ startImpl(intervalSec);
+ }
+
+ abstract void startImpl(int intervalSec);
+
+ /**
+ * Requests that keepalive be stopped. The application must wait for {@link Callback#onStopped}
+ * before using the object. See {@link SocketKeepalive}.
+ */
+ public final void stop() {
+ stopImpl();
+ }
+
+ abstract void stopImpl();
+
+ /**
+ * Deactivate this {@link SocketKeepalive} and free allocated resources. The instance won't be
+ * usable again if {@code close()} is called.
+ */
+ @Override
+ public final void close() {
+ stop();
+ try {
+ mPfd.close();
+ } catch (IOException e) {
+ // Nothing much can be done.
+ }
+ }
+
+ /**
+ * The callback which app can use to learn the status changes of {@link SocketKeepalive}. See
+ * {@link SocketKeepalive}.
+ */
+ public static class Callback {
+ /** The requested keepalive was successfully started. */
+ public void onStarted() {}
+ /** The keepalive was successfully stopped. */
+ public void onStopped() {}
+ /** An error occurred. */
+ public void onError(@ErrorCode int error) {}
+ /** The keepalive on a TCP socket was stopped because the socket received data. This is
+ * never called for UDP sockets. */
+ public void onDataReceived() {}
+ }
+}
diff --git a/android/net/StaticIpConfiguration.java b/android/net/StaticIpConfiguration.java
new file mode 100644
index 0000000..050fcfa
--- /dev/null
+++ b/android/net/StaticIpConfiguration.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2014 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.net.shared.InetAddressUtils;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Class that describes static IP configuration.
+ *
+ * <p>This class is different from {@link LinkProperties} because it represents
+ * configuration intent. The general contract is that if we can represent
+ * a configuration here, then we should be able to configure it on a network.
+ * The intent is that it closely match the UI we have for configuring networks.
+ *
+ * <p>In contrast, {@link LinkProperties} represents current state. It is much more
+ * expressive. For example, it supports multiple IP addresses, multiple routes,
+ * stacked interfaces, and so on. Because LinkProperties is so expressive,
+ * using it to represent configuration intent as well as current state causes
+ * problems. For example, we could unknowingly save a configuration that we are
+ * not in fact capable of applying, or we could save a configuration that the
+ * UI cannot display, which has the potential for malicious code to hide
+ * hostile or unexpected configuration from the user.
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+public final class StaticIpConfiguration implements Parcelable {
+ /** @hide */
+ @UnsupportedAppUsage
+ @Nullable
+ public LinkAddress ipAddress;
+ /** @hide */
+ @UnsupportedAppUsage
+ @Nullable
+ public InetAddress gateway;
+ /** @hide */
+ @UnsupportedAppUsage
+ @NonNull
+ public final ArrayList<InetAddress> dnsServers;
+ /** @hide */
+ @UnsupportedAppUsage
+ @Nullable
+ public String domains;
+
+ public StaticIpConfiguration() {
+ dnsServers = new ArrayList<>();
+ }
+
+ public StaticIpConfiguration(@Nullable StaticIpConfiguration source) {
+ this();
+ if (source != null) {
+ // All of these except dnsServers are immutable, so no need to make copies.
+ ipAddress = source.ipAddress;
+ gateway = source.gateway;
+ dnsServers.addAll(source.dnsServers);
+ domains = source.domains;
+ }
+ }
+
+ public void clear() {
+ ipAddress = null;
+ gateway = null;
+ dnsServers.clear();
+ domains = null;
+ }
+
+ /**
+ * Get the static IP address included in the configuration.
+ */
+ public @Nullable LinkAddress getIpAddress() {
+ return ipAddress;
+ }
+
+ /**
+ * Get the gateway included in the configuration.
+ */
+ public @Nullable InetAddress getGateway() {
+ return gateway;
+ }
+
+ /**
+ * Get the DNS servers included in the configuration.
+ */
+ public @NonNull List<InetAddress> getDnsServers() {
+ return dnsServers;
+ }
+
+ /**
+ * Get a {@link String} listing in priority order of the comma separated domains to search when
+ * resolving host names on the link.
+ */
+ public @Nullable String getDomains() {
+ return domains;
+ }
+
+ /**
+ * Helper class to build a new instance of {@link StaticIpConfiguration}.
+ */
+ public static final class Builder {
+ private LinkAddress mIpAddress;
+ private InetAddress mGateway;
+ private Iterable<InetAddress> mDnsServers;
+ private String mDomains;
+
+ /**
+ * Set the IP address to be included in the configuration; null by default.
+ * @return The {@link Builder} for chaining.
+ */
+ public @NonNull Builder setIpAddress(@Nullable LinkAddress ipAddress) {
+ mIpAddress = ipAddress;
+ return this;
+ }
+
+ /**
+ * Set the address of the gateway to be included in the configuration; null by default.
+ * @return The {@link Builder} for chaining.
+ */
+ public @NonNull Builder setGateway(@Nullable InetAddress gateway) {
+ mGateway = gateway;
+ return this;
+ }
+
+ /**
+ * Set the addresses of the DNS servers included in the configuration; empty by default.
+ * @return The {@link Builder} for chaining.
+ */
+ public @NonNull Builder setDnsServers(@NonNull Iterable<InetAddress> dnsServers) {
+ mDnsServers = dnsServers;
+ return this;
+ }
+
+ /**
+ * Sets the DNS domain search path to be used on the link; null by default.
+ * @param newDomains A {@link String} containing the comma separated domains to search when
+ * resolving host names on this link, in priority order.
+ * @return The {@link Builder} for chaining.
+ */
+ public @NonNull Builder setDomains(@Nullable String newDomains) {
+ mDomains = newDomains;
+ return this;
+ }
+
+ /**
+ * Create a {@link StaticIpConfiguration} from the parameters in this {@link Builder}.
+ * @return The newly created StaticIpConfiguration.
+ */
+ public @NonNull StaticIpConfiguration build() {
+ final StaticIpConfiguration config = new StaticIpConfiguration();
+ config.ipAddress = mIpAddress;
+ config.gateway = mGateway;
+ for (InetAddress server : mDnsServers) {
+ config.dnsServers.add(server);
+ }
+ config.domains = mDomains;
+ return config;
+ }
+ }
+
+ /**
+ * Add a DNS server to this configuration.
+ */
+ public void addDnsServer(@NonNull InetAddress server) {
+ dnsServers.add(server);
+ }
+
+ /**
+ * Returns the network routes specified by this object. Will typically include a
+ * directly-connected route for the IP address's local subnet and a default route.
+ * @param iface Interface to include in the routes.
+ */
+ public @NonNull List<RouteInfo> getRoutes(@Nullable String iface) {
+ List<RouteInfo> routes = new ArrayList<RouteInfo>(3);
+ if (ipAddress != null) {
+ RouteInfo connectedRoute = new RouteInfo(ipAddress, null, iface);
+ routes.add(connectedRoute);
+ // If the default gateway is not covered by the directly-connected route, also add a
+ // host route to the gateway as well. This configuration is arguably invalid, but it
+ // used to work in K and earlier, and other OSes appear to accept it.
+ if (gateway != null && !connectedRoute.matches(gateway)) {
+ routes.add(RouteInfo.makeHostRoute(gateway, iface));
+ }
+ }
+ if (gateway != null) {
+ routes.add(new RouteInfo((IpPrefix) null, gateway, iface));
+ }
+ return routes;
+ }
+
+ /**
+ * Returns a LinkProperties object expressing the data in this object. Note that the information
+ * contained in the LinkProperties will not be a complete picture of the link's configuration,
+ * because any configuration information that is obtained dynamically by the network (e.g.,
+ * IPv6 configuration) will not be included.
+ * @hide
+ */
+ public @NonNull LinkProperties toLinkProperties(String iface) {
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName(iface);
+ if (ipAddress != null) {
+ lp.addLinkAddress(ipAddress);
+ }
+ for (RouteInfo route : getRoutes(iface)) {
+ lp.addRoute(route);
+ }
+ for (InetAddress dns : dnsServers) {
+ lp.addDnsServer(dns);
+ }
+ lp.setDomains(domains);
+ return lp;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer str = new StringBuffer();
+
+ str.append("IP address ");
+ if (ipAddress != null ) str.append(ipAddress).append(" ");
+
+ str.append("Gateway ");
+ if (gateway != null) str.append(gateway.getHostAddress()).append(" ");
+
+ str.append(" DNS servers: [");
+ for (InetAddress dnsServer : dnsServers) {
+ str.append(" ").append(dnsServer.getHostAddress());
+ }
+
+ str.append(" ] Domains ");
+ if (domains != null) str.append(domains);
+ return str.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 13;
+ result = 47 * result + (ipAddress == null ? 0 : ipAddress.hashCode());
+ result = 47 * result + (gateway == null ? 0 : gateway.hashCode());
+ result = 47 * result + (domains == null ? 0 : domains.hashCode());
+ result = 47 * result + dnsServers.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+
+ if (!(obj instanceof StaticIpConfiguration)) return false;
+
+ StaticIpConfiguration other = (StaticIpConfiguration) obj;
+
+ return other != null &&
+ Objects.equals(ipAddress, other.ipAddress) &&
+ Objects.equals(gateway, other.gateway) &&
+ dnsServers.equals(other.dnsServers) &&
+ Objects.equals(domains, other.domains);
+ }
+
+ /** Implement the Parcelable interface */
+ public static final @android.annotation.NonNull Creator<StaticIpConfiguration> CREATOR =
+ new Creator<StaticIpConfiguration>() {
+ public StaticIpConfiguration createFromParcel(Parcel in) {
+ return readFromParcel(in);
+ }
+
+ public StaticIpConfiguration[] newArray(int size) {
+ return new StaticIpConfiguration[size];
+ }
+ };
+
+ /** Implement the Parcelable interface */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(ipAddress, flags);
+ InetAddressUtils.parcelInetAddress(dest, gateway, flags);
+ dest.writeInt(dnsServers.size());
+ for (InetAddress dnsServer : dnsServers) {
+ InetAddressUtils.parcelInetAddress(dest, dnsServer, flags);
+ }
+ dest.writeString(domains);
+ }
+
+ /** @hide */
+ public static StaticIpConfiguration readFromParcel(Parcel in) {
+ final StaticIpConfiguration s = new StaticIpConfiguration();
+ s.ipAddress = in.readParcelable(null);
+ s.gateway = InetAddressUtils.unparcelInetAddress(in);
+ s.dnsServers.clear();
+ int size = in.readInt();
+ for (int i = 0; i < size; i++) {
+ s.dnsServers.add(InetAddressUtils.unparcelInetAddress(in));
+ }
+ s.domains = in.readString();
+ return s;
+ }
+}
diff --git a/android/net/StringNetworkSpecifier.java b/android/net/StringNetworkSpecifier.java
new file mode 100644
index 0000000..21dee55
--- /dev/null
+++ b/android/net/StringNetworkSpecifier.java
@@ -0,0 +1,81 @@
+/*
+ * 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 android.net;
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/** @hide */
+public final class StringNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+ /**
+ * Arbitrary string used to pass (additional) information to the network factory.
+ */
+ @UnsupportedAppUsage
+ public final String specifier;
+
+ public StringNetworkSpecifier(String specifier) {
+ Preconditions.checkStringNotEmpty(specifier);
+ this.specifier = specifier;
+ }
+
+ @Override
+ public boolean satisfiedBy(NetworkSpecifier other) {
+ return equals(other);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof StringNetworkSpecifier)) return false;
+ return TextUtils.equals(specifier, ((StringNetworkSpecifier) o).specifier);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(specifier);
+ }
+
+ @Override
+ public String toString() {
+ return specifier;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(specifier);
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<StringNetworkSpecifier> CREATOR =
+ new Parcelable.Creator<StringNetworkSpecifier>() {
+ public StringNetworkSpecifier createFromParcel(Parcel in) {
+ return new StringNetworkSpecifier(in.readString());
+ }
+ public StringNetworkSpecifier[] newArray(int size) {
+ return new StringNetworkSpecifier[size];
+ }
+ };
+}
diff --git a/android/net/TcpKeepalivePacketData.java b/android/net/TcpKeepalivePacketData.java
new file mode 100644
index 0000000..7f2f499
--- /dev/null
+++ b/android/net/TcpKeepalivePacketData.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.SocketKeepalive.InvalidPacketException;
+import android.net.util.IpUtils;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.system.OsConstants;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Objects;
+
+/**
+ * Represents the actual tcp keep alive packets which will be used for hardware offload.
+ * @hide
+ */
+public class TcpKeepalivePacketData extends KeepalivePacketData implements Parcelable {
+ private static final String TAG = "TcpKeepalivePacketData";
+
+ /** TCP sequence number. */
+ public final int tcpSeq;
+
+ /** TCP ACK number. */
+ public final int tcpAck;
+
+ /** TCP RCV window. */
+ public final int tcpWnd;
+
+ /** TCP RCV window scale. */
+ public final int tcpWndScale;
+
+ /** IP TOS. */
+ public final int ipTos;
+
+ /** IP TTL. */
+ public final int ipTtl;
+
+ private static final int IPV4_HEADER_LENGTH = 20;
+ private static final int IPV6_HEADER_LENGTH = 40;
+ private static final int TCP_HEADER_LENGTH = 20;
+
+ // This should only be constructed via static factory methods, such as
+ // tcpKeepalivePacket.
+ private TcpKeepalivePacketData(final TcpKeepalivePacketDataParcelable tcpDetails,
+ final byte[] data) throws InvalidPacketException, UnknownHostException {
+ super(InetAddress.getByAddress(tcpDetails.srcAddress), tcpDetails.srcPort,
+ InetAddress.getByAddress(tcpDetails.dstAddress), tcpDetails.dstPort, data);
+ tcpSeq = tcpDetails.seq;
+ tcpAck = tcpDetails.ack;
+ // In the packet, the window is shifted right by the window scale.
+ tcpWnd = tcpDetails.rcvWnd;
+ tcpWndScale = tcpDetails.rcvWndScale;
+ ipTos = tcpDetails.tos;
+ ipTtl = tcpDetails.ttl;
+ }
+
+ /**
+ * Factory method to create tcp keepalive packet structure.
+ */
+ public static TcpKeepalivePacketData tcpKeepalivePacket(
+ TcpKeepalivePacketDataParcelable tcpDetails) throws InvalidPacketException {
+ final byte[] packet;
+ try {
+ if ((tcpDetails.srcAddress != null) && (tcpDetails.dstAddress != null)
+ && (tcpDetails.srcAddress.length == 4 /* V4 IP length */)
+ && (tcpDetails.dstAddress.length == 4 /* V4 IP length */)) {
+ packet = buildV4Packet(tcpDetails);
+ } else {
+ // TODO: support ipv6
+ throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
+ }
+ return new TcpKeepalivePacketData(tcpDetails, packet);
+ } catch (UnknownHostException e) {
+ throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
+ }
+
+ }
+
+ /**
+ * Build ipv4 tcp keepalive packet, not including the link-layer header.
+ */
+ // TODO : if this code is ever moved to the network stack, factorize constants with the ones
+ // over there.
+ private static byte[] buildV4Packet(TcpKeepalivePacketDataParcelable tcpDetails) {
+ final int length = IPV4_HEADER_LENGTH + TCP_HEADER_LENGTH;
+ ByteBuffer buf = ByteBuffer.allocate(length);
+ buf.order(ByteOrder.BIG_ENDIAN);
+ buf.put((byte) 0x45); // IP version and IHL
+ buf.put((byte) tcpDetails.tos); // TOS
+ buf.putShort((short) length);
+ buf.putInt(0x00004000); // ID, flags=DF, offset
+ buf.put((byte) tcpDetails.ttl); // TTL
+ buf.put((byte) OsConstants.IPPROTO_TCP);
+ final int ipChecksumOffset = buf.position();
+ buf.putShort((short) 0); // IP checksum
+ buf.put(tcpDetails.srcAddress);
+ buf.put(tcpDetails.dstAddress);
+ buf.putShort((short) tcpDetails.srcPort);
+ buf.putShort((short) tcpDetails.dstPort);
+ buf.putInt(tcpDetails.seq); // Sequence Number
+ buf.putInt(tcpDetails.ack); // ACK
+ buf.putShort((short) 0x5010); // TCP length=5, flags=ACK
+ buf.putShort((short) (tcpDetails.rcvWnd >> tcpDetails.rcvWndScale)); // Window size
+ final int tcpChecksumOffset = buf.position();
+ buf.putShort((short) 0); // TCP checksum
+ // URG is not set therefore the urgent pointer is zero.
+ buf.putShort((short) 0); // Urgent pointer
+
+ buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0));
+ buf.putShort(tcpChecksumOffset, IpUtils.tcpChecksum(
+ buf, 0, IPV4_HEADER_LENGTH, TCP_HEADER_LENGTH));
+
+ return buf.array();
+ }
+
+ // TODO: add buildV6Packet.
+
+ @Override
+ public boolean equals(@Nullable final Object o) {
+ if (!(o instanceof TcpKeepalivePacketData)) return false;
+ final TcpKeepalivePacketData other = (TcpKeepalivePacketData) o;
+ return this.srcAddress.equals(other.srcAddress)
+ && this.dstAddress.equals(other.dstAddress)
+ && this.srcPort == other.srcPort
+ && this.dstPort == other.dstPort
+ && this.tcpAck == other.tcpAck
+ && this.tcpSeq == other.tcpSeq
+ && this.tcpWnd == other.tcpWnd
+ && this.tcpWndScale == other.tcpWndScale
+ && this.ipTos == other.ipTos
+ && this.ipTtl == other.ipTtl;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(srcAddress, dstAddress, srcPort, dstPort, tcpAck, tcpSeq, tcpWnd,
+ tcpWndScale, ipTos, ipTtl);
+ }
+
+ /**
+ * Parcelable Implementation.
+ * Note that this object implements parcelable (and needs to keep doing this as it inherits
+ * from a class that does), but should usually be parceled as a stable parcelable using
+ * the toStableParcelable() and fromStableParcelable() methods.
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Write to parcel. */
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeInt(tcpSeq);
+ out.writeInt(tcpAck);
+ out.writeInt(tcpWnd);
+ out.writeInt(tcpWndScale);
+ out.writeInt(ipTos);
+ out.writeInt(ipTtl);
+ }
+
+ private TcpKeepalivePacketData(Parcel in) {
+ super(in);
+ tcpSeq = in.readInt();
+ tcpAck = in.readInt();
+ tcpWnd = in.readInt();
+ tcpWndScale = in.readInt();
+ ipTos = in.readInt();
+ ipTtl = in.readInt();
+ }
+
+ /** Parcelable Creator. */
+ public static final @NonNull Parcelable.Creator<TcpKeepalivePacketData> CREATOR =
+ new Parcelable.Creator<TcpKeepalivePacketData>() {
+ public TcpKeepalivePacketData createFromParcel(Parcel in) {
+ return new TcpKeepalivePacketData(in);
+ }
+
+ public TcpKeepalivePacketData[] newArray(int size) {
+ return new TcpKeepalivePacketData[size];
+ }
+ };
+
+ /**
+ * Convert this TcpKeepalivePacketData to a TcpKeepalivePacketDataParcelable.
+ */
+ @NonNull
+ public TcpKeepalivePacketDataParcelable toStableParcelable() {
+ final TcpKeepalivePacketDataParcelable parcel = new TcpKeepalivePacketDataParcelable();
+ parcel.srcAddress = srcAddress.getAddress();
+ parcel.srcPort = srcPort;
+ parcel.dstAddress = dstAddress.getAddress();
+ parcel.dstPort = dstPort;
+ parcel.seq = tcpSeq;
+ parcel.ack = tcpAck;
+ parcel.rcvWnd = tcpWnd;
+ parcel.rcvWndScale = tcpWndScale;
+ parcel.tos = ipTos;
+ parcel.ttl = ipTtl;
+ return parcel;
+ }
+
+ @Override
+ public String toString() {
+ return "saddr: " + srcAddress
+ + " daddr: " + dstAddress
+ + " sport: " + srcPort
+ + " dport: " + dstPort
+ + " seq: " + tcpSeq
+ + " ack: " + tcpAck
+ + " wnd: " + tcpWnd
+ + " wndScale: " + tcpWndScale
+ + " tos: " + ipTos
+ + " ttl: " + ipTtl;
+ }
+}
diff --git a/android/net/TcpRepairWindow.java b/android/net/TcpRepairWindow.java
new file mode 100644
index 0000000..86034f0
--- /dev/null
+++ b/android/net/TcpRepairWindow.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * Corresponds to C's {@code struct tcp_repair_window} from
+ * include/uapi/linux/tcp.h
+ *
+ * @hide
+ */
+public final class TcpRepairWindow {
+ public final int sndWl1;
+ public final int sndWnd;
+ public final int maxWindow;
+ public final int rcvWnd;
+ public final int rcvWup;
+ public final int rcvWndScale;
+
+ /**
+ * Constructs an instance with the given field values.
+ */
+ public TcpRepairWindow(final int sndWl1, final int sndWnd, final int maxWindow,
+ final int rcvWnd, final int rcvWup, final int rcvWndScale) {
+ this.sndWl1 = sndWl1;
+ this.sndWnd = sndWnd;
+ this.maxWindow = maxWindow;
+ this.rcvWnd = rcvWnd;
+ this.rcvWup = rcvWup;
+ this.rcvWndScale = rcvWndScale;
+ }
+}
diff --git a/android/net/TcpSocketKeepalive.java b/android/net/TcpSocketKeepalive.java
new file mode 100644
index 0000000..436397e
--- /dev/null
+++ b/android/net/TcpSocketKeepalive.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.util.concurrent.Executor;
+
+/** @hide */
+final class TcpSocketKeepalive extends SocketKeepalive {
+
+ TcpSocketKeepalive(@NonNull IConnectivityManager service,
+ @NonNull Network network,
+ @NonNull ParcelFileDescriptor pfd,
+ @NonNull Executor executor,
+ @NonNull Callback callback) {
+ super(service, network, pfd, executor, callback);
+ }
+
+ /**
+ * Starts keepalives. {@code mSocket} must be a connected TCP socket.
+ *
+ * - The application must not write to or read from the socket after calling this method, until
+ * onDataReceived, onStopped, or onError are called. If it does, the keepalive will fail
+ * with {@link #ERROR_SOCKET_NOT_IDLE}, or {@code #ERROR_INVALID_SOCKET} if the socket
+ * experienced an error (as in poll(2) returned POLLERR or POLLHUP); if this happens, the data
+ * received from the socket may be invalid, and the socket can't be recovered.
+ * - If the socket has data in the send or receive buffer, then this call will fail with
+ * {@link #ERROR_SOCKET_NOT_IDLE} and can be retried after the data has been processed.
+ * An app could ensure this by using an application-layer protocol to receive acknowledgement
+ * that indicates all data has been delivered to server, e.g. HTTP 200 OK.
+ * Then the app could go into keepalive mode after reading all remaining data within the
+ * acknowledgement.
+ */
+ @Override
+ void startImpl(int intervalSec) {
+ mExecutor.execute(() -> {
+ try {
+ final FileDescriptor fd = mPfd.getFileDescriptor();
+ mService.startTcpKeepalive(mNetwork, fd, intervalSec, mCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error starting packet keepalive: ", e);
+ throw e.rethrowFromSystemServer();
+ }
+ });
+ }
+
+ @Override
+ void stopImpl() {
+ mExecutor.execute(() -> {
+ try {
+ if (mSlot != null) {
+ mService.stopKeepalive(mNetwork, mSlot);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error stopping packet keepalive: ", e);
+ throw e.rethrowFromSystemServer();
+ }
+ });
+ }
+}
diff --git a/android/net/TestNetworkInterface.java b/android/net/TestNetworkInterface.java
new file mode 100644
index 0000000..8455083
--- /dev/null
+++ b/android/net/TestNetworkInterface.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+/**
+ * This class is used to return the interface name and fd of the test interface
+ *
+ * @hide
+ */
+@TestApi
+public final class TestNetworkInterface implements Parcelable {
+ private final ParcelFileDescriptor mFileDescriptor;
+ private final String mInterfaceName;
+
+ @Override
+ public int describeContents() {
+ return (mFileDescriptor != null) ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeParcelable(mFileDescriptor, PARCELABLE_WRITE_RETURN_VALUE);
+ out.writeString(mInterfaceName);
+ }
+
+ public TestNetworkInterface(ParcelFileDescriptor pfd, String intf) {
+ mFileDescriptor = pfd;
+ mInterfaceName = intf;
+ }
+
+ private TestNetworkInterface(Parcel in) {
+ mFileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
+ mInterfaceName = in.readString();
+ }
+
+ public ParcelFileDescriptor getFileDescriptor() {
+ return mFileDescriptor;
+ }
+
+ public String getInterfaceName() {
+ return mInterfaceName;
+ }
+
+ public static final Parcelable.Creator<TestNetworkInterface> CREATOR =
+ new Parcelable.Creator<TestNetworkInterface>() {
+ public TestNetworkInterface createFromParcel(Parcel in) {
+ return new TestNetworkInterface(in);
+ }
+
+ public TestNetworkInterface[] newArray(int size) {
+ return new TestNetworkInterface[size];
+ }
+ };
+}
diff --git a/android/net/TestNetworkManager.java b/android/net/TestNetworkManager.java
new file mode 100644
index 0000000..e274005
--- /dev/null
+++ b/android/net/TestNetworkManager.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Class that allows creation and management of per-app, test-only networks
+ *
+ * @hide
+ */
+@TestApi
+public class TestNetworkManager {
+ @NonNull private static final String TAG = TestNetworkManager.class.getSimpleName();
+
+ @NonNull private final ITestNetworkManager mService;
+
+ /** @hide */
+ public TestNetworkManager(@NonNull ITestNetworkManager service) {
+ mService = Preconditions.checkNotNull(service, "missing ITestNetworkManager");
+ }
+
+ /**
+ * Teardown the capability-limited, testing-only network for a given interface
+ *
+ * @param network The test network that should be torn down
+ * @hide
+ */
+ @TestApi
+ public void teardownTestNetwork(@NonNull Network network) {
+ try {
+ mService.teardownTestNetwork(network.netId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets up a capability-limited, testing-only network for a given interface
+ *
+ * @param iface the name of the interface to be used for the Network LinkProperties.
+ * @param binder A binder object guarding the lifecycle of this test network.
+ * @hide
+ */
+ @TestApi
+ public void setupTestNetwork(@NonNull String iface, @NonNull IBinder binder) {
+ try {
+ mService.setupTestNetwork(iface, binder);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Create a tun interface for testing purposes
+ *
+ * @param linkAddrs an array of LinkAddresses to assign to the TUN interface
+ * @return A ParcelFileDescriptor of the underlying TUN interface. Close this to tear down the
+ * TUN interface.
+ * @hide
+ */
+ @TestApi
+ public TestNetworkInterface createTunInterface(@NonNull LinkAddress[] linkAddrs) {
+ try {
+ return mService.createTunInterface(linkAddrs);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Create a tap interface for testing purposes
+ *
+ * @return A ParcelFileDescriptor of the underlying TAP interface. Close this to tear down the
+ * TAP interface.
+ * @hide
+ */
+ @TestApi
+ public TestNetworkInterface createTapInterface() {
+ try {
+ return mService.createTapInterface();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+}
diff --git a/android/net/TrafficStats.java b/android/net/TrafficStats.java
new file mode 100644
index 0000000..1c6a484
--- /dev/null
+++ b/android/net/TrafficStats.java
@@ -0,0 +1,999 @@
+/*
+ * Copyright (C) 2007 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;
+
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.app.DownloadManager;
+import android.app.backup.BackupManager;
+import android.app.usage.NetworkStatsManager;
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.os.Build;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.DataUnit;
+
+import com.android.server.NetworkManagementSocketTagger;
+
+import dalvik.system.SocketTagger;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.net.Socket;
+import java.net.SocketException;
+
+/**
+ * Class that provides network traffic statistics. These statistics include
+ * bytes transmitted and received and network packets transmitted and received,
+ * over all interfaces, over the mobile interface, and on a per-UID basis.
+ * <p>
+ * These statistics may not be available on all platforms. If the statistics are
+ * not supported by this device, {@link #UNSUPPORTED} will be returned.
+ * <p>
+ * Note that the statistics returned by this class reset and start from zero
+ * after every reboot. To access more robust historical network statistics data,
+ * use {@link NetworkStatsManager} instead.
+ */
+public class TrafficStats {
+ /**
+ * The return value to indicate that the device does not support the statistic.
+ */
+ public final static int UNSUPPORTED = -1;
+
+ /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */
+ @Deprecated
+ public static final long KB_IN_BYTES = 1024;
+ /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */
+ @Deprecated
+ public static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
+ /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */
+ @Deprecated
+ public static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
+ /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */
+ @Deprecated
+ public static final long TB_IN_BYTES = GB_IN_BYTES * 1024;
+ /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */
+ @Deprecated
+ public static final long PB_IN_BYTES = TB_IN_BYTES * 1024;
+
+ /**
+ * Special UID value used when collecting {@link NetworkStatsHistory} for
+ * removed applications.
+ *
+ * @hide
+ */
+ public static final int UID_REMOVED = -4;
+
+ /**
+ * Special UID value used when collecting {@link NetworkStatsHistory} for
+ * tethering traffic.
+ *
+ * @hide
+ */
+ public static final int UID_TETHERING = -5;
+
+ /**
+ * Tag values in this range are reserved for the network stack. The network stack is
+ * running as UID {@link android.os.Process.NETWORK_STACK_UID} when in the mainline
+ * module separate process, and as the system UID otherwise.
+ */
+ /** @hide */
+ @SystemApi
+ public static final int TAG_NETWORK_STACK_RANGE_START = 0xFFFFFD00;
+ /** @hide */
+ @SystemApi
+ public static final int TAG_NETWORK_STACK_RANGE_END = 0xFFFFFEFF;
+
+ /**
+ * Tags between 0xFFFFFF00 and 0xFFFFFFFF are reserved and used internally by system services
+ * like DownloadManager when performing traffic on behalf of an application.
+ */
+ // Please note there is no enforcement of these constants, so do not rely on them to
+ // determine that the caller is a system caller.
+ /** @hide */
+ @SystemApi
+ public static final int TAG_SYSTEM_IMPERSONATION_RANGE_START = 0xFFFFFF00;
+ /** @hide */
+ @SystemApi
+ public static final int TAG_SYSTEM_IMPERSONATION_RANGE_END = 0xFFFFFF0F;
+
+ /**
+ * Tag values between these ranges are reserved for the network stack to do traffic
+ * on behalf of applications. It is a subrange of the range above.
+ */
+ /** @hide */
+ @SystemApi
+ public static final int TAG_NETWORK_STACK_IMPERSONATION_RANGE_START = 0xFFFFFF80;
+ /** @hide */
+ @SystemApi
+ public static final int TAG_NETWORK_STACK_IMPERSONATION_RANGE_END = 0xFFFFFF8F;
+
+ /**
+ * Default tag value for {@link DownloadManager} traffic.
+ *
+ * @hide
+ */
+ public static final int TAG_SYSTEM_DOWNLOAD = 0xFFFFFF01;
+
+ /**
+ * Default tag value for {@link MediaPlayer} traffic.
+ *
+ * @hide
+ */
+ public static final int TAG_SYSTEM_MEDIA = 0xFFFFFF02;
+
+ /**
+ * Default tag value for {@link BackupManager} backup traffic; that is,
+ * traffic from the device to the storage backend.
+ *
+ * @hide
+ */
+ public static final int TAG_SYSTEM_BACKUP = 0xFFFFFF03;
+
+ /**
+ * Default tag value for {@link BackupManager} restore traffic; that is,
+ * app data retrieved from the storage backend at install time.
+ *
+ * @hide
+ */
+ public static final int TAG_SYSTEM_RESTORE = 0xFFFFFF04;
+
+ /**
+ * Default tag value for code (typically APKs) downloaded by an app store on
+ * behalf of the app, such as updates.
+ *
+ * @hide
+ */
+ public static final int TAG_SYSTEM_APP = 0xFFFFFF05;
+
+ // TODO : remove this constant when Wifi code is updated
+ /** @hide */
+ public static final int TAG_SYSTEM_PROBE = 0xFFFFFF42;
+
+ private static INetworkStatsService sStatsService;
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
+ private synchronized static INetworkStatsService getStatsService() {
+ if (sStatsService == null) {
+ sStatsService = INetworkStatsService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
+ }
+ return sStatsService;
+ }
+
+ /**
+ * Snapshot of {@link NetworkStats} when the currently active profiling
+ * session started, or {@code null} if no session active.
+ *
+ * @see #startDataProfiling(Context)
+ * @see #stopDataProfiling(Context)
+ */
+ private static NetworkStats sActiveProfilingStart;
+
+ private static Object sProfilingLock = new Object();
+
+ private static final String LOOPBACK_IFACE = "lo";
+
+ /**
+ * Set active tag to use when accounting {@link Socket} traffic originating
+ * from the current thread. Only one active tag per thread is supported.
+ * <p>
+ * Changes only take effect during subsequent calls to
+ * {@link #tagSocket(Socket)}.
+ * <p>
+ * Tags between {@code 0xFFFFFF00} and {@code 0xFFFFFFFF} are reserved and
+ * used internally by system services like {@link DownloadManager} when
+ * performing traffic on behalf of an application.
+ *
+ * @see #clearThreadStatsTag()
+ */
+ public static void setThreadStatsTag(int tag) {
+ NetworkManagementSocketTagger.setThreadSocketStatsTag(tag);
+ }
+
+ /**
+ * Set active tag to use when accounting {@link Socket} traffic originating
+ * from the current thread. Only one active tag per thread is supported.
+ * <p>
+ * Changes only take effect during subsequent calls to
+ * {@link #tagSocket(Socket)}.
+ * <p>
+ * Tags between {@code 0xFFFFFF00} and {@code 0xFFFFFFFF} are reserved and
+ * used internally by system services like {@link DownloadManager} when
+ * performing traffic on behalf of an application.
+ *
+ * @return the current tag for the calling thread, which can be used to
+ * restore any existing values after a nested operation is finished
+ */
+ public static int getAndSetThreadStatsTag(int tag) {
+ return NetworkManagementSocketTagger.setThreadSocketStatsTag(tag);
+ }
+
+ /**
+ * Set active tag to use when accounting {@link Socket} traffic originating
+ * from the current thread. The tag used internally is well-defined to
+ * distinguish all backup-related traffic.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static void setThreadStatsTagBackup() {
+ setThreadStatsTag(TAG_SYSTEM_BACKUP);
+ }
+
+ /**
+ * Set active tag to use when accounting {@link Socket} traffic originating
+ * from the current thread. The tag used internally is well-defined to
+ * distinguish all restore-related traffic.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static void setThreadStatsTagRestore() {
+ setThreadStatsTag(TAG_SYSTEM_RESTORE);
+ }
+
+ /**
+ * Set active tag to use when accounting {@link Socket} traffic originating
+ * from the current thread. The tag used internally is well-defined to
+ * distinguish all code (typically APKs) downloaded by an app store on
+ * behalf of the app, such as updates.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static void setThreadStatsTagApp() {
+ setThreadStatsTag(TAG_SYSTEM_APP);
+ }
+
+ /**
+ * Get the active tag used when accounting {@link Socket} traffic originating
+ * from the current thread. Only one active tag per thread is supported.
+ * {@link #tagSocket(Socket)}.
+ *
+ * @see #setThreadStatsTag(int)
+ */
+ public static int getThreadStatsTag() {
+ return NetworkManagementSocketTagger.getThreadSocketStatsTag();
+ }
+
+ /**
+ * Clear any active tag set to account {@link Socket} traffic originating
+ * from the current thread.
+ *
+ * @see #setThreadStatsTag(int)
+ */
+ public static void clearThreadStatsTag() {
+ NetworkManagementSocketTagger.setThreadSocketStatsTag(-1);
+ }
+
+ /**
+ * Set specific UID to use when accounting {@link Socket} traffic
+ * originating from the current thread. Designed for use when performing an
+ * operation on behalf of another application, or when another application
+ * is performing operations on your behalf.
+ * <p>
+ * Any app can <em>accept</em> blame for traffic performed on a socket
+ * originally created by another app by calling this method with the
+ * {@link android.system.Os#getuid()} value. However, only apps holding the
+ * {@code android.Manifest.permission#UPDATE_DEVICE_STATS} permission may
+ * <em>assign</em> blame to another UIDs.
+ * <p>
+ * Changes only take effect during subsequent calls to
+ * {@link #tagSocket(Socket)}.
+ */
+ @SuppressLint("Doclava125")
+ public static void setThreadStatsUid(int uid) {
+ NetworkManagementSocketTagger.setThreadSocketStatsUid(uid);
+ }
+
+ /**
+ * Get the active UID used when accounting {@link Socket} traffic originating
+ * from the current thread. Only one active tag per thread is supported.
+ * {@link #tagSocket(Socket)}.
+ *
+ * @see #setThreadStatsUid(int)
+ */
+ public static int getThreadStatsUid() {
+ return NetworkManagementSocketTagger.getThreadSocketStatsUid();
+ }
+
+ /**
+ * Set specific UID to use when accounting {@link Socket} traffic
+ * originating from the current thread as the calling UID. Designed for use
+ * when another application is performing operations on your behalf.
+ * <p>
+ * Changes only take effect during subsequent calls to
+ * {@link #tagSocket(Socket)}.
+ *
+ * @removed
+ * @deprecated use {@link #setThreadStatsUid(int)} instead.
+ */
+ @Deprecated
+ public static void setThreadStatsUidSelf() {
+ setThreadStatsUid(android.os.Process.myUid());
+ }
+
+ /**
+ * Clear any active UID set to account {@link Socket} traffic originating
+ * from the current thread.
+ *
+ * @see #setThreadStatsUid(int)
+ */
+ @SuppressLint("Doclava125")
+ public static void clearThreadStatsUid() {
+ NetworkManagementSocketTagger.setThreadSocketStatsUid(-1);
+ }
+
+ /**
+ * Tag the given {@link Socket} with any statistics parameters active for
+ * the current thread. Subsequent calls always replace any existing
+ * parameters. When finished, call {@link #untagSocket(Socket)} to remove
+ * statistics parameters.
+ *
+ * @see #setThreadStatsTag(int)
+ */
+ public static void tagSocket(Socket socket) throws SocketException {
+ SocketTagger.get().tag(socket);
+ }
+
+ /**
+ * Remove any statistics parameters from the given {@link Socket}.
+ * <p>
+ * In Android 8.1 (API level 27) and lower, a socket is automatically
+ * untagged when it's sent to another process using binder IPC with a
+ * {@code ParcelFileDescriptor} container. In Android 9.0 (API level 28)
+ * and higher, the socket tag is kept when the socket is sent to another
+ * process using binder IPC. You can mimic the previous behavior by
+ * calling {@code untagSocket()} before sending the socket to another
+ * process.
+ */
+ public static void untagSocket(Socket socket) throws SocketException {
+ SocketTagger.get().untag(socket);
+ }
+
+ /**
+ * Tag the given {@link DatagramSocket} with any statistics parameters
+ * active for the current thread. Subsequent calls always replace any
+ * existing parameters. When finished, call
+ * {@link #untagDatagramSocket(DatagramSocket)} to remove statistics
+ * parameters.
+ *
+ * @see #setThreadStatsTag(int)
+ */
+ public static void tagDatagramSocket(DatagramSocket socket) throws SocketException {
+ SocketTagger.get().tag(socket);
+ }
+
+ /**
+ * Remove any statistics parameters from the given {@link DatagramSocket}.
+ */
+ public static void untagDatagramSocket(DatagramSocket socket) throws SocketException {
+ SocketTagger.get().untag(socket);
+ }
+
+ /**
+ * Tag the given {@link FileDescriptor} socket with any statistics
+ * parameters active for the current thread. Subsequent calls always replace
+ * any existing parameters. When finished, call
+ * {@link #untagFileDescriptor(FileDescriptor)} to remove statistics
+ * parameters.
+ *
+ * @see #setThreadStatsTag(int)
+ */
+ public static void tagFileDescriptor(FileDescriptor fd) throws IOException {
+ SocketTagger.get().tag(fd);
+ }
+
+ /**
+ * Remove any statistics parameters from the given {@link FileDescriptor}
+ * socket.
+ */
+ public static void untagFileDescriptor(FileDescriptor fd) throws IOException {
+ SocketTagger.get().untag(fd);
+ }
+
+ /**
+ * Start profiling data usage for current UID. Only one profiling session
+ * can be active at a time.
+ *
+ * @hide
+ */
+ public static void startDataProfiling(Context context) {
+ synchronized (sProfilingLock) {
+ if (sActiveProfilingStart != null) {
+ throw new IllegalStateException("already profiling data");
+ }
+
+ // take snapshot in time; we calculate delta later
+ sActiveProfilingStart = getDataLayerSnapshotForUid(context);
+ }
+ }
+
+ /**
+ * Stop profiling data usage for current UID.
+ *
+ * @return Detailed {@link NetworkStats} of data that occurred since last
+ * {@link #startDataProfiling(Context)} call.
+ * @hide
+ */
+ public static NetworkStats stopDataProfiling(Context context) {
+ synchronized (sProfilingLock) {
+ if (sActiveProfilingStart == null) {
+ throw new IllegalStateException("not profiling data");
+ }
+
+ // subtract starting values and return delta
+ final NetworkStats profilingStop = getDataLayerSnapshotForUid(context);
+ final NetworkStats profilingDelta = NetworkStats.subtract(
+ profilingStop, sActiveProfilingStart, null, null);
+ sActiveProfilingStart = null;
+ return profilingDelta;
+ }
+ }
+
+ /**
+ * Increment count of network operations performed under the accounting tag
+ * currently active on the calling thread. This can be used to derive
+ * bytes-per-operation.
+ *
+ * @param operationCount Number of operations to increment count by.
+ */
+ public static void incrementOperationCount(int operationCount) {
+ final int tag = getThreadStatsTag();
+ incrementOperationCount(tag, operationCount);
+ }
+
+ /**
+ * Increment count of network operations performed under the given
+ * accounting tag. This can be used to derive bytes-per-operation.
+ *
+ * @param tag Accounting tag used in {@link #setThreadStatsTag(int)}.
+ * @param operationCount Number of operations to increment count by.
+ */
+ public static void incrementOperationCount(int tag, int operationCount) {
+ final int uid = android.os.Process.myUid();
+ try {
+ getStatsService().incrementOperationCount(uid, tag, operationCount);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
+ public static void closeQuietly(INetworkStatsSession session) {
+ // TODO: move to NetworkStatsService once it exists
+ if (session != null) {
+ try {
+ session.close();
+ } catch (RuntimeException rethrown) {
+ throw rethrown;
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ private static long addIfSupported(long stat) {
+ return (stat == UNSUPPORTED) ? 0 : stat;
+ }
+
+ /**
+ * Return number of packets transmitted across mobile networks since device
+ * boot. Counts packets across all mobile network interfaces, and always
+ * increases monotonically since device boot. Statistics are measured at the
+ * network layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
+ */
+ public static long getMobileTxPackets() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ total += addIfSupported(getTxPackets(iface));
+ }
+ return total;
+ }
+
+ /**
+ * Return number of packets received across mobile networks since device
+ * boot. Counts packets across all mobile network interfaces, and always
+ * increases monotonically since device boot. Statistics are measured at the
+ * network layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
+ */
+ public static long getMobileRxPackets() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ total += addIfSupported(getRxPackets(iface));
+ }
+ return total;
+ }
+
+ /**
+ * Return number of bytes transmitted across mobile networks since device
+ * boot. Counts packets across all mobile network interfaces, and always
+ * increases monotonically since device boot. Statistics are measured at the
+ * network layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
+ */
+ public static long getMobileTxBytes() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ total += addIfSupported(getTxBytes(iface));
+ }
+ return total;
+ }
+
+ /**
+ * Return number of bytes received across mobile networks since device boot.
+ * Counts packets across all mobile network interfaces, and always increases
+ * monotonically since device boot. Statistics are measured at the network
+ * layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
+ */
+ public static long getMobileRxBytes() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ total += addIfSupported(getRxBytes(iface));
+ }
+ return total;
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public static long getMobileTcpRxPackets() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ long stat = UNSUPPORTED;
+ try {
+ stat = getStatsService().getIfaceStats(iface, TYPE_TCP_RX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ total += addIfSupported(stat);
+ }
+ return total;
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public static long getMobileTcpTxPackets() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ long stat = UNSUPPORTED;
+ try {
+ stat = getStatsService().getIfaceStats(iface, TYPE_TCP_TX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ total += addIfSupported(stat);
+ }
+ return total;
+ }
+
+ /** {@hide} */
+ public static long getTxPackets(String iface) {
+ try {
+ return getStatsService().getIfaceStats(iface, TYPE_TX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
+ public static long getRxPackets(String iface) {
+ try {
+ return getStatsService().getIfaceStats(iface, TYPE_RX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public static long getTxBytes(String iface) {
+ try {
+ return getStatsService().getIfaceStats(iface, TYPE_TX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public static long getRxBytes(String iface) {
+ try {
+ return getStatsService().getIfaceStats(iface, TYPE_RX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
+ @TestApi
+ public static long getLoopbackTxPackets() {
+ try {
+ return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_TX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
+ @TestApi
+ public static long getLoopbackRxPackets() {
+ try {
+ return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_RX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
+ @TestApi
+ public static long getLoopbackTxBytes() {
+ try {
+ return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_TX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** {@hide} */
+ @TestApi
+ public static long getLoopbackRxBytes() {
+ try {
+ return getStatsService().getIfaceStats(LOOPBACK_IFACE, TYPE_RX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return number of packets transmitted since device boot. Counts packets
+ * across all network interfaces, and always increases monotonically since
+ * device boot. Statistics are measured at the network layer, so they
+ * include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
+ */
+ public static long getTotalTxPackets() {
+ try {
+ return getStatsService().getTotalStats(TYPE_TX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return number of packets received since device boot. Counts packets
+ * across all network interfaces, and always increases monotonically since
+ * device boot. Statistics are measured at the network layer, so they
+ * include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
+ */
+ public static long getTotalRxPackets() {
+ try {
+ return getStatsService().getTotalStats(TYPE_RX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return number of bytes transmitted since device boot. Counts packets
+ * across all network interfaces, and always increases monotonically since
+ * device boot. Statistics are measured at the network layer, so they
+ * include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
+ */
+ public static long getTotalTxBytes() {
+ try {
+ return getStatsService().getTotalStats(TYPE_TX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return number of bytes received since device boot. Counts packets across
+ * all network interfaces, and always increases monotonically since device
+ * boot. Statistics are measured at the network layer, so they include both
+ * TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
+ */
+ public static long getTotalRxBytes() {
+ try {
+ return getStatsService().getTotalStats(TYPE_RX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return number of bytes transmitted by the given UID since device boot.
+ * Counts packets across all network interfaces, and always increases
+ * monotonically since device boot. Statistics are measured at the network
+ * layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#N} this will only
+ * report traffic statistics for the calling UID. It will return
+ * {@link #UNSUPPORTED} for all other UIDs for privacy reasons. To access
+ * historical network statistics belonging to other UIDs, use
+ * {@link NetworkStatsManager}.
+ *
+ * @see android.os.Process#myUid()
+ * @see android.content.pm.ApplicationInfo#uid
+ */
+ public static long getUidTxBytes(int uid) {
+ // This isn't actually enforcing any security; it just returns the
+ // unsupported value. The real filtering is done at the kernel level.
+ final int callingUid = android.os.Process.myUid();
+ if (callingUid == android.os.Process.SYSTEM_UID || callingUid == uid) {
+ try {
+ return getStatsService().getUidStats(uid, TYPE_TX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ return UNSUPPORTED;
+ }
+ }
+
+ /**
+ * Return number of bytes received by the given UID since device boot.
+ * Counts packets across all network interfaces, and always increases
+ * monotonically since device boot. Statistics are measured at the network
+ * layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may return
+ * {@link #UNSUPPORTED} on devices where statistics aren't available.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#N} this will only
+ * report traffic statistics for the calling UID. It will return
+ * {@link #UNSUPPORTED} for all other UIDs for privacy reasons. To access
+ * historical network statistics belonging to other UIDs, use
+ * {@link NetworkStatsManager}.
+ *
+ * @see android.os.Process#myUid()
+ * @see android.content.pm.ApplicationInfo#uid
+ */
+ public static long getUidRxBytes(int uid) {
+ // This isn't actually enforcing any security; it just returns the
+ // unsupported value. The real filtering is done at the kernel level.
+ final int callingUid = android.os.Process.myUid();
+ if (callingUid == android.os.Process.SYSTEM_UID || callingUid == uid) {
+ try {
+ return getStatsService().getUidStats(uid, TYPE_RX_BYTES);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ return UNSUPPORTED;
+ }
+ }
+
+ /**
+ * Return number of packets transmitted by the given UID since device boot.
+ * Counts packets across all network interfaces, and always increases
+ * monotonically since device boot. Statistics are measured at the network
+ * layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may return
+ * {@link #UNSUPPORTED} on devices where statistics aren't available.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#N} this will only
+ * report traffic statistics for the calling UID. It will return
+ * {@link #UNSUPPORTED} for all other UIDs for privacy reasons. To access
+ * historical network statistics belonging to other UIDs, use
+ * {@link NetworkStatsManager}.
+ *
+ * @see android.os.Process#myUid()
+ * @see android.content.pm.ApplicationInfo#uid
+ */
+ public static long getUidTxPackets(int uid) {
+ // This isn't actually enforcing any security; it just returns the
+ // unsupported value. The real filtering is done at the kernel level.
+ final int callingUid = android.os.Process.myUid();
+ if (callingUid == android.os.Process.SYSTEM_UID || callingUid == uid) {
+ try {
+ return getStatsService().getUidStats(uid, TYPE_TX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ return UNSUPPORTED;
+ }
+ }
+
+ /**
+ * Return number of packets received by the given UID since device boot.
+ * Counts packets across all network interfaces, and always increases
+ * monotonically since device boot. Statistics are measured at the network
+ * layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may return
+ * {@link #UNSUPPORTED} on devices where statistics aren't available.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#N} this will only
+ * report traffic statistics for the calling UID. It will return
+ * {@link #UNSUPPORTED} for all other UIDs for privacy reasons. To access
+ * historical network statistics belonging to other UIDs, use
+ * {@link NetworkStatsManager}.
+ *
+ * @see android.os.Process#myUid()
+ * @see android.content.pm.ApplicationInfo#uid
+ */
+ public static long getUidRxPackets(int uid) {
+ // This isn't actually enforcing any security; it just returns the
+ // unsupported value. The real filtering is done at the kernel level.
+ final int callingUid = android.os.Process.myUid();
+ if (callingUid == android.os.Process.SYSTEM_UID || callingUid == uid) {
+ try {
+ return getStatsService().getUidStats(uid, TYPE_RX_PACKETS);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ return UNSUPPORTED;
+ }
+ }
+
+ /**
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidTxBytes(int)
+ */
+ @Deprecated
+ public static long getUidTcpTxBytes(int uid) {
+ return UNSUPPORTED;
+ }
+
+ /**
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidRxBytes(int)
+ */
+ @Deprecated
+ public static long getUidTcpRxBytes(int uid) {
+ return UNSUPPORTED;
+ }
+
+ /**
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidTxBytes(int)
+ */
+ @Deprecated
+ public static long getUidUdpTxBytes(int uid) {
+ return UNSUPPORTED;
+ }
+
+ /**
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidRxBytes(int)
+ */
+ @Deprecated
+ public static long getUidUdpRxBytes(int uid) {
+ return UNSUPPORTED;
+ }
+
+ /**
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidTxPackets(int)
+ */
+ @Deprecated
+ public static long getUidTcpTxSegments(int uid) {
+ return UNSUPPORTED;
+ }
+
+ /**
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidRxPackets(int)
+ */
+ @Deprecated
+ public static long getUidTcpRxSegments(int uid) {
+ return UNSUPPORTED;
+ }
+
+ /**
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidTxPackets(int)
+ */
+ @Deprecated
+ public static long getUidUdpTxPackets(int uid) {
+ return UNSUPPORTED;
+ }
+
+ /**
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidRxPackets(int)
+ */
+ @Deprecated
+ public static long getUidUdpRxPackets(int uid) {
+ return UNSUPPORTED;
+ }
+
+ /**
+ * Return detailed {@link NetworkStats} for the current UID. Requires no
+ * special permission.
+ */
+ private static NetworkStats getDataLayerSnapshotForUid(Context context) {
+ // TODO: take snapshot locally, since proc file is now visible
+ final int uid = android.os.Process.myUid();
+ try {
+ return getStatsService().getDataLayerSnapshotForUid(uid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return set of any ifaces associated with mobile networks since boot.
+ * Interfaces are never removed from this list, so counters should always be
+ * monotonic.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
+ private static String[] getMobileIfaces() {
+ try {
+ return getStatsService().getMobileIfaces();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ // NOTE: keep these in sync with android_net_TrafficStats.cpp
+ private static final int TYPE_RX_BYTES = 0;
+ private static final int TYPE_RX_PACKETS = 1;
+ private static final int TYPE_TX_BYTES = 2;
+ private static final int TYPE_TX_PACKETS = 3;
+ private static final int TYPE_TCP_RX_PACKETS = 4;
+ private static final int TYPE_TCP_TX_PACKETS = 5;
+}
diff --git a/android/net/TransportInfo.java b/android/net/TransportInfo.java
new file mode 100644
index 0000000..b78d3fe
--- /dev/null
+++ b/android/net/TransportInfo.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * A container for transport-specific capabilities which is returned by
+ * {@link NetworkCapabilities#getTransportInfo()}. Specific networks
+ * may provide concrete implementations of this interface.
+ */
+public interface TransportInfo {
+}
diff --git a/android/net/UidRange.java b/android/net/UidRange.java
new file mode 100644
index 0000000..d75c43d
--- /dev/null
+++ b/android/net/UidRange.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2014 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;
+
+import static android.os.UserHandle.PER_USER_RANGE;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Collection;
+
+/**
+ * An inclusive range of UIDs.
+ *
+ * @hide
+ */
+public final class UidRange implements Parcelable {
+ public final int start;
+ public final int stop;
+
+ public UidRange(int startUid, int stopUid) {
+ if (startUid < 0) throw new IllegalArgumentException("Invalid start UID.");
+ if (stopUid < 0) throw new IllegalArgumentException("Invalid stop UID.");
+ if (startUid > stopUid) throw new IllegalArgumentException("Invalid UID range.");
+ start = startUid;
+ stop = stopUid;
+ }
+
+ public static UidRange createForUser(int userId) {
+ return new UidRange(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1);
+ }
+
+ /** Returns the smallest user Id which is contained in this UidRange */
+ public int getStartUser() {
+ return start / PER_USER_RANGE;
+ }
+
+ /** Returns the largest user Id which is contained in this UidRange */
+ public int getEndUser() {
+ return stop / PER_USER_RANGE;
+ }
+
+ public boolean contains(int uid) {
+ return start <= uid && uid <= stop;
+ }
+
+ /**
+ * Returns the count of UIDs in this range.
+ */
+ public int count() {
+ return 1 + stop - start;
+ }
+
+ /**
+ * @return {@code true} if this range contains every UID contained by the {@param other} range.
+ */
+ public boolean containsRange(UidRange other) {
+ return start <= other.start && other.stop <= stop;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + start;
+ result = 31 * result + stop;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o instanceof UidRange) {
+ UidRange other = (UidRange) o;
+ return start == other.start && stop == other.stop;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return start + "-" + stop;
+ }
+
+ // Implement the Parcelable interface
+ // TODO: Consider making this class no longer parcelable, since all users are likely in the
+ // system server.
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(start);
+ dest.writeInt(stop);
+ }
+
+ public static final @android.annotation.NonNull Creator<UidRange> CREATOR =
+ new Creator<UidRange>() {
+ @Override
+ public UidRange createFromParcel(Parcel in) {
+ int start = in.readInt();
+ int stop = in.readInt();
+
+ return new UidRange(start, stop);
+ }
+ @Override
+ public UidRange[] newArray(int size) {
+ return new UidRange[size];
+ }
+ };
+
+ /**
+ * Returns whether any of the UidRange in the collection contains the specified uid
+ *
+ * @param ranges The collection of UidRange to check
+ * @param uid the uid in question
+ * @return {@code true} if the uid is contained within the ranges, {@code false} otherwise
+ *
+ * @see UidRange#contains(int)
+ */
+ public static boolean containsUid(Collection<UidRange> ranges, int uid) {
+ if (ranges == null) return false;
+ for (UidRange range : ranges) {
+ if (range.contains(uid)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/android/net/Uri.java b/android/net/Uri.java
new file mode 100644
index 0000000..c3166e9
--- /dev/null
+++ b/android/net/Uri.java
@@ -0,0 +1,2428 @@
+/*
+ * Copyright (C) 2007 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.UnsupportedAppUsage;
+import android.content.Intent;
+import android.os.Environment;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.StrictMode;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.RandomAccess;
+import java.util.Set;
+
+/**
+ * Immutable URI reference. A URI reference includes a URI and a fragment, the
+ * component of the URI following a '#'. Builds and parses URI references
+ * which conform to
+ * <a href="http://www.faqs.org/rfcs/rfc2396.html">RFC 2396</a>.
+ *
+ * <p>In the interest of performance, this class performs little to no
+ * validation. Behavior is undefined for invalid input. This class is very
+ * forgiving--in the face of invalid input, it will return garbage
+ * rather than throw an exception unless otherwise specified.
+ */
+public abstract class Uri implements Parcelable, Comparable<Uri> {
+
+ /*
+
+ This class aims to do as little up front work as possible. To accomplish
+ that, we vary the implementation depending on what the user passes in.
+ For example, we have one implementation if the user passes in a
+ URI string (StringUri) and another if the user passes in the
+ individual components (OpaqueUri).
+
+ *Concurrency notes*: Like any truly immutable object, this class is safe
+ for concurrent use. This class uses a caching pattern in some places where
+ it doesn't use volatile or synchronized. This is safe to do with ints
+ because getting or setting an int is atomic. It's safe to do with a String
+ because the internal fields are final and the memory model guarantees other
+ threads won't see a partially initialized instance. We are not guaranteed
+ that some threads will immediately see changes from other threads on
+ certain platforms, but we don't mind if those threads reconstruct the
+ cached result. As a result, we get thread safe caching with no concurrency
+ overhead, which means the most common case, access from a single thread,
+ is as fast as possible.
+
+ From the Java Language spec.:
+
+ "17.5 Final Field Semantics
+
+ ... when the object is seen by another thread, that thread will always
+ see the correctly constructed version of that object's final fields.
+ It will also see versions of any object or array referenced by
+ those final fields that are at least as up-to-date as the final fields
+ are."
+
+ In that same vein, all non-transient fields within Uri
+ implementations should be final and immutable so as to ensure true
+ immutability for clients even when they don't use proper concurrency
+ control.
+
+ For reference, from RFC 2396:
+
+ "4.3. Parsing a URI Reference
+
+ A URI reference is typically parsed according to the four main
+ components and fragment identifier in order to determine what
+ components are present and whether the reference is relative or
+ absolute. The individual components are then parsed for their
+ subparts and, if not opaque, to verify their validity.
+
+ Although the BNF defines what is allowed in each component, it is
+ ambiguous in terms of differentiating between an authority component
+ and a path component that begins with two slash characters. The
+ greedy algorithm is used for disambiguation: the left-most matching
+ rule soaks up as much of the URI reference string as it is capable of
+ matching. In other words, the authority component wins."
+
+ The "four main components" of a hierarchical URI consist of
+ <scheme>://<authority><path>?<query>
+
+ */
+
+ /** Log tag. */
+ private static final String LOG = Uri.class.getSimpleName();
+
+ /**
+ * NOTE: EMPTY accesses this field during its own initialization, so this
+ * field *must* be initialized first, or else EMPTY will see a null value!
+ *
+ * Placeholder for strings which haven't been cached. This enables us
+ * to cache null. We intentionally create a new String instance so we can
+ * compare its identity and there is no chance we will confuse it with
+ * user data.
+ */
+ @SuppressWarnings("RedundantStringConstructorCall")
+ private static final String NOT_CACHED = new String("NOT CACHED");
+
+ /**
+ * The empty URI, equivalent to "".
+ */
+ public static final Uri EMPTY = new HierarchicalUri(null, Part.NULL,
+ PathPart.EMPTY, Part.NULL, Part.NULL);
+
+ /**
+ * Prevents external subclassing.
+ */
+ @UnsupportedAppUsage
+ private Uri() {}
+
+ /**
+ * Returns true if this URI is hierarchical like "http://google.com".
+ * Absolute URIs are hierarchical if the scheme-specific part starts with
+ * a '/'. Relative URIs are always hierarchical.
+ */
+ public abstract boolean isHierarchical();
+
+ /**
+ * Returns true if this URI is opaque like "mailto:[email protected]". The
+ * scheme-specific part of an opaque URI cannot start with a '/'.
+ */
+ public boolean isOpaque() {
+ return !isHierarchical();
+ }
+
+ /**
+ * Returns true if this URI is relative, i.e. if it doesn't contain an
+ * explicit scheme.
+ *
+ * @return true if this URI is relative, false if it's absolute
+ */
+ public abstract boolean isRelative();
+
+ /**
+ * Returns true if this URI is absolute, i.e. if it contains an
+ * explicit scheme.
+ *
+ * @return true if this URI is absolute, false if it's relative
+ */
+ public boolean isAbsolute() {
+ return !isRelative();
+ }
+
+ /**
+ * Gets the scheme of this URI. Example: "http"
+ *
+ * @return the scheme or null if this is a relative URI
+ */
+ @Nullable
+ public abstract String getScheme();
+
+ /**
+ * Gets the scheme-specific part of this URI, i.e. everything between
+ * the scheme separator ':' and the fragment separator '#'. If this is a
+ * relative URI, this method returns the entire URI. Decodes escaped octets.
+ *
+ * <p>Example: "//www.google.com/search?q=android"
+ *
+ * @return the decoded scheme-specific-part
+ */
+ public abstract String getSchemeSpecificPart();
+
+ /**
+ * Gets the scheme-specific part of this URI, i.e. everything between
+ * the scheme separator ':' and the fragment separator '#'. If this is a
+ * relative URI, this method returns the entire URI. Leaves escaped octets
+ * intact.
+ *
+ * <p>Example: "//www.google.com/search?q=android"
+ *
+ * @return the encoded scheme-specific-part
+ */
+ public abstract String getEncodedSchemeSpecificPart();
+
+ /**
+ * Gets the decoded authority part of this URI. For
+ * server addresses, the authority is structured as follows:
+ * {@code [ userinfo '@' ] host [ ':' port ]}
+ *
+ * <p>Examples: "google.com", "[email protected]:80"
+ *
+ * @return the authority for this URI or null if not present
+ */
+ @Nullable
+ public abstract String getAuthority();
+
+ /**
+ * Gets the encoded authority part of this URI. For
+ * server addresses, the authority is structured as follows:
+ * {@code [ userinfo '@' ] host [ ':' port ]}
+ *
+ * <p>Examples: "google.com", "[email protected]:80"
+ *
+ * @return the authority for this URI or null if not present
+ */
+ @Nullable
+ public abstract String getEncodedAuthority();
+
+ /**
+ * Gets the decoded user information from the authority.
+ * For example, if the authority is "[email protected]", this method will
+ * return "nobody".
+ *
+ * @return the user info for this URI or null if not present
+ */
+ @Nullable
+ public abstract String getUserInfo();
+
+ /**
+ * Gets the encoded user information from the authority.
+ * For example, if the authority is "[email protected]", this method will
+ * return "nobody".
+ *
+ * @return the user info for this URI or null if not present
+ */
+ @Nullable
+ public abstract String getEncodedUserInfo();
+
+ /**
+ * Gets the encoded host from the authority for this URI. For example,
+ * if the authority is "[email protected]", this method will return
+ * "google.com".
+ *
+ * @return the host for this URI or null if not present
+ */
+ @Nullable
+ public abstract String getHost();
+
+ /**
+ * Gets the port from the authority for this URI. For example,
+ * if the authority is "google.com:80", this method will return 80.
+ *
+ * @return the port for this URI or -1 if invalid or not present
+ */
+ public abstract int getPort();
+
+ /**
+ * Gets the decoded path.
+ *
+ * @return the decoded path, or null if this is not a hierarchical URI
+ * (like "mailto:[email protected]") or the URI is invalid
+ */
+ @Nullable
+ public abstract String getPath();
+
+ /**
+ * Gets the encoded path.
+ *
+ * @return the encoded path, or null if this is not a hierarchical URI
+ * (like "mailto:[email protected]") or the URI is invalid
+ */
+ @Nullable
+ public abstract String getEncodedPath();
+
+ /**
+ * Gets the decoded query component from this URI. The query comes after
+ * the query separator ('?') and before the fragment separator ('#'). This
+ * method would return "q=android" for
+ * "http://www.google.com/search?q=android".
+ *
+ * @return the decoded query or null if there isn't one
+ */
+ @Nullable
+ public abstract String getQuery();
+
+ /**
+ * Gets the encoded query component from this URI. The query comes after
+ * the query separator ('?') and before the fragment separator ('#'). This
+ * method would return "q=android" for
+ * "http://www.google.com/search?q=android".
+ *
+ * @return the encoded query or null if there isn't one
+ */
+ @Nullable
+ public abstract String getEncodedQuery();
+
+ /**
+ * Gets the decoded fragment part of this URI, everything after the '#'.
+ *
+ * @return the decoded fragment or null if there isn't one
+ */
+ @Nullable
+ public abstract String getFragment();
+
+ /**
+ * Gets the encoded fragment part of this URI, everything after the '#'.
+ *
+ * @return the encoded fragment or null if there isn't one
+ */
+ @Nullable
+ public abstract String getEncodedFragment();
+
+ /**
+ * Gets the decoded path segments.
+ *
+ * @return decoded path segments, each without a leading or trailing '/'
+ */
+ public abstract List<String> getPathSegments();
+
+ /**
+ * Gets the decoded last segment in the path.
+ *
+ * @return the decoded last segment or null if the path is empty
+ */
+ @Nullable
+ public abstract String getLastPathSegment();
+
+ /**
+ * Compares this Uri to another object for equality. Returns true if the
+ * encoded string representations of this Uri and the given Uri are
+ * equal. Case counts. Paths are not normalized. If one Uri specifies a
+ * default port explicitly and the other leaves it implicit, they will not
+ * be considered equal.
+ */
+ public boolean equals(Object o) {
+ if (!(o instanceof Uri)) {
+ return false;
+ }
+
+ Uri other = (Uri) o;
+
+ return toString().equals(other.toString());
+ }
+
+ /**
+ * Hashes the encoded string represention of this Uri consistently with
+ * {@link #equals(Object)}.
+ */
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+ /**
+ * Compares the string representation of this Uri with that of
+ * another.
+ */
+ public int compareTo(Uri other) {
+ return toString().compareTo(other.toString());
+ }
+
+ /**
+ * Returns the encoded string representation of this URI.
+ * Example: "http://google.com/"
+ */
+ public abstract String toString();
+
+ /**
+ * Return a string representation of this URI that has common forms of PII redacted,
+ * making it safer to use for logging purposes. For example, {@code tel:800-466-4411} is
+ * returned as {@code tel:xxx-xxx-xxxx} and {@code http://example.com/path/to/item/} is
+ * returned as {@code http://example.com/...}.
+ * @return the common forms PII redacted string of this URI
+ * @hide
+ */
+ @SystemApi
+ public @NonNull String toSafeString() {
+ String scheme = getScheme();
+ String ssp = getSchemeSpecificPart();
+ if (scheme != null) {
+ if (scheme.equalsIgnoreCase("tel") || scheme.equalsIgnoreCase("sip")
+ || scheme.equalsIgnoreCase("sms") || scheme.equalsIgnoreCase("smsto")
+ || scheme.equalsIgnoreCase("mailto") || scheme.equalsIgnoreCase("nfc")) {
+ StringBuilder builder = new StringBuilder(64);
+ builder.append(scheme);
+ builder.append(':');
+ if (ssp != null) {
+ for (int i=0; i<ssp.length(); i++) {
+ char c = ssp.charAt(i);
+ if (c == '-' || c == '@' || c == '.') {
+ builder.append(c);
+ } else {
+ builder.append('x');
+ }
+ }
+ }
+ return builder.toString();
+ } else if (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")
+ || scheme.equalsIgnoreCase("ftp") || scheme.equalsIgnoreCase("rtsp")) {
+ ssp = "//" + ((getHost() != null) ? getHost() : "")
+ + ((getPort() != -1) ? (":" + getPort()) : "")
+ + "/...";
+ }
+ }
+ // Not a sensitive scheme, but let's still be conservative about
+ // the data we include -- only the ssp, not the query params or
+ // fragment, because those can often have sensitive info.
+ StringBuilder builder = new StringBuilder(64);
+ if (scheme != null) {
+ builder.append(scheme);
+ builder.append(':');
+ }
+ if (ssp != null) {
+ builder.append(ssp);
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Constructs a new builder, copying the attributes from this Uri.
+ */
+ public abstract Builder buildUpon();
+
+ /** Index of a component which was not found. */
+ private final static int NOT_FOUND = -1;
+
+ /** Placeholder value for an index which hasn't been calculated yet. */
+ private final static int NOT_CALCULATED = -2;
+
+ /**
+ * Error message presented when a user tries to treat an opaque URI as
+ * hierarchical.
+ */
+ private static final String NOT_HIERARCHICAL
+ = "This isn't a hierarchical URI.";
+
+ /** Default encoding. */
+ private static final String DEFAULT_ENCODING = "UTF-8";
+
+ /**
+ * Creates a Uri which parses the given encoded URI string.
+ *
+ * @param uriString an RFC 2396-compliant, encoded URI
+ * @throws NullPointerException if uriString is null
+ * @return Uri for this given uri string
+ */
+ public static Uri parse(String uriString) {
+ return new StringUri(uriString);
+ }
+
+ /**
+ * Creates a Uri from a file. The URI has the form
+ * "file://<absolute path>". Encodes path characters with the exception of
+ * '/'.
+ *
+ * <p>Example: "file:///tmp/android.txt"
+ *
+ * @throws NullPointerException if file is null
+ * @return a Uri for the given file
+ */
+ public static Uri fromFile(File file) {
+ if (file == null) {
+ throw new NullPointerException("file");
+ }
+
+ PathPart path = PathPart.fromDecoded(file.getAbsolutePath());
+ return new HierarchicalUri(
+ "file", Part.EMPTY, path, Part.NULL, Part.NULL);
+ }
+
+ /**
+ * An implementation which wraps a String URI. This URI can be opaque or
+ * hierarchical, but we extend AbstractHierarchicalUri in case we need
+ * the hierarchical functionality.
+ */
+ private static class StringUri extends AbstractHierarchicalUri {
+
+ /** Used in parcelling. */
+ static final int TYPE_ID = 1;
+
+ /** URI string representation. */
+ private final String uriString;
+
+ private StringUri(String uriString) {
+ if (uriString == null) {
+ throw new NullPointerException("uriString");
+ }
+
+ this.uriString = uriString;
+ }
+
+ static Uri readFrom(Parcel parcel) {
+ return new StringUri(parcel.readString());
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(TYPE_ID);
+ parcel.writeString(uriString);
+ }
+
+ /** Cached scheme separator index. */
+ private volatile int cachedSsi = NOT_CALCULATED;
+
+ /** Finds the first ':'. Returns -1 if none found. */
+ private int findSchemeSeparator() {
+ return cachedSsi == NOT_CALCULATED
+ ? cachedSsi = uriString.indexOf(':')
+ : cachedSsi;
+ }
+
+ /** Cached fragment separator index. */
+ private volatile int cachedFsi = NOT_CALCULATED;
+
+ /** Finds the first '#'. Returns -1 if none found. */
+ private int findFragmentSeparator() {
+ return cachedFsi == NOT_CALCULATED
+ ? cachedFsi = uriString.indexOf('#', findSchemeSeparator())
+ : cachedFsi;
+ }
+
+ public boolean isHierarchical() {
+ int ssi = findSchemeSeparator();
+
+ if (ssi == NOT_FOUND) {
+ // All relative URIs are hierarchical.
+ return true;
+ }
+
+ if (uriString.length() == ssi + 1) {
+ // No ssp.
+ return false;
+ }
+
+ // If the ssp starts with a '/', this is hierarchical.
+ return uriString.charAt(ssi + 1) == '/';
+ }
+
+ public boolean isRelative() {
+ // Note: We return true if the index is 0
+ return findSchemeSeparator() == NOT_FOUND;
+ }
+
+ private volatile String scheme = NOT_CACHED;
+
+ public String getScheme() {
+ @SuppressWarnings("StringEquality")
+ boolean cached = (scheme != NOT_CACHED);
+ return cached ? scheme : (scheme = parseScheme());
+ }
+
+ private String parseScheme() {
+ int ssi = findSchemeSeparator();
+ return ssi == NOT_FOUND ? null : uriString.substring(0, ssi);
+ }
+
+ private Part ssp;
+
+ private Part getSsp() {
+ return ssp == null ? ssp = Part.fromEncoded(parseSsp()) : ssp;
+ }
+
+ public String getEncodedSchemeSpecificPart() {
+ return getSsp().getEncoded();
+ }
+
+ public String getSchemeSpecificPart() {
+ return getSsp().getDecoded();
+ }
+
+ private String parseSsp() {
+ int ssi = findSchemeSeparator();
+ int fsi = findFragmentSeparator();
+
+ // Return everything between ssi and fsi.
+ return fsi == NOT_FOUND
+ ? uriString.substring(ssi + 1)
+ : uriString.substring(ssi + 1, fsi);
+ }
+
+ private Part authority;
+
+ private Part getAuthorityPart() {
+ if (authority == null) {
+ String encodedAuthority
+ = parseAuthority(this.uriString, findSchemeSeparator());
+ return authority = Part.fromEncoded(encodedAuthority);
+ }
+
+ return authority;
+ }
+
+ public String getEncodedAuthority() {
+ return getAuthorityPart().getEncoded();
+ }
+
+ public String getAuthority() {
+ return getAuthorityPart().getDecoded();
+ }
+
+ private PathPart path;
+
+ private PathPart getPathPart() {
+ return path == null
+ ? path = PathPart.fromEncoded(parsePath())
+ : path;
+ }
+
+ public String getPath() {
+ return getPathPart().getDecoded();
+ }
+
+ public String getEncodedPath() {
+ return getPathPart().getEncoded();
+ }
+
+ public List<String> getPathSegments() {
+ return getPathPart().getPathSegments();
+ }
+
+ private String parsePath() {
+ String uriString = this.uriString;
+ int ssi = findSchemeSeparator();
+
+ // If the URI is absolute.
+ if (ssi > -1) {
+ // Is there anything after the ':'?
+ boolean schemeOnly = ssi + 1 == uriString.length();
+ if (schemeOnly) {
+ // Opaque URI.
+ return null;
+ }
+
+ // A '/' after the ':' means this is hierarchical.
+ if (uriString.charAt(ssi + 1) != '/') {
+ // Opaque URI.
+ return null;
+ }
+ } else {
+ // All relative URIs are hierarchical.
+ }
+
+ return parsePath(uriString, ssi);
+ }
+
+ private Part query;
+
+ private Part getQueryPart() {
+ return query == null
+ ? query = Part.fromEncoded(parseQuery()) : query;
+ }
+
+ public String getEncodedQuery() {
+ return getQueryPart().getEncoded();
+ }
+
+ private String parseQuery() {
+ // It doesn't make sense to cache this index. We only ever
+ // calculate it once.
+ int qsi = uriString.indexOf('?', findSchemeSeparator());
+ if (qsi == NOT_FOUND) {
+ return null;
+ }
+
+ int fsi = findFragmentSeparator();
+
+ if (fsi == NOT_FOUND) {
+ return uriString.substring(qsi + 1);
+ }
+
+ if (fsi < qsi) {
+ // Invalid.
+ return null;
+ }
+
+ return uriString.substring(qsi + 1, fsi);
+ }
+
+ public String getQuery() {
+ return getQueryPart().getDecoded();
+ }
+
+ private Part fragment;
+
+ private Part getFragmentPart() {
+ return fragment == null
+ ? fragment = Part.fromEncoded(parseFragment()) : fragment;
+ }
+
+ public String getEncodedFragment() {
+ return getFragmentPart().getEncoded();
+ }
+
+ private String parseFragment() {
+ int fsi = findFragmentSeparator();
+ return fsi == NOT_FOUND ? null : uriString.substring(fsi + 1);
+ }
+
+ public String getFragment() {
+ return getFragmentPart().getDecoded();
+ }
+
+ public String toString() {
+ return uriString;
+ }
+
+ /**
+ * Parses an authority out of the given URI string.
+ *
+ * @param uriString URI string
+ * @param ssi scheme separator index, -1 for a relative URI
+ *
+ * @return the authority or null if none is found
+ */
+ static String parseAuthority(String uriString, int ssi) {
+ int length = uriString.length();
+
+ // If "//" follows the scheme separator, we have an authority.
+ if (length > ssi + 2
+ && uriString.charAt(ssi + 1) == '/'
+ && uriString.charAt(ssi + 2) == '/') {
+ // We have an authority.
+
+ // Look for the start of the path, query, or fragment, or the
+ // end of the string.
+ int end = ssi + 3;
+ LOOP: while (end < length) {
+ switch (uriString.charAt(end)) {
+ case '/': // Start of path
+ case '\\':// Start of path
+ // Per http://url.spec.whatwg.org/#host-state, the \ character
+ // is treated as if it were a / character when encountered in a
+ // host
+ case '?': // Start of query
+ case '#': // Start of fragment
+ break LOOP;
+ }
+ end++;
+ }
+
+ return uriString.substring(ssi + 3, end);
+ } else {
+ return null;
+ }
+
+ }
+
+ /**
+ * Parses a path out of this given URI string.
+ *
+ * @param uriString URI string
+ * @param ssi scheme separator index, -1 for a relative URI
+ *
+ * @return the path
+ */
+ static String parsePath(String uriString, int ssi) {
+ int length = uriString.length();
+
+ // Find start of path.
+ int pathStart;
+ if (length > ssi + 2
+ && uriString.charAt(ssi + 1) == '/'
+ && uriString.charAt(ssi + 2) == '/') {
+ // Skip over authority to path.
+ pathStart = ssi + 3;
+ LOOP: while (pathStart < length) {
+ switch (uriString.charAt(pathStart)) {
+ case '?': // Start of query
+ case '#': // Start of fragment
+ return ""; // Empty path.
+ case '/': // Start of path!
+ case '\\':// Start of path!
+ // Per http://url.spec.whatwg.org/#host-state, the \ character
+ // is treated as if it were a / character when encountered in a
+ // host
+ break LOOP;
+ }
+ pathStart++;
+ }
+ } else {
+ // Path starts immediately after scheme separator.
+ pathStart = ssi + 1;
+ }
+
+ // Find end of path.
+ int pathEnd = pathStart;
+ LOOP: while (pathEnd < length) {
+ switch (uriString.charAt(pathEnd)) {
+ case '?': // Start of query
+ case '#': // Start of fragment
+ break LOOP;
+ }
+ pathEnd++;
+ }
+
+ return uriString.substring(pathStart, pathEnd);
+ }
+
+ public Builder buildUpon() {
+ if (isHierarchical()) {
+ return new Builder()
+ .scheme(getScheme())
+ .authority(getAuthorityPart())
+ .path(getPathPart())
+ .query(getQueryPart())
+ .fragment(getFragmentPart());
+ } else {
+ return new Builder()
+ .scheme(getScheme())
+ .opaquePart(getSsp())
+ .fragment(getFragmentPart());
+ }
+ }
+ }
+
+ /**
+ * Creates an opaque Uri from the given components. Encodes the ssp
+ * which means this method cannot be used to create hierarchical URIs.
+ *
+ * @param scheme of the URI
+ * @param ssp scheme-specific-part, everything between the
+ * scheme separator (':') and the fragment separator ('#'), which will
+ * get encoded
+ * @param fragment fragment, everything after the '#', null if undefined,
+ * will get encoded
+ *
+ * @throws NullPointerException if scheme or ssp is null
+ * @return Uri composed of the given scheme, ssp, and fragment
+ *
+ * @see Builder if you don't want the ssp and fragment to be encoded
+ */
+ public static Uri fromParts(String scheme, String ssp,
+ String fragment) {
+ if (scheme == null) {
+ throw new NullPointerException("scheme");
+ }
+ if (ssp == null) {
+ throw new NullPointerException("ssp");
+ }
+
+ return new OpaqueUri(scheme, Part.fromDecoded(ssp),
+ Part.fromDecoded(fragment));
+ }
+
+ /**
+ * Opaque URI.
+ */
+ private static class OpaqueUri extends Uri {
+
+ /** Used in parcelling. */
+ static final int TYPE_ID = 2;
+
+ private final String scheme;
+ private final Part ssp;
+ private final Part fragment;
+
+ private OpaqueUri(String scheme, Part ssp, Part fragment) {
+ this.scheme = scheme;
+ this.ssp = ssp;
+ this.fragment = fragment == null ? Part.NULL : fragment;
+ }
+
+ static Uri readFrom(Parcel parcel) {
+ return new OpaqueUri(
+ parcel.readString(),
+ Part.readFrom(parcel),
+ Part.readFrom(parcel)
+ );
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(TYPE_ID);
+ parcel.writeString(scheme);
+ ssp.writeTo(parcel);
+ fragment.writeTo(parcel);
+ }
+
+ public boolean isHierarchical() {
+ return false;
+ }
+
+ public boolean isRelative() {
+ return scheme == null;
+ }
+
+ public String getScheme() {
+ return this.scheme;
+ }
+
+ public String getEncodedSchemeSpecificPart() {
+ return ssp.getEncoded();
+ }
+
+ public String getSchemeSpecificPart() {
+ return ssp.getDecoded();
+ }
+
+ public String getAuthority() {
+ return null;
+ }
+
+ public String getEncodedAuthority() {
+ return null;
+ }
+
+ public String getPath() {
+ return null;
+ }
+
+ public String getEncodedPath() {
+ return null;
+ }
+
+ public String getQuery() {
+ return null;
+ }
+
+ public String getEncodedQuery() {
+ return null;
+ }
+
+ public String getFragment() {
+ return fragment.getDecoded();
+ }
+
+ public String getEncodedFragment() {
+ return fragment.getEncoded();
+ }
+
+ public List<String> getPathSegments() {
+ return Collections.emptyList();
+ }
+
+ public String getLastPathSegment() {
+ return null;
+ }
+
+ public String getUserInfo() {
+ return null;
+ }
+
+ public String getEncodedUserInfo() {
+ return null;
+ }
+
+ public String getHost() {
+ return null;
+ }
+
+ public int getPort() {
+ return -1;
+ }
+
+ private volatile String cachedString = NOT_CACHED;
+
+ public String toString() {
+ @SuppressWarnings("StringEquality")
+ boolean cached = cachedString != NOT_CACHED;
+ if (cached) {
+ return cachedString;
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ sb.append(scheme).append(':');
+ sb.append(getEncodedSchemeSpecificPart());
+
+ if (!fragment.isEmpty()) {
+ sb.append('#').append(fragment.getEncoded());
+ }
+
+ return cachedString = sb.toString();
+ }
+
+ public Builder buildUpon() {
+ return new Builder()
+ .scheme(this.scheme)
+ .opaquePart(this.ssp)
+ .fragment(this.fragment);
+ }
+ }
+
+ /**
+ * Wrapper for path segment array.
+ */
+ static class PathSegments extends AbstractList<String>
+ implements RandomAccess {
+
+ static final PathSegments EMPTY = new PathSegments(null, 0);
+
+ final String[] segments;
+ final int size;
+
+ PathSegments(String[] segments, int size) {
+ this.segments = segments;
+ this.size = size;
+ }
+
+ public String get(int index) {
+ if (index >= size) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ return segments[index];
+ }
+
+ public int size() {
+ return this.size;
+ }
+ }
+
+ /**
+ * Builds PathSegments.
+ */
+ static class PathSegmentsBuilder {
+
+ String[] segments;
+ int size = 0;
+
+ void add(String segment) {
+ if (segments == null) {
+ segments = new String[4];
+ } else if (size + 1 == segments.length) {
+ String[] expanded = new String[segments.length * 2];
+ System.arraycopy(segments, 0, expanded, 0, segments.length);
+ segments = expanded;
+ }
+
+ segments[size++] = segment;
+ }
+
+ PathSegments build() {
+ if (segments == null) {
+ return PathSegments.EMPTY;
+ }
+
+ try {
+ return new PathSegments(segments, size);
+ } finally {
+ // Makes sure this doesn't get reused.
+ segments = null;
+ }
+ }
+ }
+
+ /**
+ * Support for hierarchical URIs.
+ */
+ private abstract static class AbstractHierarchicalUri extends Uri {
+
+ public String getLastPathSegment() {
+ // TODO: If we haven't parsed all of the segments already, just
+ // grab the last one directly so we only allocate one string.
+
+ List<String> segments = getPathSegments();
+ int size = segments.size();
+ if (size == 0) {
+ return null;
+ }
+ return segments.get(size - 1);
+ }
+
+ private Part userInfo;
+
+ private Part getUserInfoPart() {
+ return userInfo == null
+ ? userInfo = Part.fromEncoded(parseUserInfo()) : userInfo;
+ }
+
+ public final String getEncodedUserInfo() {
+ return getUserInfoPart().getEncoded();
+ }
+
+ private String parseUserInfo() {
+ String authority = getEncodedAuthority();
+ if (authority == null) {
+ return null;
+ }
+
+ int end = authority.lastIndexOf('@');
+ return end == NOT_FOUND ? null : authority.substring(0, end);
+ }
+
+ public String getUserInfo() {
+ return getUserInfoPart().getDecoded();
+ }
+
+ private volatile String host = NOT_CACHED;
+
+ public String getHost() {
+ @SuppressWarnings("StringEquality")
+ boolean cached = (host != NOT_CACHED);
+ return cached ? host : (host = parseHost());
+ }
+
+ private String parseHost() {
+ final String authority = getEncodedAuthority();
+ if (authority == null) {
+ return null;
+ }
+
+ // Parse out user info and then port.
+ int userInfoSeparator = authority.lastIndexOf('@');
+ int portSeparator = findPortSeparator(authority);
+
+ String encodedHost = portSeparator == NOT_FOUND
+ ? authority.substring(userInfoSeparator + 1)
+ : authority.substring(userInfoSeparator + 1, portSeparator);
+
+ return decode(encodedHost);
+ }
+
+ private volatile int port = NOT_CALCULATED;
+
+ public int getPort() {
+ return port == NOT_CALCULATED
+ ? port = parsePort()
+ : port;
+ }
+
+ private int parsePort() {
+ final String authority = getEncodedAuthority();
+ int portSeparator = findPortSeparator(authority);
+ if (portSeparator == NOT_FOUND) {
+ return -1;
+ }
+
+ String portString = decode(authority.substring(portSeparator + 1));
+ try {
+ return Integer.parseInt(portString);
+ } catch (NumberFormatException e) {
+ Log.w(LOG, "Error parsing port string.", e);
+ return -1;
+ }
+ }
+
+ private int findPortSeparator(String authority) {
+ if (authority == null) {
+ return NOT_FOUND;
+ }
+
+ // Reverse search for the ':' character that breaks as soon as a char that is neither
+ // a colon nor an ascii digit is encountered. Thanks to the goodness of UTF-16 encoding,
+ // it's not possible that a surrogate matches one of these, so this loop can just
+ // look for characters rather than care about code points.
+ for (int i = authority.length() - 1; i >= 0; --i) {
+ final int character = authority.charAt(i);
+ if (':' == character) return i;
+ // Character.isDigit would include non-ascii digits
+ if (character < '0' || character > '9') return NOT_FOUND;
+ }
+ return NOT_FOUND;
+ }
+ }
+
+ /**
+ * Hierarchical Uri.
+ */
+ private static class HierarchicalUri extends AbstractHierarchicalUri {
+
+ /** Used in parcelling. */
+ static final int TYPE_ID = 3;
+
+ private final String scheme; // can be null
+ private final Part authority;
+ private final PathPart path;
+ private final Part query;
+ private final Part fragment;
+
+ private HierarchicalUri(String scheme, Part authority, PathPart path,
+ Part query, Part fragment) {
+ this.scheme = scheme;
+ this.authority = Part.nonNull(authority);
+ this.path = path == null ? PathPart.NULL : path;
+ this.query = Part.nonNull(query);
+ this.fragment = Part.nonNull(fragment);
+ }
+
+ static Uri readFrom(Parcel parcel) {
+ return new HierarchicalUri(
+ parcel.readString(),
+ Part.readFrom(parcel),
+ PathPart.readFrom(parcel),
+ Part.readFrom(parcel),
+ Part.readFrom(parcel)
+ );
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(TYPE_ID);
+ parcel.writeString(scheme);
+ authority.writeTo(parcel);
+ path.writeTo(parcel);
+ query.writeTo(parcel);
+ fragment.writeTo(parcel);
+ }
+
+ public boolean isHierarchical() {
+ return true;
+ }
+
+ public boolean isRelative() {
+ return scheme == null;
+ }
+
+ public String getScheme() {
+ return scheme;
+ }
+
+ private Part ssp;
+
+ private Part getSsp() {
+ return ssp == null
+ ? ssp = Part.fromEncoded(makeSchemeSpecificPart()) : ssp;
+ }
+
+ public String getEncodedSchemeSpecificPart() {
+ return getSsp().getEncoded();
+ }
+
+ public String getSchemeSpecificPart() {
+ return getSsp().getDecoded();
+ }
+
+ /**
+ * Creates the encoded scheme-specific part from its sub parts.
+ */
+ private String makeSchemeSpecificPart() {
+ StringBuilder builder = new StringBuilder();
+ appendSspTo(builder);
+ return builder.toString();
+ }
+
+ private void appendSspTo(StringBuilder builder) {
+ String encodedAuthority = authority.getEncoded();
+ if (encodedAuthority != null) {
+ // Even if the authority is "", we still want to append "//".
+ builder.append("//").append(encodedAuthority);
+ }
+
+ String encodedPath = path.getEncoded();
+ if (encodedPath != null) {
+ builder.append(encodedPath);
+ }
+
+ if (!query.isEmpty()) {
+ builder.append('?').append(query.getEncoded());
+ }
+ }
+
+ public String getAuthority() {
+ return this.authority.getDecoded();
+ }
+
+ public String getEncodedAuthority() {
+ return this.authority.getEncoded();
+ }
+
+ public String getEncodedPath() {
+ return this.path.getEncoded();
+ }
+
+ public String getPath() {
+ return this.path.getDecoded();
+ }
+
+ public String getQuery() {
+ return this.query.getDecoded();
+ }
+
+ public String getEncodedQuery() {
+ return this.query.getEncoded();
+ }
+
+ public String getFragment() {
+ return this.fragment.getDecoded();
+ }
+
+ public String getEncodedFragment() {
+ return this.fragment.getEncoded();
+ }
+
+ public List<String> getPathSegments() {
+ return this.path.getPathSegments();
+ }
+
+ private volatile String uriString = NOT_CACHED;
+
+ @Override
+ public String toString() {
+ @SuppressWarnings("StringEquality")
+ boolean cached = (uriString != NOT_CACHED);
+ return cached ? uriString
+ : (uriString = makeUriString());
+ }
+
+ private String makeUriString() {
+ StringBuilder builder = new StringBuilder();
+
+ if (scheme != null) {
+ builder.append(scheme).append(':');
+ }
+
+ appendSspTo(builder);
+
+ if (!fragment.isEmpty()) {
+ builder.append('#').append(fragment.getEncoded());
+ }
+
+ return builder.toString();
+ }
+
+ public Builder buildUpon() {
+ return new Builder()
+ .scheme(scheme)
+ .authority(authority)
+ .path(path)
+ .query(query)
+ .fragment(fragment);
+ }
+ }
+
+ /**
+ * Helper class for building or manipulating URI references. Not safe for
+ * concurrent use.
+ *
+ * <p>An absolute hierarchical URI reference follows the pattern:
+ * {@code <scheme>://<authority><absolute path>?<query>#<fragment>}
+ *
+ * <p>Relative URI references (which are always hierarchical) follow one
+ * of two patterns: {@code <relative or absolute path>?<query>#<fragment>}
+ * or {@code //<authority><absolute path>?<query>#<fragment>}
+ *
+ * <p>An opaque URI follows this pattern:
+ * {@code <scheme>:<opaque part>#<fragment>}
+ *
+ * <p>Use {@link Uri#buildUpon()} to obtain a builder representing an existing URI.
+ */
+ public static final class Builder {
+
+ private String scheme;
+ private Part opaquePart;
+ private Part authority;
+ private PathPart path;
+ private Part query;
+ private Part fragment;
+
+ /**
+ * Constructs a new Builder.
+ */
+ public Builder() {}
+
+ /**
+ * Sets the scheme.
+ *
+ * @param scheme name or {@code null} if this is a relative Uri
+ */
+ public Builder scheme(String scheme) {
+ this.scheme = scheme;
+ return this;
+ }
+
+ Builder opaquePart(Part opaquePart) {
+ this.opaquePart = opaquePart;
+ return this;
+ }
+
+ /**
+ * Encodes and sets the given opaque scheme-specific-part.
+ *
+ * @param opaquePart decoded opaque part
+ */
+ public Builder opaquePart(String opaquePart) {
+ return opaquePart(Part.fromDecoded(opaquePart));
+ }
+
+ /**
+ * Sets the previously encoded opaque scheme-specific-part.
+ *
+ * @param opaquePart encoded opaque part
+ */
+ public Builder encodedOpaquePart(String opaquePart) {
+ return opaquePart(Part.fromEncoded(opaquePart));
+ }
+
+ Builder authority(Part authority) {
+ // This URI will be hierarchical.
+ this.opaquePart = null;
+
+ this.authority = authority;
+ return this;
+ }
+
+ /**
+ * Encodes and sets the authority.
+ */
+ public Builder authority(String authority) {
+ return authority(Part.fromDecoded(authority));
+ }
+
+ /**
+ * Sets the previously encoded authority.
+ */
+ public Builder encodedAuthority(String authority) {
+ return authority(Part.fromEncoded(authority));
+ }
+
+ Builder path(PathPart path) {
+ // This URI will be hierarchical.
+ this.opaquePart = null;
+
+ this.path = path;
+ return this;
+ }
+
+ /**
+ * Sets the path. Leaves '/' characters intact but encodes others as
+ * necessary.
+ *
+ * <p>If the path is not null and doesn't start with a '/', and if
+ * you specify a scheme and/or authority, the builder will prepend the
+ * given path with a '/'.
+ */
+ public Builder path(String path) {
+ return path(PathPart.fromDecoded(path));
+ }
+
+ /**
+ * Sets the previously encoded path.
+ *
+ * <p>If the path is not null and doesn't start with a '/', and if
+ * you specify a scheme and/or authority, the builder will prepend the
+ * given path with a '/'.
+ */
+ public Builder encodedPath(String path) {
+ return path(PathPart.fromEncoded(path));
+ }
+
+ /**
+ * Encodes the given segment and appends it to the path.
+ */
+ public Builder appendPath(String newSegment) {
+ return path(PathPart.appendDecodedSegment(path, newSegment));
+ }
+
+ /**
+ * Appends the given segment to the path.
+ */
+ public Builder appendEncodedPath(String newSegment) {
+ return path(PathPart.appendEncodedSegment(path, newSegment));
+ }
+
+ Builder query(Part query) {
+ // This URI will be hierarchical.
+ this.opaquePart = null;
+
+ this.query = query;
+ return this;
+ }
+
+ /**
+ * Encodes and sets the query.
+ */
+ public Builder query(String query) {
+ return query(Part.fromDecoded(query));
+ }
+
+ /**
+ * Sets the previously encoded query.
+ */
+ public Builder encodedQuery(String query) {
+ return query(Part.fromEncoded(query));
+ }
+
+ Builder fragment(Part fragment) {
+ this.fragment = fragment;
+ return this;
+ }
+
+ /**
+ * Encodes and sets the fragment.
+ */
+ public Builder fragment(String fragment) {
+ return fragment(Part.fromDecoded(fragment));
+ }
+
+ /**
+ * Sets the previously encoded fragment.
+ */
+ public Builder encodedFragment(String fragment) {
+ return fragment(Part.fromEncoded(fragment));
+ }
+
+ /**
+ * Encodes the key and value and then appends the parameter to the
+ * query string.
+ *
+ * @param key which will be encoded
+ * @param value which will be encoded
+ */
+ public Builder appendQueryParameter(String key, String value) {
+ // This URI will be hierarchical.
+ this.opaquePart = null;
+
+ String encodedParameter = encode(key, null) + "="
+ + encode(value, null);
+
+ if (query == null) {
+ query = Part.fromEncoded(encodedParameter);
+ return this;
+ }
+
+ String oldQuery = query.getEncoded();
+ if (oldQuery == null || oldQuery.length() == 0) {
+ query = Part.fromEncoded(encodedParameter);
+ } else {
+ query = Part.fromEncoded(oldQuery + "&" + encodedParameter);
+ }
+
+ return this;
+ }
+
+ /**
+ * Clears the the previously set query.
+ */
+ public Builder clearQuery() {
+ return query((Part) null);
+ }
+
+ /**
+ * Constructs a Uri with the current attributes.
+ *
+ * @throws UnsupportedOperationException if the URI is opaque and the
+ * scheme is null
+ */
+ public Uri build() {
+ if (opaquePart != null) {
+ if (this.scheme == null) {
+ throw new UnsupportedOperationException(
+ "An opaque URI must have a scheme.");
+ }
+
+ return new OpaqueUri(scheme, opaquePart, fragment);
+ } else {
+ // Hierarchical URIs should not return null for getPath().
+ PathPart path = this.path;
+ if (path == null || path == PathPart.NULL) {
+ path = PathPart.EMPTY;
+ } else {
+ // If we have a scheme and/or authority, the path must
+ // be absolute. Prepend it with a '/' if necessary.
+ if (hasSchemeOrAuthority()) {
+ path = PathPart.makeAbsolute(path);
+ }
+ }
+
+ return new HierarchicalUri(
+ scheme, authority, path, query, fragment);
+ }
+ }
+
+ private boolean hasSchemeOrAuthority() {
+ return scheme != null
+ || (authority != null && authority != Part.NULL);
+
+ }
+
+ @Override
+ public String toString() {
+ return build().toString();
+ }
+ }
+
+ /**
+ * Returns a set of the unique names of all query parameters. Iterating
+ * over the set will return the names in order of their first occurrence.
+ *
+ * @throws UnsupportedOperationException if this isn't a hierarchical URI
+ *
+ * @return a set of decoded names
+ */
+ public Set<String> getQueryParameterNames() {
+ if (isOpaque()) {
+ throw new UnsupportedOperationException(NOT_HIERARCHICAL);
+ }
+
+ String query = getEncodedQuery();
+ if (query == null) {
+ return Collections.emptySet();
+ }
+
+ Set<String> names = new LinkedHashSet<String>();
+ int start = 0;
+ do {
+ int next = query.indexOf('&', start);
+ int end = (next == -1) ? query.length() : next;
+
+ int separator = query.indexOf('=', start);
+ if (separator > end || separator == -1) {
+ separator = end;
+ }
+
+ String name = query.substring(start, separator);
+ names.add(decode(name));
+
+ // Move start to end of name.
+ start = end + 1;
+ } while (start < query.length());
+
+ return Collections.unmodifiableSet(names);
+ }
+
+ /**
+ * Searches the query string for parameter values with the given key.
+ *
+ * @param key which will be encoded
+ *
+ * @throws UnsupportedOperationException if this isn't a hierarchical URI
+ * @throws NullPointerException if key is null
+ * @return a list of decoded values
+ */
+ public List<String> getQueryParameters(String key) {
+ if (isOpaque()) {
+ throw new UnsupportedOperationException(NOT_HIERARCHICAL);
+ }
+ if (key == null) {
+ throw new NullPointerException("key");
+ }
+
+ String query = getEncodedQuery();
+ if (query == null) {
+ return Collections.emptyList();
+ }
+
+ String encodedKey;
+ try {
+ encodedKey = URLEncoder.encode(key, DEFAULT_ENCODING);
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError(e);
+ }
+
+ ArrayList<String> values = new ArrayList<String>();
+
+ int start = 0;
+ do {
+ int nextAmpersand = query.indexOf('&', start);
+ int end = nextAmpersand != -1 ? nextAmpersand : query.length();
+
+ int separator = query.indexOf('=', start);
+ if (separator > end || separator == -1) {
+ separator = end;
+ }
+
+ if (separator - start == encodedKey.length()
+ && query.regionMatches(start, encodedKey, 0, encodedKey.length())) {
+ if (separator == end) {
+ values.add("");
+ } else {
+ values.add(decode(query.substring(separator + 1, end)));
+ }
+ }
+
+ // Move start to end of name.
+ if (nextAmpersand != -1) {
+ start = nextAmpersand + 1;
+ } else {
+ break;
+ }
+ } while (true);
+
+ return Collections.unmodifiableList(values);
+ }
+
+ /**
+ * Searches the query string for the first value with the given key.
+ *
+ * <p><strong>Warning:</strong> Prior to Jelly Bean, this decoded
+ * the '+' character as '+' rather than ' '.
+ *
+ * @param key which will be encoded
+ * @throws UnsupportedOperationException if this isn't a hierarchical URI
+ * @throws NullPointerException if key is null
+ * @return the decoded value or null if no parameter is found
+ */
+ @Nullable
+ public String getQueryParameter(String key) {
+ if (isOpaque()) {
+ throw new UnsupportedOperationException(NOT_HIERARCHICAL);
+ }
+ if (key == null) {
+ throw new NullPointerException("key");
+ }
+
+ final String query = getEncodedQuery();
+ if (query == null) {
+ return null;
+ }
+
+ final String encodedKey = encode(key, null);
+ final int length = query.length();
+ int start = 0;
+ do {
+ int nextAmpersand = query.indexOf('&', start);
+ int end = nextAmpersand != -1 ? nextAmpersand : length;
+
+ int separator = query.indexOf('=', start);
+ if (separator > end || separator == -1) {
+ separator = end;
+ }
+
+ if (separator - start == encodedKey.length()
+ && query.regionMatches(start, encodedKey, 0, encodedKey.length())) {
+ if (separator == end) {
+ return "";
+ } else {
+ String encodedValue = query.substring(separator + 1, end);
+ return UriCodec.decode(encodedValue, true, StandardCharsets.UTF_8, false);
+ }
+ }
+
+ // Move start to end of name.
+ if (nextAmpersand != -1) {
+ start = nextAmpersand + 1;
+ } else {
+ break;
+ }
+ } while (true);
+ return null;
+ }
+
+ /**
+ * Searches the query string for the first value with the given key and interprets it
+ * as a boolean value. "false" and "0" are interpreted as <code>false</code>, everything
+ * else is interpreted as <code>true</code>.
+ *
+ * @param key which will be decoded
+ * @param defaultValue the default value to return if there is no query parameter for key
+ * @return the boolean interpretation of the query parameter key
+ */
+ public boolean getBooleanQueryParameter(String key, boolean defaultValue) {
+ String flag = getQueryParameter(key);
+ if (flag == null) {
+ return defaultValue;
+ }
+ flag = flag.toLowerCase(Locale.ROOT);
+ return (!"false".equals(flag) && !"0".equals(flag));
+ }
+
+ /**
+ * Return an equivalent URI with a lowercase scheme component.
+ * This aligns the Uri with Android best practices for
+ * intent filtering.
+ *
+ * <p>For example, "HTTP://www.android.com" becomes
+ * "http://www.android.com"
+ *
+ * <p>All URIs received from outside Android (such as user input,
+ * or external sources like Bluetooth, NFC, or the Internet) should
+ * be normalized before they are used to create an Intent.
+ *
+ * <p class="note">This method does <em>not</em> validate bad URI's,
+ * or 'fix' poorly formatted URI's - so do not use it for input validation.
+ * A Uri will always be returned, even if the Uri is badly formatted to
+ * begin with and a scheme component cannot be found.
+ *
+ * @return normalized Uri (never null)
+ * @see android.content.Intent#setData
+ * @see android.content.Intent#setDataAndNormalize
+ */
+ public Uri normalizeScheme() {
+ String scheme = getScheme();
+ if (scheme == null) return this; // give up
+ String lowerScheme = scheme.toLowerCase(Locale.ROOT);
+ if (scheme.equals(lowerScheme)) return this; // no change
+
+ return buildUpon().scheme(lowerScheme).build();
+ }
+
+ /** Identifies a null parcelled Uri. */
+ private static final int NULL_TYPE_ID = 0;
+
+ /**
+ * Reads Uris from Parcels.
+ */
+ public static final @android.annotation.NonNull Parcelable.Creator<Uri> CREATOR
+ = new Parcelable.Creator<Uri>() {
+ public Uri createFromParcel(Parcel in) {
+ int type = in.readInt();
+ switch (type) {
+ case NULL_TYPE_ID: return null;
+ case StringUri.TYPE_ID: return StringUri.readFrom(in);
+ case OpaqueUri.TYPE_ID: return OpaqueUri.readFrom(in);
+ case HierarchicalUri.TYPE_ID:
+ return HierarchicalUri.readFrom(in);
+ }
+
+ throw new IllegalArgumentException("Unknown URI type: " + type);
+ }
+
+ public Uri[] newArray(int size) {
+ return new Uri[size];
+ }
+ };
+
+ /**
+ * Writes a Uri to a Parcel.
+ *
+ * @param out parcel to write to
+ * @param uri to write, can be null
+ */
+ public static void writeToParcel(Parcel out, Uri uri) {
+ if (uri == null) {
+ out.writeInt(NULL_TYPE_ID);
+ } else {
+ uri.writeToParcel(out, 0);
+ }
+ }
+
+ private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
+
+ /**
+ * Encodes characters in the given string as '%'-escaped octets
+ * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers
+ * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes
+ * all other characters.
+ *
+ * @param s string to encode
+ * @return an encoded version of s suitable for use as a URI component,
+ * or null if s is null
+ */
+ public static String encode(String s) {
+ return encode(s, null);
+ }
+
+ /**
+ * Encodes characters in the given string as '%'-escaped octets
+ * using the UTF-8 scheme. Leaves letters ("A-Z", "a-z"), numbers
+ * ("0-9"), and unreserved characters ("_-!.~'()*") intact. Encodes
+ * all other characters with the exception of those specified in the
+ * allow argument.
+ *
+ * @param s string to encode
+ * @param allow set of additional characters to allow in the encoded form,
+ * null if no characters should be skipped
+ * @return an encoded version of s suitable for use as a URI component,
+ * or null if s is null
+ */
+ public static String encode(String s, String allow) {
+ if (s == null) {
+ return null;
+ }
+
+ // Lazily-initialized buffers.
+ StringBuilder encoded = null;
+
+ int oldLength = s.length();
+
+ // This loop alternates between copying over allowed characters and
+ // encoding in chunks. This results in fewer method calls and
+ // allocations than encoding one character at a time.
+ int current = 0;
+ while (current < oldLength) {
+ // Start in "copying" mode where we copy over allowed chars.
+
+ // Find the next character which needs to be encoded.
+ int nextToEncode = current;
+ while (nextToEncode < oldLength
+ && isAllowed(s.charAt(nextToEncode), allow)) {
+ nextToEncode++;
+ }
+
+ // If there's nothing more to encode...
+ if (nextToEncode == oldLength) {
+ if (current == 0) {
+ // We didn't need to encode anything!
+ return s;
+ } else {
+ // Presumably, we've already done some encoding.
+ encoded.append(s, current, oldLength);
+ return encoded.toString();
+ }
+ }
+
+ if (encoded == null) {
+ encoded = new StringBuilder();
+ }
+
+ if (nextToEncode > current) {
+ // Append allowed characters leading up to this point.
+ encoded.append(s, current, nextToEncode);
+ } else {
+ // assert nextToEncode == current
+ }
+
+ // Switch to "encoding" mode.
+
+ // Find the next allowed character.
+ current = nextToEncode;
+ int nextAllowed = current + 1;
+ while (nextAllowed < oldLength
+ && !isAllowed(s.charAt(nextAllowed), allow)) {
+ nextAllowed++;
+ }
+
+ // Convert the substring to bytes and encode the bytes as
+ // '%'-escaped octets.
+ String toEncode = s.substring(current, nextAllowed);
+ try {
+ byte[] bytes = toEncode.getBytes(DEFAULT_ENCODING);
+ int bytesLength = bytes.length;
+ for (int i = 0; i < bytesLength; i++) {
+ encoded.append('%');
+ encoded.append(HEX_DIGITS[(bytes[i] & 0xf0) >> 4]);
+ encoded.append(HEX_DIGITS[bytes[i] & 0xf]);
+ }
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError(e);
+ }
+
+ current = nextAllowed;
+ }
+
+ // Encoded could still be null at this point if s is empty.
+ return encoded == null ? s : encoded.toString();
+ }
+
+ /**
+ * Returns true if the given character is allowed.
+ *
+ * @param c character to check
+ * @param allow characters to allow
+ * @return true if the character is allowed or false if it should be
+ * encoded
+ */
+ private static boolean isAllowed(char c, String allow) {
+ return (c >= 'A' && c <= 'Z')
+ || (c >= 'a' && c <= 'z')
+ || (c >= '0' && c <= '9')
+ || "_-!.~'()*".indexOf(c) != NOT_FOUND
+ || (allow != null && allow.indexOf(c) != NOT_FOUND);
+ }
+
+ /**
+ * Decodes '%'-escaped octets in the given string using the UTF-8 scheme.
+ * Replaces invalid octets with the unicode replacement character
+ * ("\\uFFFD").
+ *
+ * @param s encoded string to decode
+ * @return the given string with escaped octets decoded, or null if
+ * s is null
+ */
+ public static String decode(String s) {
+ if (s == null) {
+ return null;
+ }
+ return UriCodec.decode(
+ s, false /* convertPlus */, StandardCharsets.UTF_8, false /* throwOnFailure */);
+ }
+
+ /**
+ * Support for part implementations.
+ */
+ static abstract class AbstractPart {
+
+ /**
+ * Enum which indicates which representation of a given part we have.
+ */
+ static class Representation {
+ static final int BOTH = 0;
+ static final int ENCODED = 1;
+ static final int DECODED = 2;
+ }
+
+ volatile String encoded;
+ volatile String decoded;
+
+ AbstractPart(String encoded, String decoded) {
+ this.encoded = encoded;
+ this.decoded = decoded;
+ }
+
+ abstract String getEncoded();
+
+ final String getDecoded() {
+ @SuppressWarnings("StringEquality")
+ boolean hasDecoded = decoded != NOT_CACHED;
+ return hasDecoded ? decoded : (decoded = decode(encoded));
+ }
+
+ final void writeTo(Parcel parcel) {
+ @SuppressWarnings("StringEquality")
+ boolean hasEncoded = encoded != NOT_CACHED;
+
+ @SuppressWarnings("StringEquality")
+ boolean hasDecoded = decoded != NOT_CACHED;
+
+ if (hasEncoded && hasDecoded) {
+ parcel.writeInt(Representation.BOTH);
+ parcel.writeString(encoded);
+ parcel.writeString(decoded);
+ } else if (hasEncoded) {
+ parcel.writeInt(Representation.ENCODED);
+ parcel.writeString(encoded);
+ } else if (hasDecoded) {
+ parcel.writeInt(Representation.DECODED);
+ parcel.writeString(decoded);
+ } else {
+ throw new IllegalArgumentException("Neither encoded nor decoded");
+ }
+ }
+ }
+
+ /**
+ * Immutable wrapper of encoded and decoded versions of a URI part. Lazily
+ * creates the encoded or decoded version from the other.
+ */
+ static class Part extends AbstractPart {
+
+ /** A part with null values. */
+ static final Part NULL = new EmptyPart(null);
+
+ /** A part with empty strings for values. */
+ static final Part EMPTY = new EmptyPart("");
+
+ private Part(String encoded, String decoded) {
+ super(encoded, decoded);
+ }
+
+ boolean isEmpty() {
+ return false;
+ }
+
+ String getEncoded() {
+ @SuppressWarnings("StringEquality")
+ boolean hasEncoded = encoded != NOT_CACHED;
+ return hasEncoded ? encoded : (encoded = encode(decoded));
+ }
+
+ static Part readFrom(Parcel parcel) {
+ int representation = parcel.readInt();
+ switch (representation) {
+ case Representation.BOTH:
+ return from(parcel.readString(), parcel.readString());
+ case Representation.ENCODED:
+ return fromEncoded(parcel.readString());
+ case Representation.DECODED:
+ return fromDecoded(parcel.readString());
+ default:
+ throw new IllegalArgumentException("Unknown representation: "
+ + representation);
+ }
+ }
+
+ /**
+ * Returns given part or {@link #NULL} if the given part is null.
+ */
+ static Part nonNull(Part part) {
+ return part == null ? NULL : part;
+ }
+
+ /**
+ * Creates a part from the encoded string.
+ *
+ * @param encoded part string
+ */
+ static Part fromEncoded(String encoded) {
+ return from(encoded, NOT_CACHED);
+ }
+
+ /**
+ * Creates a part from the decoded string.
+ *
+ * @param decoded part string
+ */
+ static Part fromDecoded(String decoded) {
+ return from(NOT_CACHED, decoded);
+ }
+
+ /**
+ * Creates a part from the encoded and decoded strings.
+ *
+ * @param encoded part string
+ * @param decoded part string
+ */
+ static Part from(String encoded, String decoded) {
+ // We have to check both encoded and decoded in case one is
+ // NOT_CACHED.
+
+ if (encoded == null) {
+ return NULL;
+ }
+ if (encoded.length() == 0) {
+ return EMPTY;
+ }
+
+ if (decoded == null) {
+ return NULL;
+ }
+ if (decoded .length() == 0) {
+ return EMPTY;
+ }
+
+ return new Part(encoded, decoded);
+ }
+
+ private static class EmptyPart extends Part {
+ public EmptyPart(String value) {
+ super(value, value);
+ }
+
+ @Override
+ boolean isEmpty() {
+ return true;
+ }
+ }
+ }
+
+ /**
+ * Immutable wrapper of encoded and decoded versions of a path part. Lazily
+ * creates the encoded or decoded version from the other.
+ */
+ static class PathPart extends AbstractPart {
+
+ /** A part with null values. */
+ static final PathPart NULL = new PathPart(null, null);
+
+ /** A part with empty strings for values. */
+ static final PathPart EMPTY = new PathPart("", "");
+
+ private PathPart(String encoded, String decoded) {
+ super(encoded, decoded);
+ }
+
+ String getEncoded() {
+ @SuppressWarnings("StringEquality")
+ boolean hasEncoded = encoded != NOT_CACHED;
+
+ // Don't encode '/'.
+ return hasEncoded ? encoded : (encoded = encode(decoded, "/"));
+ }
+
+ /**
+ * Cached path segments. This doesn't need to be volatile--we don't
+ * care if other threads see the result.
+ */
+ private PathSegments pathSegments;
+
+ /**
+ * Gets the individual path segments. Parses them if necessary.
+ *
+ * @return parsed path segments or null if this isn't a hierarchical
+ * URI
+ */
+ PathSegments getPathSegments() {
+ if (pathSegments != null) {
+ return pathSegments;
+ }
+
+ String path = getEncoded();
+ if (path == null) {
+ return pathSegments = PathSegments.EMPTY;
+ }
+
+ PathSegmentsBuilder segmentBuilder = new PathSegmentsBuilder();
+
+ int previous = 0;
+ int current;
+ while ((current = path.indexOf('/', previous)) > -1) {
+ // This check keeps us from adding a segment if the path starts
+ // '/' and an empty segment for "//".
+ if (previous < current) {
+ String decodedSegment
+ = decode(path.substring(previous, current));
+ segmentBuilder.add(decodedSegment);
+ }
+ previous = current + 1;
+ }
+
+ // Add in the final path segment.
+ if (previous < path.length()) {
+ segmentBuilder.add(decode(path.substring(previous)));
+ }
+
+ return pathSegments = segmentBuilder.build();
+ }
+
+ static PathPart appendEncodedSegment(PathPart oldPart,
+ String newSegment) {
+ // If there is no old path, should we make the new path relative
+ // or absolute? I pick absolute.
+
+ if (oldPart == null) {
+ // No old path.
+ return fromEncoded("/" + newSegment);
+ }
+
+ String oldPath = oldPart.getEncoded();
+
+ if (oldPath == null) {
+ oldPath = "";
+ }
+
+ int oldPathLength = oldPath.length();
+ String newPath;
+ if (oldPathLength == 0) {
+ // No old path.
+ newPath = "/" + newSegment;
+ } else if (oldPath.charAt(oldPathLength - 1) == '/') {
+ newPath = oldPath + newSegment;
+ } else {
+ newPath = oldPath + "/" + newSegment;
+ }
+
+ return fromEncoded(newPath);
+ }
+
+ static PathPart appendDecodedSegment(PathPart oldPart, String decoded) {
+ String encoded = encode(decoded);
+
+ // TODO: Should we reuse old PathSegments? Probably not.
+ return appendEncodedSegment(oldPart, encoded);
+ }
+
+ static PathPart readFrom(Parcel parcel) {
+ int representation = parcel.readInt();
+ switch (representation) {
+ case Representation.BOTH:
+ return from(parcel.readString(), parcel.readString());
+ case Representation.ENCODED:
+ return fromEncoded(parcel.readString());
+ case Representation.DECODED:
+ return fromDecoded(parcel.readString());
+ default:
+ throw new IllegalArgumentException("Bad representation: " + representation);
+ }
+ }
+
+ /**
+ * Creates a path from the encoded string.
+ *
+ * @param encoded part string
+ */
+ static PathPart fromEncoded(String encoded) {
+ return from(encoded, NOT_CACHED);
+ }
+
+ /**
+ * Creates a path from the decoded string.
+ *
+ * @param decoded part string
+ */
+ static PathPart fromDecoded(String decoded) {
+ return from(NOT_CACHED, decoded);
+ }
+
+ /**
+ * Creates a path from the encoded and decoded strings.
+ *
+ * @param encoded part string
+ * @param decoded part string
+ */
+ static PathPart from(String encoded, String decoded) {
+ if (encoded == null) {
+ return NULL;
+ }
+
+ if (encoded.length() == 0) {
+ return EMPTY;
+ }
+
+ return new PathPart(encoded, decoded);
+ }
+
+ /**
+ * Prepends path values with "/" if they're present, not empty, and
+ * they don't already start with "/".
+ */
+ static PathPart makeAbsolute(PathPart oldPart) {
+ @SuppressWarnings("StringEquality")
+ boolean encodedCached = oldPart.encoded != NOT_CACHED;
+
+ // We don't care which version we use, and we don't want to force
+ // unneccessary encoding/decoding.
+ String oldPath = encodedCached ? oldPart.encoded : oldPart.decoded;
+
+ if (oldPath == null || oldPath.length() == 0
+ || oldPath.startsWith("/")) {
+ return oldPart;
+ }
+
+ // Prepend encoded string if present.
+ String newEncoded = encodedCached
+ ? "/" + oldPart.encoded : NOT_CACHED;
+
+ // Prepend decoded string if present.
+ @SuppressWarnings("StringEquality")
+ boolean decodedCached = oldPart.decoded != NOT_CACHED;
+ String newDecoded = decodedCached
+ ? "/" + oldPart.decoded
+ : NOT_CACHED;
+
+ return new PathPart(newEncoded, newDecoded);
+ }
+ }
+
+ /**
+ * Creates a new Uri by appending an already-encoded path segment to a
+ * base Uri.
+ *
+ * @param baseUri Uri to append path segment to
+ * @param pathSegment encoded path segment to append
+ * @return a new Uri based on baseUri with the given segment appended to
+ * the path
+ * @throws NullPointerException if baseUri is null
+ */
+ public static Uri withAppendedPath(Uri baseUri, String pathSegment) {
+ Builder builder = baseUri.buildUpon();
+ builder = builder.appendEncodedPath(pathSegment);
+ return builder.build();
+ }
+
+ /**
+ * If this {@link Uri} is {@code file://}, then resolve and return its
+ * canonical path. Also fixes legacy emulated storage paths so they are
+ * usable across user boundaries. Should always be called from the app
+ * process before sending elsewhere.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public Uri getCanonicalUri() {
+ if ("file".equals(getScheme())) {
+ final String canonicalPath;
+ try {
+ canonicalPath = new File(getPath()).getCanonicalPath();
+ } catch (IOException e) {
+ return this;
+ }
+
+ if (Environment.isExternalStorageEmulated()) {
+ final String legacyPath = Environment.getLegacyExternalStorageDirectory()
+ .toString();
+
+ // Splice in user-specific path when legacy path is found
+ if (canonicalPath.startsWith(legacyPath)) {
+ return Uri.fromFile(new File(
+ Environment.getExternalStorageDirectory().toString(),
+ canonicalPath.substring(legacyPath.length() + 1)));
+ }
+ }
+
+ return Uri.fromFile(new File(canonicalPath));
+ } else {
+ return this;
+ }
+ }
+
+ /**
+ * If this is a {@code file://} Uri, it will be reported to
+ * {@link StrictMode}.
+ *
+ * @hide
+ */
+ public void checkFileUriExposed(String location) {
+ if ("file".equals(getScheme())
+ && (getPath() != null) && !getPath().startsWith("/system/")) {
+ StrictMode.onFileUriExposed(this, location);
+ }
+ }
+
+ /**
+ * If this is a {@code content://} Uri without access flags, it will be
+ * reported to {@link StrictMode}.
+ *
+ * @hide
+ */
+ public void checkContentUriWithoutPermission(String location, int flags) {
+ if ("content".equals(getScheme()) && !Intent.isAccessUriMode(flags)) {
+ StrictMode.onContentUriWithoutPermission(this, location);
+ }
+ }
+
+ /**
+ * Test if this is a path prefix match against the given Uri. Verifies that
+ * scheme, authority, and atomic path segments match.
+ *
+ * @hide
+ */
+ public boolean isPathPrefixMatch(Uri prefix) {
+ if (!Objects.equals(getScheme(), prefix.getScheme())) return false;
+ if (!Objects.equals(getAuthority(), prefix.getAuthority())) return false;
+
+ List<String> seg = getPathSegments();
+ List<String> prefixSeg = prefix.getPathSegments();
+
+ final int prefixSize = prefixSeg.size();
+ if (seg.size() < prefixSize) return false;
+
+ for (int i = 0; i < prefixSize; i++) {
+ if (!Objects.equals(seg.get(i), prefixSeg.get(i))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/android/net/UriCodec.java b/android/net/UriCodec.java
new file mode 100644
index 0000000..e1470e0
--- /dev/null
+++ b/android/net/UriCodec.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2015 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;
+
+import java.net.URISyntaxException;
+import java.nio.ByteBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CodingErrorAction;
+
+/**
+ * Decodes “application/x-www-form-urlencoded” content.
+ *
+ * @hide
+ */
+public final class UriCodec {
+
+ private UriCodec() {}
+
+ /**
+ * Interprets a char as hex digits, returning a number from -1 (invalid char) to 15 ('f').
+ */
+ private static int hexCharToValue(char c) {
+ if ('0' <= c && c <= '9') {
+ return c - '0';
+ }
+ if ('a' <= c && c <= 'f') {
+ return 10 + c - 'a';
+ }
+ if ('A' <= c && c <= 'F') {
+ return 10 + c - 'A';
+ }
+ return -1;
+ }
+
+ private static URISyntaxException unexpectedCharacterException(
+ String uri, String name, char unexpected, int index) {
+ String nameString = (name == null) ? "" : " in [" + name + "]";
+ return new URISyntaxException(
+ uri, "Unexpected character" + nameString + ": " + unexpected, index);
+ }
+
+ private static char getNextCharacter(String uri, int index, int end, String name)
+ throws URISyntaxException {
+ if (index >= end) {
+ String nameString = (name == null) ? "" : " in [" + name + "]";
+ throw new URISyntaxException(
+ uri, "Unexpected end of string" + nameString, index);
+ }
+ return uri.charAt(index);
+ }
+
+ /**
+ * Decode a string according to the rules of this decoder.
+ *
+ * - if {@code convertPlus == true} all ‘+’ chars in the decoded output are converted to ‘ ‘
+ * (white space)
+ * - if {@code throwOnFailure == true}, an {@link IllegalArgumentException} is thrown for
+ * invalid inputs. Else, U+FFFd is emitted to the output in place of invalid input octets.
+ */
+ public static String decode(
+ String s, boolean convertPlus, Charset charset, boolean throwOnFailure) {
+ StringBuilder builder = new StringBuilder(s.length());
+ appendDecoded(builder, s, convertPlus, charset, throwOnFailure);
+ return builder.toString();
+ }
+
+ /**
+ * Character to be output when there's an error decoding an input.
+ */
+ private static final char INVALID_INPUT_CHARACTER = '\ufffd';
+
+ private static void appendDecoded(
+ StringBuilder builder,
+ String s,
+ boolean convertPlus,
+ Charset charset,
+ boolean throwOnFailure) {
+ CharsetDecoder decoder = charset.newDecoder()
+ .onMalformedInput(CodingErrorAction.REPLACE)
+ .replaceWith("\ufffd")
+ .onUnmappableCharacter(CodingErrorAction.REPORT);
+ // Holds the bytes corresponding to the escaped chars being read (empty if the last char
+ // wasn't a escaped char).
+ ByteBuffer byteBuffer = ByteBuffer.allocate(s.length());
+ int i = 0;
+ while (i < s.length()) {
+ char c = s.charAt(i);
+ i++;
+ switch (c) {
+ case '+':
+ flushDecodingByteAccumulator(
+ builder, decoder, byteBuffer, throwOnFailure);
+ builder.append(convertPlus ? ' ' : '+');
+ break;
+ case '%':
+ // Expect two characters representing a number in hex.
+ byte hexValue = 0;
+ for (int j = 0; j < 2; j++) {
+ try {
+ c = getNextCharacter(s, i, s.length(), null /* name */);
+ } catch (URISyntaxException e) {
+ // Unexpected end of input.
+ if (throwOnFailure) {
+ throw new IllegalArgumentException(e);
+ } else {
+ flushDecodingByteAccumulator(
+ builder, decoder, byteBuffer, throwOnFailure);
+ builder.append(INVALID_INPUT_CHARACTER);
+ return;
+ }
+ }
+ i++;
+ int newDigit = hexCharToValue(c);
+ if (newDigit < 0) {
+ if (throwOnFailure) {
+ throw new IllegalArgumentException(
+ unexpectedCharacterException(s, null /* name */, c, i - 1));
+ } else {
+ flushDecodingByteAccumulator(
+ builder, decoder, byteBuffer, throwOnFailure);
+ builder.append(INVALID_INPUT_CHARACTER);
+ break;
+ }
+ }
+ hexValue = (byte) (hexValue * 0x10 + newDigit);
+ }
+ byteBuffer.put(hexValue);
+ break;
+ default:
+ flushDecodingByteAccumulator(builder, decoder, byteBuffer, throwOnFailure);
+ builder.append(c);
+ }
+ }
+ flushDecodingByteAccumulator(builder, decoder, byteBuffer, throwOnFailure);
+ }
+
+ private static void flushDecodingByteAccumulator(
+ StringBuilder builder,
+ CharsetDecoder decoder,
+ ByteBuffer byteBuffer,
+ boolean throwOnFailure) {
+ if (byteBuffer.position() == 0) {
+ return;
+ }
+ byteBuffer.flip();
+ try {
+ builder.append(decoder.decode(byteBuffer));
+ } catch (CharacterCodingException e) {
+ if (throwOnFailure) {
+ throw new IllegalArgumentException(e);
+ } else {
+ builder.append(INVALID_INPUT_CHARACTER);
+ }
+ } finally {
+ // Use the byte buffer to write again.
+ byteBuffer.flip();
+ byteBuffer.limit(byteBuffer.capacity());
+ }
+ }
+}
diff --git a/android/net/UrlQuerySanitizer.java b/android/net/UrlQuerySanitizer.java
new file mode 100644
index 0000000..cf08b65
--- /dev/null
+++ b/android/net/UrlQuerySanitizer.java
@@ -0,0 +1,911 @@
+/*
+ * Copyright (C) 2007 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;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ *
+ * Sanitizes the Query portion of a URL. Simple example:
+ * <code>
+ * UrlQuerySanitizer sanitizer = new UrlQuerySanitizer();
+ * sanitizer.setAllowUnregisteredParamaters(true);
+ * sanitizer.parseUrl("http://example.com/?name=Joe+User");
+ * String name = sanitizer.getValue("name"));
+ * // name now contains "Joe_User"
+ * </code>
+ *
+ * Register ValueSanitizers to customize the way individual
+ * parameters are sanitized:
+ * <code>
+ * UrlQuerySanitizer sanitizer = new UrlQuerySanitizer();
+ * sanitizer.registerParamater("name", UrlQuerySanitizer.createSpaceLegal());
+ * sanitizer.parseUrl("http://example.com/?name=Joe+User");
+ * String name = sanitizer.getValue("name"));
+ * // name now contains "Joe User". (The string is first decoded, which
+ * // converts the '+' to a ' '. Then the string is sanitized, which
+ * // converts the ' ' to an '_'. (The ' ' is converted because the default
+ * unregistered parameter sanitizer does not allow any special characters,
+ * and ' ' is a special character.)
+ * </code>
+ *
+ * There are several ways to create ValueSanitizers. In order of increasing
+ * sophistication:
+ * <ol>
+ * <li>Call one of the UrlQuerySanitizer.createXXX() methods.
+ * <li>Construct your own instance of
+ * UrlQuerySanitizer.IllegalCharacterValueSanitizer.
+ * <li>Subclass UrlQuerySanitizer.ValueSanitizer to define your own value
+ * sanitizer.
+ * </ol>
+ *
+ */
+public class UrlQuerySanitizer {
+
+ /**
+ * A simple tuple that holds parameter-value pairs.
+ *
+ */
+ public class ParameterValuePair {
+ /**
+ * Construct a parameter-value tuple.
+ * @param parameter an unencoded parameter
+ * @param value an unencoded value
+ */
+ public ParameterValuePair(String parameter,
+ String value) {
+ mParameter = parameter;
+ mValue = value;
+ }
+ /**
+ * The unencoded parameter
+ */
+ public String mParameter;
+ /**
+ * The unencoded value
+ */
+ public String mValue;
+ }
+
+ final private HashMap<String, ValueSanitizer> mSanitizers =
+ new HashMap<String, ValueSanitizer>();
+ final private HashMap<String, String> mEntries =
+ new HashMap<String, String>();
+ final private ArrayList<ParameterValuePair> mEntriesList =
+ new ArrayList<ParameterValuePair>();
+ private boolean mAllowUnregisteredParamaters;
+ private boolean mPreferFirstRepeatedParameter;
+ private ValueSanitizer mUnregisteredParameterValueSanitizer =
+ getAllIllegal();
+
+ /**
+ * A functor used to sanitize a single query value.
+ *
+ */
+ public static interface ValueSanitizer {
+ /**
+ * Sanitize an unencoded value.
+ * @param value
+ * @return the sanitized unencoded value
+ */
+ public String sanitize(String value);
+ }
+
+ /**
+ * Sanitize values based on which characters they contain. Illegal
+ * characters are replaced with either space or '_', depending upon
+ * whether space is a legal character or not.
+ */
+ public static class IllegalCharacterValueSanitizer implements
+ ValueSanitizer {
+ private int mFlags;
+
+ /**
+ * Allow space (' ') characters.
+ */
+ public final static int SPACE_OK = 1 << 0;
+ /**
+ * Allow whitespace characters other than space. The
+ * other whitespace characters are
+ * '\t' '\f' '\n' '\r' and '\0x000b' (vertical tab)
+ */
+ public final static int OTHER_WHITESPACE_OK = 1 << 1;
+ /**
+ * Allow characters with character codes 128 to 255.
+ */
+ public final static int NON_7_BIT_ASCII_OK = 1 << 2;
+ /**
+ * Allow double quote characters. ('"')
+ */
+ public final static int DQUOTE_OK = 1 << 3;
+ /**
+ * Allow single quote characters. ('\'')
+ */
+ public final static int SQUOTE_OK = 1 << 4;
+ /**
+ * Allow less-than characters. ('<')
+ */
+ public final static int LT_OK = 1 << 5;
+ /**
+ * Allow greater-than characters. ('>')
+ */
+ public final static int GT_OK = 1 << 6;
+ /**
+ * Allow ampersand characters ('&')
+ */
+ public final static int AMP_OK = 1 << 7;
+ /**
+ * Allow percent-sign characters ('%')
+ */
+ public final static int PCT_OK = 1 << 8;
+ /**
+ * Allow nul characters ('\0')
+ */
+ public final static int NUL_OK = 1 << 9;
+ /**
+ * Allow text to start with a script URL
+ * such as "javascript:" or "vbscript:"
+ */
+ public final static int SCRIPT_URL_OK = 1 << 10;
+
+ /**
+ * Mask with all fields set to OK
+ */
+ public final static int ALL_OK = 0x7ff;
+
+ /**
+ * Mask with both regular space and other whitespace OK
+ */
+ public final static int ALL_WHITESPACE_OK =
+ SPACE_OK | OTHER_WHITESPACE_OK;
+
+
+ // Common flag combinations:
+
+ /**
+ * <ul>
+ * <li>Deny all special characters.
+ * <li>Deny script URLs.
+ * </ul>
+ */
+ public final static int ALL_ILLEGAL =
+ 0;
+ /**
+ * <ul>
+ * <li>Allow all special characters except Nul. ('\0').
+ * <li>Allow script URLs.
+ * </ul>
+ */
+ public final static int ALL_BUT_NUL_LEGAL =
+ ALL_OK & ~NUL_OK;
+ /**
+ * <ul>
+ * <li>Allow all special characters except for:
+ * <ul>
+ * <li>whitespace characters
+ * <li>Nul ('\0')
+ * </ul>
+ * <li>Allow script URLs.
+ * </ul>
+ */
+ public final static int ALL_BUT_WHITESPACE_LEGAL =
+ ALL_OK & ~(ALL_WHITESPACE_OK | NUL_OK);
+ /**
+ * <ul>
+ * <li>Allow characters used by encoded URLs.
+ * <li>Deny script URLs.
+ * </ul>
+ */
+ public final static int URL_LEGAL =
+ NON_7_BIT_ASCII_OK | SQUOTE_OK | AMP_OK | PCT_OK;
+ /**
+ * <ul>
+ * <li>Allow characters used by encoded URLs.
+ * <li>Allow spaces.
+ * <li>Deny script URLs.
+ * </ul>
+ */
+ public final static int URL_AND_SPACE_LEGAL =
+ URL_LEGAL | SPACE_OK;
+ /**
+ * <ul>
+ * <li>Allow ampersand.
+ * <li>Deny script URLs.
+ * </ul>
+ */
+ public final static int AMP_LEGAL =
+ AMP_OK;
+ /**
+ * <ul>
+ * <li>Allow ampersand.
+ * <li>Allow space.
+ * <li>Deny script URLs.
+ * </ul>
+ */
+ public final static int AMP_AND_SPACE_LEGAL =
+ AMP_OK | SPACE_OK;
+ /**
+ * <ul>
+ * <li>Allow space.
+ * <li>Deny script URLs.
+ * </ul>
+ */
+ public final static int SPACE_LEGAL =
+ SPACE_OK;
+ /**
+ * <ul>
+ * <li>Allow all but.
+ * <ul>
+ * <li>Nul ('\0')
+ * <li>Angle brackets ('<', '>')
+ * </ul>
+ * <li>Deny script URLs.
+ * </ul>
+ */
+ public final static int ALL_BUT_NUL_AND_ANGLE_BRACKETS_LEGAL =
+ ALL_OK & ~(NUL_OK | LT_OK | GT_OK);
+
+ /**
+ * Script URL definitions
+ */
+
+ private final static String JAVASCRIPT_PREFIX = "javascript:";
+
+ private final static String VBSCRIPT_PREFIX = "vbscript:";
+
+ private final static int MIN_SCRIPT_PREFIX_LENGTH = Math.min(
+ JAVASCRIPT_PREFIX.length(), VBSCRIPT_PREFIX.length());
+
+ /**
+ * Construct a sanitizer. The parameters set the behavior of the
+ * sanitizer.
+ * @param flags some combination of the XXX_OK flags.
+ */
+ public IllegalCharacterValueSanitizer(
+ int flags) {
+ mFlags = flags;
+ }
+ /**
+ * Sanitize a value.
+ * <ol>
+ * <li>If script URLs are not OK, they will be removed.
+ * <li>If neither spaces nor other white space is OK, then
+ * white space will be trimmed from the beginning and end of
+ * the URL. (Just the actual white space characters are trimmed, not
+ * other control codes.)
+ * <li> Illegal characters will be replaced with
+ * either ' ' or '_', depending on whether a space is itself a
+ * legal character.
+ * </ol>
+ * @param value
+ * @return the sanitized value
+ */
+ public String sanitize(String value) {
+ if (value == null) {
+ return null;
+ }
+ int length = value.length();
+ if ((mFlags & SCRIPT_URL_OK) != 0) {
+ if (length >= MIN_SCRIPT_PREFIX_LENGTH) {
+ String asLower = value.toLowerCase(Locale.ROOT);
+ if (asLower.startsWith(JAVASCRIPT_PREFIX) ||
+ asLower.startsWith(VBSCRIPT_PREFIX)) {
+ return "";
+ }
+ }
+ }
+
+ // If whitespace isn't OK, get rid of whitespace at beginning
+ // and end of value.
+ if ( (mFlags & ALL_WHITESPACE_OK) == 0) {
+ value = trimWhitespace(value);
+ // The length could have changed, so we need to correct
+ // the length variable.
+ length = value.length();
+ }
+
+ StringBuilder stringBuilder = new StringBuilder(length);
+ for(int i = 0; i < length; i++) {
+ char c = value.charAt(i);
+ if (!characterIsLegal(c)) {
+ if ((mFlags & SPACE_OK) != 0) {
+ c = ' ';
+ }
+ else {
+ c = '_';
+ }
+ }
+ stringBuilder.append(c);
+ }
+ return stringBuilder.toString();
+ }
+
+ /**
+ * Trim whitespace from the beginning and end of a string.
+ * <p>
+ * Note: can't use {@link String#trim} because {@link String#trim} has a
+ * different definition of whitespace than we want.
+ * @param value the string to trim
+ * @return the trimmed string
+ */
+ private String trimWhitespace(String value) {
+ int start = 0;
+ int last = value.length() - 1;
+ int end = last;
+ while (start <= end && isWhitespace(value.charAt(start))) {
+ start++;
+ }
+ while (end >= start && isWhitespace(value.charAt(end))) {
+ end--;
+ }
+ if (start == 0 && end == last) {
+ return value;
+ }
+ return value.substring(start, end + 1);
+ }
+
+ /**
+ * Check if c is whitespace.
+ * @param c character to test
+ * @return true if c is a whitespace character
+ */
+ private boolean isWhitespace(char c) {
+ switch(c) {
+ case ' ':
+ case '\t':
+ case '\f':
+ case '\n':
+ case '\r':
+ case 11: /* VT */
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Check whether an individual character is legal. Uses the
+ * flag bit-set passed into the constructor.
+ * @param c
+ * @return true if c is a legal character
+ */
+ private boolean characterIsLegal(char c) {
+ switch(c) {
+ case ' ' : return (mFlags & SPACE_OK) != 0;
+ case '\t': case '\f': case '\n': case '\r': case 11: /* VT */
+ return (mFlags & OTHER_WHITESPACE_OK) != 0;
+ case '\"': return (mFlags & DQUOTE_OK) != 0;
+ case '\'': return (mFlags & SQUOTE_OK) != 0;
+ case '<' : return (mFlags & LT_OK) != 0;
+ case '>' : return (mFlags & GT_OK) != 0;
+ case '&' : return (mFlags & AMP_OK) != 0;
+ case '%' : return (mFlags & PCT_OK) != 0;
+ case '\0': return (mFlags & NUL_OK) != 0;
+ default : return (c >= 32 && c < 127) ||
+ ((c >= 128) && ((mFlags & NON_7_BIT_ASCII_OK) != 0));
+ }
+ }
+ }
+
+ /**
+ * Get the current value sanitizer used when processing
+ * unregistered parameter values.
+ * <p>
+ * <b>Note:</b> The default unregistered parameter value sanitizer is
+ * one that doesn't allow any special characters, similar to what
+ * is returned by calling createAllIllegal.
+ *
+ * @return the current ValueSanitizer used to sanitize unregistered
+ * parameter values.
+ */
+ public ValueSanitizer getUnregisteredParameterValueSanitizer() {
+ return mUnregisteredParameterValueSanitizer;
+ }
+
+ /**
+ * Set the value sanitizer used when processing unregistered
+ * parameter values.
+ * @param sanitizer set the ValueSanitizer used to sanitize unregistered
+ * parameter values.
+ */
+ public void setUnregisteredParameterValueSanitizer(
+ ValueSanitizer sanitizer) {
+ mUnregisteredParameterValueSanitizer = sanitizer;
+ }
+
+
+ // Private fields for singleton sanitizers:
+
+ private static final ValueSanitizer sAllIllegal =
+ new IllegalCharacterValueSanitizer(
+ IllegalCharacterValueSanitizer.ALL_ILLEGAL);
+
+ private static final ValueSanitizer sAllButNulLegal =
+ new IllegalCharacterValueSanitizer(
+ IllegalCharacterValueSanitizer.ALL_BUT_NUL_LEGAL);
+
+ private static final ValueSanitizer sAllButWhitespaceLegal =
+ new IllegalCharacterValueSanitizer(
+ IllegalCharacterValueSanitizer.ALL_BUT_WHITESPACE_LEGAL);
+
+ private static final ValueSanitizer sURLLegal =
+ new IllegalCharacterValueSanitizer(
+ IllegalCharacterValueSanitizer.URL_LEGAL);
+
+ private static final ValueSanitizer sUrlAndSpaceLegal =
+ new IllegalCharacterValueSanitizer(
+ IllegalCharacterValueSanitizer.URL_AND_SPACE_LEGAL);
+
+ private static final ValueSanitizer sAmpLegal =
+ new IllegalCharacterValueSanitizer(
+ IllegalCharacterValueSanitizer.AMP_LEGAL);
+
+ private static final ValueSanitizer sAmpAndSpaceLegal =
+ new IllegalCharacterValueSanitizer(
+ IllegalCharacterValueSanitizer.AMP_AND_SPACE_LEGAL);
+
+ private static final ValueSanitizer sSpaceLegal =
+ new IllegalCharacterValueSanitizer(
+ IllegalCharacterValueSanitizer.SPACE_LEGAL);
+
+ private static final ValueSanitizer sAllButNulAndAngleBracketsLegal =
+ new IllegalCharacterValueSanitizer(
+ IllegalCharacterValueSanitizer.ALL_BUT_NUL_AND_ANGLE_BRACKETS_LEGAL);
+
+ /**
+ * Return a value sanitizer that does not allow any special characters,
+ * and also does not allow script URLs.
+ * @return a value sanitizer
+ */
+ public static final ValueSanitizer getAllIllegal() {
+ return sAllIllegal;
+ }
+
+ /**
+ * Return a value sanitizer that allows everything except Nul ('\0')
+ * characters. Script URLs are allowed.
+ * @return a value sanitizer
+ */
+ public static final ValueSanitizer getAllButNulLegal() {
+ return sAllButNulLegal;
+ }
+ /**
+ * Return a value sanitizer that allows everything except Nul ('\0')
+ * characters, space (' '), and other whitespace characters.
+ * Script URLs are allowed.
+ * @return a value sanitizer
+ */
+ public static final ValueSanitizer getAllButWhitespaceLegal() {
+ return sAllButWhitespaceLegal;
+ }
+ /**
+ * Return a value sanitizer that allows all the characters used by
+ * encoded URLs. Does not allow script URLs.
+ * @return a value sanitizer
+ */
+ public static final ValueSanitizer getUrlLegal() {
+ return sURLLegal;
+ }
+ /**
+ * Return a value sanitizer that allows all the characters used by
+ * encoded URLs and allows spaces, which are not technically legal
+ * in encoded URLs, but commonly appear anyway.
+ * Does not allow script URLs.
+ * @return a value sanitizer
+ */
+ public static final ValueSanitizer getUrlAndSpaceLegal() {
+ return sUrlAndSpaceLegal;
+ }
+ /**
+ * Return a value sanitizer that does not allow any special characters
+ * except ampersand ('&'). Does not allow script URLs.
+ * @return a value sanitizer
+ */
+ public static final ValueSanitizer getAmpLegal() {
+ return sAmpLegal;
+ }
+ /**
+ * Return a value sanitizer that does not allow any special characters
+ * except ampersand ('&') and space (' '). Does not allow script URLs.
+ * @return a value sanitizer
+ */
+ public static final ValueSanitizer getAmpAndSpaceLegal() {
+ return sAmpAndSpaceLegal;
+ }
+ /**
+ * Return a value sanitizer that does not allow any special characters
+ * except space (' '). Does not allow script URLs.
+ * @return a value sanitizer
+ */
+ public static final ValueSanitizer getSpaceLegal() {
+ return sSpaceLegal;
+ }
+ /**
+ * Return a value sanitizer that allows any special characters
+ * except angle brackets ('<' and '>') and Nul ('\0').
+ * Allows script URLs.
+ * @return a value sanitizer
+ */
+ public static final ValueSanitizer getAllButNulAndAngleBracketsLegal() {
+ return sAllButNulAndAngleBracketsLegal;
+ }
+
+ /**
+ * Constructs a UrlQuerySanitizer.
+ * <p>
+ * Defaults:
+ * <ul>
+ * <li>unregistered parameters are not allowed.
+ * <li>the last instance of a repeated parameter is preferred.
+ * <li>The default value sanitizer is an AllIllegal value sanitizer.
+ * <ul>
+ */
+ public UrlQuerySanitizer() {
+ }
+
+ /**
+ * Constructs a UrlQuerySanitizer and parses a URL.
+ * This constructor is provided for convenience when the
+ * default parsing behavior is acceptable.
+ * <p>
+ * Because the URL is parsed before the constructor returns, there isn't
+ * a chance to configure the sanitizer to change the parsing behavior.
+ * <p>
+ * <code>
+ * UrlQuerySanitizer sanitizer = new UrlQuerySanitizer(myUrl);
+ * String name = sanitizer.getValue("name");
+ * </code>
+ * <p>
+ * Defaults:
+ * <ul>
+ * <li>unregistered parameters <em>are</em> allowed.
+ * <li>the last instance of a repeated parameter is preferred.
+ * <li>The default value sanitizer is an AllIllegal value sanitizer.
+ * <ul>
+ */
+ public UrlQuerySanitizer(String url) {
+ setAllowUnregisteredParamaters(true);
+ parseUrl(url);
+ }
+
+ /**
+ * Parse the query parameters out of an encoded URL.
+ * Works by extracting the query portion from the URL and then
+ * calling parseQuery(). If there is no query portion it is
+ * treated as if the query portion is an empty string.
+ * @param url the encoded URL to parse.
+ */
+ public void parseUrl(String url) {
+ int queryIndex = url.indexOf('?');
+ String query;
+ if (queryIndex >= 0) {
+ query = url.substring(queryIndex + 1);
+ }
+ else {
+ query = "";
+ }
+ parseQuery(query);
+ }
+
+ /**
+ * Parse a query. A query string is any number of parameter-value clauses
+ * separated by any non-zero number of ampersands. A parameter-value clause
+ * is a parameter followed by an equal sign, followed by a value. If the
+ * equal sign is missing, the value is assumed to be the empty string.
+ * @param query the query to parse.
+ */
+ public void parseQuery(String query) {
+ clear();
+ // Split by '&'
+ StringTokenizer tokenizer = new StringTokenizer(query, "&");
+ while(tokenizer.hasMoreElements()) {
+ String attributeValuePair = tokenizer.nextToken();
+ if (attributeValuePair.length() > 0) {
+ int assignmentIndex = attributeValuePair.indexOf('=');
+ if (assignmentIndex < 0) {
+ // No assignment found, treat as if empty value
+ parseEntry(attributeValuePair, "");
+ }
+ else {
+ parseEntry(attributeValuePair.substring(0, assignmentIndex),
+ attributeValuePair.substring(assignmentIndex + 1));
+ }
+ }
+ }
+ }
+
+ /**
+ * Get a set of all of the parameters found in the sanitized query.
+ * <p>
+ * Note: Do not modify this set. Treat it as a read-only set.
+ * @return all the parameters found in the current query.
+ */
+ public Set<String> getParameterSet() {
+ return mEntries.keySet();
+ }
+
+ /**
+ * An array list of all of the parameter-value pairs in the sanitized
+ * query, in the order they appeared in the query. May contain duplicate
+ * parameters.
+ * <p class="note"><b>Note:</b> Do not modify this list. Treat it as a read-only list.</p>
+ */
+ public List<ParameterValuePair> getParameterList() {
+ return mEntriesList;
+ }
+
+ /**
+ * Check if a parameter exists in the current sanitized query.
+ * @param parameter the unencoded name of a parameter.
+ * @return true if the parameter exists in the current sanitized queary.
+ */
+ public boolean hasParameter(String parameter) {
+ return mEntries.containsKey(parameter);
+ }
+
+ /**
+ * Get the value for a parameter in the current sanitized query.
+ * Returns null if the parameter does not
+ * exit.
+ * @param parameter the unencoded name of a parameter.
+ * @return the sanitized unencoded value of the parameter,
+ * or null if the parameter does not exist.
+ */
+ public String getValue(String parameter) {
+ return mEntries.get(parameter);
+ }
+
+ /**
+ * Register a value sanitizer for a particular parameter. Can also be used
+ * to replace or remove an already-set value sanitizer.
+ * <p>
+ * Registering a non-null value sanitizer for a particular parameter
+ * makes that parameter a registered parameter.
+ * @param parameter an unencoded parameter name
+ * @param valueSanitizer the value sanitizer to use for a particular
+ * parameter. May be null in order to unregister that parameter.
+ * @see #getAllowUnregisteredParamaters()
+ */
+ public void registerParameter(String parameter,
+ ValueSanitizer valueSanitizer) {
+ if (valueSanitizer == null) {
+ mSanitizers.remove(parameter);
+ }
+ mSanitizers.put(parameter, valueSanitizer);
+ }
+
+ /**
+ * Register a value sanitizer for an array of parameters.
+ * @param parameters An array of unencoded parameter names.
+ * @param valueSanitizer
+ * @see #registerParameter
+ */
+ public void registerParameters(String[] parameters,
+ ValueSanitizer valueSanitizer) {
+ int length = parameters.length;
+ for(int i = 0; i < length; i++) {
+ mSanitizers.put(parameters[i], valueSanitizer);
+ }
+ }
+
+ /**
+ * Set whether or not unregistered parameters are allowed. If they
+ * are not allowed, then they will be dropped when a query is sanitized.
+ * <p>
+ * Defaults to false.
+ * @param allowUnregisteredParamaters true to allow unregistered parameters.
+ * @see #getAllowUnregisteredParamaters()
+ */
+ public void setAllowUnregisteredParamaters(
+ boolean allowUnregisteredParamaters) {
+ mAllowUnregisteredParamaters = allowUnregisteredParamaters;
+ }
+
+ /**
+ * Get whether or not unregistered parameters are allowed. If not
+ * allowed, they will be dropped when a query is parsed.
+ * @return true if unregistered parameters are allowed.
+ * @see #setAllowUnregisteredParamaters(boolean)
+ */
+ public boolean getAllowUnregisteredParamaters() {
+ return mAllowUnregisteredParamaters;
+ }
+
+ /**
+ * Set whether or not the first occurrence of a repeated parameter is
+ * preferred. True means the first repeated parameter is preferred.
+ * False means that the last repeated parameter is preferred.
+ * <p>
+ * The preferred parameter is the one that is returned when getParameter
+ * is called.
+ * <p>
+ * defaults to false.
+ * @param preferFirstRepeatedParameter True if the first repeated
+ * parameter is preferred.
+ * @see #getPreferFirstRepeatedParameter()
+ */
+ public void setPreferFirstRepeatedParameter(
+ boolean preferFirstRepeatedParameter) {
+ mPreferFirstRepeatedParameter = preferFirstRepeatedParameter;
+ }
+
+ /**
+ * Get whether or not the first occurrence of a repeated parameter is
+ * preferred.
+ * @return true if the first occurrence of a repeated parameter is
+ * preferred.
+ * @see #setPreferFirstRepeatedParameter(boolean)
+ */
+ public boolean getPreferFirstRepeatedParameter() {
+ return mPreferFirstRepeatedParameter;
+ }
+
+ /**
+ * Parse an escaped parameter-value pair. The default implementation
+ * unescapes both the parameter and the value, then looks up the
+ * effective value sanitizer for the parameter and uses it to sanitize
+ * the value. If all goes well then addSanitizedValue is called with
+ * the unescaped parameter and the sanitized unescaped value.
+ * @param parameter an escaped parameter
+ * @param value an unsanitized escaped value
+ */
+ protected void parseEntry(String parameter, String value) {
+ String unescapedParameter = unescape(parameter);
+ ValueSanitizer valueSanitizer =
+ getEffectiveValueSanitizer(unescapedParameter);
+
+ if (valueSanitizer == null) {
+ return;
+ }
+ String unescapedValue = unescape(value);
+ String sanitizedValue = valueSanitizer.sanitize(unescapedValue);
+ addSanitizedEntry(unescapedParameter, sanitizedValue);
+ }
+
+ /**
+ * Record a sanitized parameter-value pair. Override if you want to
+ * do additional filtering or validation.
+ * @param parameter an unescaped parameter
+ * @param value a sanitized unescaped value
+ */
+ protected void addSanitizedEntry(String parameter, String value) {
+ mEntriesList.add(
+ new ParameterValuePair(parameter, value));
+ if (mPreferFirstRepeatedParameter) {
+ if (mEntries.containsKey(parameter)) {
+ return;
+ }
+ }
+ mEntries.put(parameter, value);
+ }
+
+ /**
+ * Get the value sanitizer for a parameter. Returns null if there
+ * is no value sanitizer registered for the parameter.
+ * @param parameter the unescaped parameter
+ * @return the currently registered value sanitizer for this parameter.
+ * @see #registerParameter(String, android.net.UrlQuerySanitizer.ValueSanitizer)
+ */
+ public ValueSanitizer getValueSanitizer(String parameter) {
+ return mSanitizers.get(parameter);
+ }
+
+ /**
+ * Get the effective value sanitizer for a parameter. Like getValueSanitizer,
+ * except if there is no value sanitizer registered for a parameter, and
+ * unregistered parameters are allowed, then the default value sanitizer is
+ * returned.
+ * @param parameter an unescaped parameter
+ * @return the effective value sanitizer for a parameter.
+ */
+ public ValueSanitizer getEffectiveValueSanitizer(String parameter) {
+ ValueSanitizer sanitizer = getValueSanitizer(parameter);
+ if (sanitizer == null && mAllowUnregisteredParamaters) {
+ sanitizer = getUnregisteredParameterValueSanitizer();
+ }
+ return sanitizer;
+ }
+
+ /**
+ * Unescape an escaped string.
+ * <ul>
+ * <li>'+' characters are replaced by
+ * ' ' characters.
+ * <li>Valid "%xx" escape sequences are replaced by the
+ * corresponding unescaped character.
+ * <li>Invalid escape sequences such as %1z", are passed through unchanged.
+ * <ol>
+ * @param string the escaped string
+ * @return the unescaped string.
+ */
+ private static final Pattern plusOrPercent = Pattern.compile("[+%]");
+ public String unescape(String string) {
+ final Matcher matcher = plusOrPercent.matcher(string);
+ if (!matcher.find()) return string;
+ final int firstEscape = matcher.start();
+
+ int length = string.length();
+
+ StringBuilder stringBuilder = new StringBuilder(length);
+ stringBuilder.append(string.substring(0, firstEscape));
+ for (int i = firstEscape; i < length; i++) {
+ char c = string.charAt(i);
+ if (c == '+') {
+ c = ' ';
+ } else if (c == '%' && i + 2 < length) {
+ char c1 = string.charAt(i + 1);
+ char c2 = string.charAt(i + 2);
+ if (isHexDigit(c1) && isHexDigit(c2)) {
+ c = (char) (decodeHexDigit(c1) * 16 + decodeHexDigit(c2));
+ i += 2;
+ }
+ }
+ stringBuilder.append(c);
+ }
+ return stringBuilder.toString();
+ }
+
+ /**
+ * Test if a character is a hexidecimal digit. Both upper case and lower
+ * case hex digits are allowed.
+ * @param c the character to test
+ * @return true if c is a hex digit.
+ */
+ protected boolean isHexDigit(char c) {
+ return decodeHexDigit(c) >= 0;
+ }
+
+ /**
+ * Convert a character that represents a hexidecimal digit into an integer.
+ * If the character is not a hexidecimal digit, then -1 is returned.
+ * Both upper case and lower case hex digits are allowed.
+ * @param c the hexidecimal digit.
+ * @return the integer value of the hexidecimal digit.
+ */
+
+ protected int decodeHexDigit(char c) {
+ if (c >= '0' && c <= '9') {
+ return c - '0';
+ }
+ else if (c >= 'A' && c <= 'F') {
+ return c - 'A' + 10;
+ }
+ else if (c >= 'a' && c <= 'f') {
+ return c - 'a' + 10;
+ }
+ else {
+ return -1;
+ }
+ }
+
+ /**
+ * Clear the existing entries. Called to get ready to parse a new
+ * query string.
+ */
+ protected void clear() {
+ mEntries.clear();
+ mEntriesList.clear();
+ }
+}
+
diff --git a/android/net/VpnService.java b/android/net/VpnService.java
new file mode 100644
index 0000000..ed7cddc
--- /dev/null
+++ b/android/net/VpnService.java
@@ -0,0 +1,903 @@
+/*
+ * Copyright (C) 2011 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;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.UnsupportedAppUsage;
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+
+import com.android.internal.net.VpnConfig;
+
+import java.net.DatagramSocket;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * VpnService is a base class for applications to extend and build their
+ * own VPN solutions. In general, it creates a virtual network interface,
+ * configures addresses and routing rules, and returns a file descriptor
+ * to the application. Each read from the descriptor retrieves an outgoing
+ * packet which was routed to the interface. Each write to the descriptor
+ * injects an incoming packet just like it was received from the interface.
+ * The interface is running on Internet Protocol (IP), so packets are
+ * always started with IP headers. The application then completes a VPN
+ * connection by processing and exchanging packets with the remote server
+ * over a tunnel.
+ *
+ * <p>Letting applications intercept packets raises huge security concerns.
+ * A VPN application can easily break the network. Besides, two of them may
+ * conflict with each other. The system takes several actions to address
+ * these issues. Here are some key points:
+ * <ul>
+ * <li>User action is required the first time an application creates a VPN
+ * connection.</li>
+ * <li>There can be only one VPN connection running at the same time. The
+ * existing interface is deactivated when a new one is created.</li>
+ * <li>A system-managed notification is shown during the lifetime of a
+ * VPN connection.</li>
+ * <li>A system-managed dialog gives the information of the current VPN
+ * connection. It also provides a button to disconnect.</li>
+ * <li>The network is restored automatically when the file descriptor is
+ * closed. It also covers the cases when a VPN application is crashed
+ * or killed by the system.</li>
+ * </ul>
+ *
+ * <p>There are two primary methods in this class: {@link #prepare} and
+ * {@link Builder#establish}. The former deals with user action and stops
+ * the VPN connection created by another application. The latter creates
+ * a VPN interface using the parameters supplied to the {@link Builder}.
+ * An application must call {@link #prepare} to grant the right to use
+ * other methods in this class, and the right can be revoked at any time.
+ * Here are the general steps to create a VPN connection:
+ * <ol>
+ * <li>When the user presses the button to connect, call {@link #prepare}
+ * and launch the returned intent, if non-null.</li>
+ * <li>When the application becomes prepared, start the service.</li>
+ * <li>Create a tunnel to the remote server and negotiate the network
+ * parameters for the VPN connection.</li>
+ * <li>Supply those parameters to a {@link Builder} and create a VPN
+ * interface by calling {@link Builder#establish}.</li>
+ * <li>Process and exchange packets between the tunnel and the returned
+ * file descriptor.</li>
+ * <li>When {@link #onRevoke} is invoked, close the file descriptor and
+ * shut down the tunnel gracefully.</li>
+ * </ol>
+ *
+ * <p>Services extending this class need to be declared with an appropriate
+ * permission and intent filter. Their access must be secured by
+ * {@link android.Manifest.permission#BIND_VPN_SERVICE} permission, and
+ * their intent filter must match {@link #SERVICE_INTERFACE} action. Here
+ * is an example of declaring a VPN service in {@code AndroidManifest.xml}:
+ * <pre>
+ * <service android:name=".ExampleVpnService"
+ * android:permission="android.permission.BIND_VPN_SERVICE">
+ * <intent-filter>
+ * <action android:name="android.net.VpnService"/>
+ * </intent-filter>
+ * </service></pre>
+ *
+ * <p> The Android system starts a VPN in the background by calling
+ * {@link android.content.Context#startService startService()}. In Android 8.0
+ * (API level 26) and higher, the system places VPN apps on the temporary
+ * whitelist for a short period so the app can start in the background. The VPN
+ * app must promote itself to the foreground after it's launched or the system
+ * will shut down the app.
+ *
+ * <h3>Developer's guide</h3>
+ *
+ * <p>To learn more about developing VPN apps, read the
+ * <a href="{@docRoot}guide/topics/connectivity/vpn">VPN developer's guide</a>.
+ *
+ * @see Builder
+ */
+public class VpnService extends Service {
+
+ /**
+ * The action must be matched by the intent filter of this service. It also
+ * needs to require {@link android.Manifest.permission#BIND_VPN_SERVICE}
+ * permission so that other applications cannot abuse it.
+ */
+ public static final String SERVICE_INTERFACE = VpnConfig.SERVICE_INTERFACE;
+
+ /**
+ * Key for boolean meta-data field indicating whether this VpnService supports always-on mode.
+ *
+ * <p>For a VPN app targeting {@link android.os.Build.VERSION_CODES#N API 24} or above, Android
+ * provides users with the ability to set it as always-on, so that VPN connection is
+ * persisted after device reboot and app upgrade. Always-on VPN can also be enabled by device
+ * owner and profile owner apps through
+ * {@link DevicePolicyManager#setAlwaysOnVpnPackage}.
+ *
+ * <p>VPN apps not supporting this feature should opt out by adding this meta-data field to the
+ * {@code VpnService} component of {@code AndroidManifest.xml}. In case there is more than one
+ * {@code VpnService} component defined in {@code AndroidManifest.xml}, opting out any one of
+ * them will opt out the entire app. For example,
+ * <pre> {@code
+ * <service android:name=".ExampleVpnService"
+ * android:permission="android.permission.BIND_VPN_SERVICE">
+ * <intent-filter>
+ * <action android:name="android.net.VpnService"/>
+ * </intent-filter>
+ * <meta-data android:name="android.net.VpnService.SUPPORTS_ALWAYS_ON"
+ * android:value=false/>
+ * </service>
+ * } </pre>
+ *
+ * <p>This meta-data field defaults to {@code true} if absent. It will only have effect on
+ * {@link android.os.Build.VERSION_CODES#O_MR1} or higher.
+ */
+ public static final String SERVICE_META_DATA_SUPPORTS_ALWAYS_ON =
+ "android.net.VpnService.SUPPORTS_ALWAYS_ON";
+
+ /**
+ * Use IConnectivityManager since those methods are hidden and not
+ * available in ConnectivityManager.
+ */
+ private static IConnectivityManager getService() {
+ return IConnectivityManager.Stub.asInterface(
+ ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
+ }
+
+ /**
+ * Prepare to establish a VPN connection. This method returns {@code null}
+ * if the VPN application is already prepared or if the user has previously
+ * consented to the VPN application. Otherwise, it returns an
+ * {@link Intent} to a system activity. The application should launch the
+ * activity using {@link Activity#startActivityForResult} to get itself
+ * prepared. The activity may pop up a dialog to require user action, and
+ * the result will come back via its {@link Activity#onActivityResult}.
+ * If the result is {@link Activity#RESULT_OK}, the application becomes
+ * prepared and is granted to use other methods in this class.
+ *
+ * <p>Only one application can be granted at the same time. The right
+ * is revoked when another application is granted. The application
+ * losing the right will be notified via its {@link #onRevoke}. Unless
+ * it becomes prepared again, subsequent calls to other methods in this
+ * class will fail.
+ *
+ * <p>The user may disable the VPN at any time while it is activated, in
+ * which case this method will return an intent the next time it is
+ * executed to obtain the user's consent again.
+ *
+ * @see #onRevoke
+ */
+ public static Intent prepare(Context context) {
+ try {
+ if (getService().prepareVpn(context.getPackageName(), null, context.getUserId())) {
+ return null;
+ }
+ } catch (RemoteException e) {
+ // ignore
+ }
+ return VpnConfig.getIntentForConfirmation();
+ }
+
+ /**
+ * Version of {@link #prepare(Context)} which does not require user consent.
+ *
+ * <p>Requires {@link android.Manifest.permission#CONTROL_VPN} and should generally not be
+ * used. Only acceptable in situations where user consent has been obtained through other means.
+ *
+ * <p>Once this is run, future preparations may be done with the standard prepare method as this
+ * will authorize the package to prepare the VPN without consent in the future.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.CONTROL_VPN)
+ public static void prepareAndAuthorize(Context context) {
+ IConnectivityManager cm = getService();
+ String packageName = context.getPackageName();
+ try {
+ // Only prepare if we're not already prepared.
+ int userId = context.getUserId();
+ if (!cm.prepareVpn(packageName, null, userId)) {
+ cm.prepareVpn(null, packageName, userId);
+ }
+ cm.setVpnPackageAuthorization(packageName, userId, true);
+ } catch (RemoteException e) {
+ // ignore
+ }
+ }
+
+ /**
+ * Protect a socket from VPN connections. After protecting, data sent
+ * through this socket will go directly to the underlying network,
+ * so its traffic will not be forwarded through the VPN.
+ * This method is useful if some connections need to be kept
+ * outside of VPN. For example, a VPN tunnel should protect itself if its
+ * destination is covered by VPN routes. Otherwise its outgoing packets
+ * will be sent back to the VPN interface and cause an infinite loop. This
+ * method will fail if the application is not prepared or is revoked.
+ *
+ * <p class="note">The socket is NOT closed by this method.
+ *
+ * @return {@code true} on success.
+ */
+ public boolean protect(int socket) {
+ return NetworkUtils.protectFromVpn(socket);
+ }
+
+ /**
+ * Convenience method to protect a {@link Socket} from VPN connections.
+ *
+ * @return {@code true} on success.
+ * @see #protect(int)
+ */
+ public boolean protect(Socket socket) {
+ return protect(socket.getFileDescriptor$().getInt$());
+ }
+
+ /**
+ * Convenience method to protect a {@link DatagramSocket} from VPN
+ * connections.
+ *
+ * @return {@code true} on success.
+ * @see #protect(int)
+ */
+ public boolean protect(DatagramSocket socket) {
+ return protect(socket.getFileDescriptor$().getInt$());
+ }
+
+ /**
+ * Adds a network address to the VPN interface.
+ *
+ * Both IPv4 and IPv6 addresses are supported. The VPN must already be established. Fails if the
+ * address is already in use or cannot be assigned to the interface for any other reason.
+ *
+ * Adding an address implicitly allows traffic from that address family (i.e., IPv4 or IPv6) to
+ * be routed over the VPN. @see Builder#allowFamily
+ *
+ * @throws IllegalArgumentException if the address is invalid.
+ *
+ * @param address The IP address (IPv4 or IPv6) to assign to the VPN interface.
+ * @param prefixLength The prefix length of the address.
+ *
+ * @return {@code true} on success.
+ * @see Builder#addAddress
+ *
+ * @hide
+ */
+ public boolean addAddress(InetAddress address, int prefixLength) {
+ check(address, prefixLength);
+ try {
+ return getService().addVpnAddress(address.getHostAddress(), prefixLength);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /**
+ * Removes a network address from the VPN interface.
+ *
+ * Both IPv4 and IPv6 addresses are supported. The VPN must already be established. Fails if the
+ * address is not assigned to the VPN interface, or if it is the only address assigned (thus
+ * cannot be removed), or if the address cannot be removed for any other reason.
+ *
+ * After removing an address, if there are no addresses, routes or DNS servers of a particular
+ * address family (i.e., IPv4 or IPv6) configured on the VPN, that <b>DOES NOT</b> block that
+ * family from being routed. In other words, once an address family has been allowed, it stays
+ * allowed for the rest of the VPN's session. @see Builder#allowFamily
+ *
+ * @throws IllegalArgumentException if the address is invalid.
+ *
+ * @param address The IP address (IPv4 or IPv6) to assign to the VPN interface.
+ * @param prefixLength The prefix length of the address.
+ *
+ * @return {@code true} on success.
+ *
+ * @hide
+ */
+ public boolean removeAddress(InetAddress address, int prefixLength) {
+ check(address, prefixLength);
+ try {
+ return getService().removeVpnAddress(address.getHostAddress(), prefixLength);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /**
+ * Sets the underlying networks used by the VPN for its upstream connections.
+ *
+ * <p>Used by the system to know the actual networks that carry traffic for apps affected by
+ * this VPN in order to present this information to the user (e.g., via status bar icons).
+ *
+ * <p>This method only needs to be called if the VPN has explicitly bound its underlying
+ * communications channels — such as the socket(s) passed to {@link #protect(int)} —
+ * to a {@code Network} using APIs such as {@link Network#bindSocket(Socket)} or
+ * {@link Network#bindSocket(DatagramSocket)}. The VPN should call this method every time
+ * the set of {@code Network}s it is using changes.
+ *
+ * <p>{@code networks} is one of the following:
+ * <ul>
+ * <li><strong>a non-empty array</strong>: an array of one or more {@link Network}s, in
+ * decreasing preference order. For example, if this VPN uses both wifi and mobile (cellular)
+ * networks to carry app traffic, but prefers or uses wifi more than mobile, wifi should appear
+ * first in the array.</li>
+ * <li><strong>an empty array</strong>: a zero-element array, meaning that the VPN has no
+ * underlying network connection, and thus, app traffic will not be sent or received.</li>
+ * <li><strong>null</strong>: (default) signifies that the VPN uses whatever is the system's
+ * default network. I.e., it doesn't use the {@code bindSocket} or {@code bindDatagramSocket}
+ * APIs mentioned above to send traffic over specific channels.</li>
+ * </ul>
+ *
+ * <p>This call will succeed only if the VPN is currently established. For setting this value
+ * when the VPN has not yet been established, see {@link Builder#setUnderlyingNetworks}.
+ *
+ * @param networks An array of networks the VPN uses to tunnel traffic to/from its servers.
+ *
+ * @return {@code true} on success.
+ */
+ public boolean setUnderlyingNetworks(Network[] networks) {
+ try {
+ return getService().setUnderlyingNetworksForVpn(networks);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /**
+ * Returns whether the service is running in always-on VPN mode. In this mode the system ensures
+ * that the service is always running by restarting it when necessary, e.g. after reboot.
+ *
+ * @see DevicePolicyManager#setAlwaysOnVpnPackage(ComponentName, String, boolean, Set)
+ */
+ public final boolean isAlwaysOn() {
+ try {
+ return getService().isCallerCurrentAlwaysOnVpnApp();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether the service is running in always-on VPN lockdown mode. In this mode the
+ * system ensures that the service is always running and that the apps aren't allowed to bypass
+ * the VPN.
+ *
+ * @see DevicePolicyManager#setAlwaysOnVpnPackage(ComponentName, String, boolean, Set)
+ */
+ public final boolean isLockdownEnabled() {
+ try {
+ return getService().isCallerCurrentAlwaysOnVpnLockdownApp();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return the communication interface to the service. This method returns
+ * {@code null} on {@link Intent}s other than {@link #SERVICE_INTERFACE}
+ * action. Applications overriding this method must identify the intent
+ * and return the corresponding interface accordingly.
+ *
+ * @see Service#onBind
+ */
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (intent != null && SERVICE_INTERFACE.equals(intent.getAction())) {
+ return new Callback();
+ }
+ return null;
+ }
+
+ /**
+ * Invoked when the application is revoked. At this moment, the VPN
+ * interface is already deactivated by the system. The application should
+ * close the file descriptor and shut down gracefully. The default
+ * implementation of this method is calling {@link Service#stopSelf()}.
+ *
+ * <p class="note">Calls to this method may not happen on the main thread
+ * of the process.
+ *
+ * @see #prepare
+ */
+ public void onRevoke() {
+ stopSelf();
+ }
+
+ /**
+ * Use raw Binder instead of AIDL since now there is only one usage.
+ */
+ private class Callback extends Binder {
+ @Override
+ protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) {
+ if (code == IBinder.LAST_CALL_TRANSACTION) {
+ onRevoke();
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Private method to validate address and prefixLength.
+ */
+ private static void check(InetAddress address, int prefixLength) {
+ if (address.isLoopbackAddress()) {
+ throw new IllegalArgumentException("Bad address");
+ }
+ if (address instanceof Inet4Address) {
+ if (prefixLength < 0 || prefixLength > 32) {
+ throw new IllegalArgumentException("Bad prefixLength");
+ }
+ } else if (address instanceof Inet6Address) {
+ if (prefixLength < 0 || prefixLength > 128) {
+ throw new IllegalArgumentException("Bad prefixLength");
+ }
+ } else {
+ throw new IllegalArgumentException("Unsupported family");
+ }
+ }
+
+ /**
+ * Helper class to create a VPN interface. This class should be always
+ * used within the scope of the outer {@link VpnService}.
+ *
+ * @see VpnService
+ */
+ public class Builder {
+
+ private final VpnConfig mConfig = new VpnConfig();
+ @UnsupportedAppUsage
+ private final List<LinkAddress> mAddresses = new ArrayList<LinkAddress>();
+ @UnsupportedAppUsage
+ private final List<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
+
+ public Builder() {
+ mConfig.user = VpnService.this.getClass().getName();
+ }
+
+ /**
+ * Set the name of this session. It will be displayed in
+ * system-managed dialogs and notifications. This is recommended
+ * not required.
+ */
+ @NonNull
+ public Builder setSession(@NonNull String session) {
+ mConfig.session = session;
+ return this;
+ }
+
+ /**
+ * Set the {@link PendingIntent} to an activity for users to
+ * configure the VPN connection. If it is not set, the button
+ * to configure will not be shown in system-managed dialogs.
+ */
+ @NonNull
+ public Builder setConfigureIntent(@NonNull PendingIntent intent) {
+ mConfig.configureIntent = intent;
+ return this;
+ }
+
+ /**
+ * Set the maximum transmission unit (MTU) of the VPN interface. If
+ * it is not set, the default value in the operating system will be
+ * used.
+ *
+ * @throws IllegalArgumentException if the value is not positive.
+ */
+ @NonNull
+ public Builder setMtu(int mtu) {
+ if (mtu <= 0) {
+ throw new IllegalArgumentException("Bad mtu");
+ }
+ mConfig.mtu = mtu;
+ return this;
+ }
+
+ /**
+ * Sets an HTTP proxy for the VPN network. This proxy is only a recommendation
+ * and it is possible that some apps will ignore it.
+ */
+ @NonNull
+ public Builder setHttpProxy(@NonNull ProxyInfo proxyInfo) {
+ mConfig.proxyInfo = proxyInfo;
+ return this;
+ }
+
+ /**
+ * Add a network address to the VPN interface. Both IPv4 and IPv6
+ * addresses are supported. At least one address must be set before
+ * calling {@link #establish}.
+ *
+ * Adding an address implicitly allows traffic from that address family
+ * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
+ *
+ * @throws IllegalArgumentException if the address is invalid.
+ */
+ @NonNull
+ public Builder addAddress(@NonNull InetAddress address, int prefixLength) {
+ check(address, prefixLength);
+
+ if (address.isAnyLocalAddress()) {
+ throw new IllegalArgumentException("Bad address");
+ }
+ mAddresses.add(new LinkAddress(address, prefixLength));
+ mConfig.updateAllowedFamilies(address);
+ return this;
+ }
+
+ /**
+ * Convenience method to add a network address to the VPN interface
+ * using a numeric address string. See {@link InetAddress} for the
+ * definitions of numeric address formats.
+ *
+ * Adding an address implicitly allows traffic from that address family
+ * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
+ *
+ * @throws IllegalArgumentException if the address is invalid.
+ * @see #addAddress(InetAddress, int)
+ */
+ @NonNull
+ public Builder addAddress(@NonNull String address, int prefixLength) {
+ return addAddress(InetAddress.parseNumericAddress(address), prefixLength);
+ }
+
+ /**
+ * Add a network route to the VPN interface. Both IPv4 and IPv6
+ * routes are supported.
+ *
+ * Adding a route implicitly allows traffic from that address family
+ * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
+ *
+ * @throws IllegalArgumentException if the route is invalid.
+ */
+ @NonNull
+ public Builder addRoute(@NonNull InetAddress address, int prefixLength) {
+ check(address, prefixLength);
+
+ int offset = prefixLength / 8;
+ byte[] bytes = address.getAddress();
+ if (offset < bytes.length) {
+ for (bytes[offset] <<= prefixLength % 8; offset < bytes.length; ++offset) {
+ if (bytes[offset] != 0) {
+ throw new IllegalArgumentException("Bad address");
+ }
+ }
+ }
+ mRoutes.add(new RouteInfo(new IpPrefix(address, prefixLength), null));
+ mConfig.updateAllowedFamilies(address);
+ return this;
+ }
+
+ /**
+ * Convenience method to add a network route to the VPN interface
+ * using a numeric address string. See {@link InetAddress} for the
+ * definitions of numeric address formats.
+ *
+ * Adding a route implicitly allows traffic from that address family
+ * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
+ *
+ * @throws IllegalArgumentException if the route is invalid.
+ * @see #addRoute(InetAddress, int)
+ */
+ @NonNull
+ public Builder addRoute(@NonNull String address, int prefixLength) {
+ return addRoute(InetAddress.parseNumericAddress(address), prefixLength);
+ }
+
+ /**
+ * Add a DNS server to the VPN connection. Both IPv4 and IPv6
+ * addresses are supported. If none is set, the DNS servers of
+ * the default network will be used.
+ *
+ * Adding a server implicitly allows traffic from that address family
+ * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
+ *
+ * @throws IllegalArgumentException if the address is invalid.
+ */
+ @NonNull
+ public Builder addDnsServer(@NonNull InetAddress address) {
+ if (address.isLoopbackAddress() || address.isAnyLocalAddress()) {
+ throw new IllegalArgumentException("Bad address");
+ }
+ if (mConfig.dnsServers == null) {
+ mConfig.dnsServers = new ArrayList<String>();
+ }
+ mConfig.dnsServers.add(address.getHostAddress());
+ return this;
+ }
+
+ /**
+ * Convenience method to add a DNS server to the VPN connection
+ * using a numeric address string. See {@link InetAddress} for the
+ * definitions of numeric address formats.
+ *
+ * Adding a server implicitly allows traffic from that address family
+ * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily
+ *
+ * @throws IllegalArgumentException if the address is invalid.
+ * @see #addDnsServer(InetAddress)
+ */
+ @NonNull
+ public Builder addDnsServer(@NonNull String address) {
+ return addDnsServer(InetAddress.parseNumericAddress(address));
+ }
+
+ /**
+ * Add a search domain to the DNS resolver.
+ */
+ @NonNull
+ public Builder addSearchDomain(@NonNull String domain) {
+ if (mConfig.searchDomains == null) {
+ mConfig.searchDomains = new ArrayList<String>();
+ }
+ mConfig.searchDomains.add(domain);
+ return this;
+ }
+
+ /**
+ * Allows traffic from the specified address family.
+ *
+ * By default, if no address, route or DNS server of a specific family (IPv4 or IPv6) is
+ * added to this VPN, then all outgoing traffic of that family is blocked. If any address,
+ * route or DNS server is added, that family is allowed.
+ *
+ * This method allows an address family to be unblocked even without adding an address,
+ * route or DNS server of that family. Traffic of that family will then typically
+ * fall-through to the underlying network if it's supported.
+ *
+ * {@code family} must be either {@code AF_INET} (for IPv4) or {@code AF_INET6} (for IPv6).
+ * {@link IllegalArgumentException} is thrown if it's neither.
+ *
+ * @param family The address family ({@code AF_INET} or {@code AF_INET6}) to allow.
+ *
+ * @return this {@link Builder} object to facilitate chaining of method calls.
+ */
+ @NonNull
+ public Builder allowFamily(int family) {
+ if (family == AF_INET) {
+ mConfig.allowIPv4 = true;
+ } else if (family == AF_INET6) {
+ mConfig.allowIPv6 = true;
+ } else {
+ throw new IllegalArgumentException(family + " is neither " + AF_INET + " nor " +
+ AF_INET6);
+ }
+ return this;
+ }
+
+ private void verifyApp(String packageName) throws PackageManager.NameNotFoundException {
+ IPackageManager pm = IPackageManager.Stub.asInterface(
+ ServiceManager.getService("package"));
+ try {
+ pm.getApplicationInfo(packageName, 0, UserHandle.getCallingUserId());
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /**
+ * Adds an application that's allowed to access the VPN connection.
+ *
+ * If this method is called at least once, only applications added through this method (and
+ * no others) are allowed access. Else (if this method is never called), all applications
+ * are allowed by default. If some applications are added, other, un-added applications
+ * will use networking as if the VPN wasn't running.
+ *
+ * A {@link Builder} may have only a set of allowed applications OR a set of disallowed
+ * ones, but not both. Calling this method after {@link #addDisallowedApplication} has
+ * already been called, or vice versa, will throw an {@link UnsupportedOperationException}.
+ *
+ * {@code packageName} must be the canonical name of a currently installed application.
+ * {@link PackageManager.NameNotFoundException} is thrown if there's no such application.
+ *
+ * @throws PackageManager.NameNotFoundException If the application isn't installed.
+ *
+ * @param packageName The full name (e.g.: "com.google.apps.contacts") of an application.
+ *
+ * @return this {@link Builder} object to facilitate chaining method calls.
+ */
+ @NonNull
+ public Builder addAllowedApplication(@NonNull String packageName)
+ throws PackageManager.NameNotFoundException {
+ if (mConfig.disallowedApplications != null) {
+ throw new UnsupportedOperationException("addDisallowedApplication already called");
+ }
+ verifyApp(packageName);
+ if (mConfig.allowedApplications == null) {
+ mConfig.allowedApplications = new ArrayList<String>();
+ }
+ mConfig.allowedApplications.add(packageName);
+ return this;
+ }
+
+ /**
+ * Adds an application that's denied access to the VPN connection.
+ *
+ * By default, all applications are allowed access, except for those denied through this
+ * method. Denied applications will use networking as if the VPN wasn't running.
+ *
+ * A {@link Builder} may have only a set of allowed applications OR a set of disallowed
+ * ones, but not both. Calling this method after {@link #addAllowedApplication} has already
+ * been called, or vice versa, will throw an {@link UnsupportedOperationException}.
+ *
+ * {@code packageName} must be the canonical name of a currently installed application.
+ * {@link PackageManager.NameNotFoundException} is thrown if there's no such application.
+ *
+ * @throws PackageManager.NameNotFoundException If the application isn't installed.
+ *
+ * @param packageName The full name (e.g.: "com.google.apps.contacts") of an application.
+ *
+ * @return this {@link Builder} object to facilitate chaining method calls.
+ */
+ @NonNull
+ public Builder addDisallowedApplication(@NonNull String packageName)
+ throws PackageManager.NameNotFoundException {
+ if (mConfig.allowedApplications != null) {
+ throw new UnsupportedOperationException("addAllowedApplication already called");
+ }
+ verifyApp(packageName);
+ if (mConfig.disallowedApplications == null) {
+ mConfig.disallowedApplications = new ArrayList<String>();
+ }
+ mConfig.disallowedApplications.add(packageName);
+ return this;
+ }
+
+ /**
+ * Allows all apps to bypass this VPN connection.
+ *
+ * By default, all traffic from apps is forwarded through the VPN interface and it is not
+ * possible for apps to side-step the VPN. If this method is called, apps may use methods
+ * such as {@link ConnectivityManager#bindProcessToNetwork} to instead send/receive
+ * directly over the underlying network or any other network they have permissions for.
+ *
+ * @return this {@link Builder} object to facilitate chaining of method calls.
+ */
+ @NonNull
+ public Builder allowBypass() {
+ mConfig.allowBypass = true;
+ return this;
+ }
+
+ /**
+ * Sets the VPN interface's file descriptor to be in blocking/non-blocking mode.
+ *
+ * By default, the file descriptor returned by {@link #establish} is non-blocking.
+ *
+ * @param blocking True to put the descriptor into blocking mode; false for non-blocking.
+ *
+ * @return this {@link Builder} object to facilitate chaining method calls.
+ */
+ @NonNull
+ public Builder setBlocking(boolean blocking) {
+ mConfig.blocking = blocking;
+ return this;
+ }
+
+ /**
+ * Sets the underlying networks used by the VPN for its upstream connections.
+ *
+ * @see VpnService#setUnderlyingNetworks
+ *
+ * @param networks An array of networks the VPN uses to tunnel traffic to/from its servers.
+ *
+ * @return this {@link Builder} object to facilitate chaining method calls.
+ */
+ @NonNull
+ public Builder setUnderlyingNetworks(@Nullable Network[] networks) {
+ mConfig.underlyingNetworks = networks != null ? networks.clone() : null;
+ return this;
+ }
+
+ /**
+ * Marks the VPN network as metered. A VPN network is classified as metered when the user is
+ * sensitive to heavy data usage due to monetary costs and/or data limitations. In such
+ * cases, you should set this to {@code true} so that apps on the system can avoid doing
+ * large data transfers. Otherwise, set this to {@code false}. Doing so would cause VPN
+ * network to inherit its meteredness from its underlying networks.
+ *
+ * <p>VPN apps targeting {@link android.os.Build.VERSION_CODES#Q} or above will be
+ * considered metered by default.
+ *
+ * @param isMetered {@code true} if VPN network should be treated as metered regardless of
+ * underlying network meteredness
+ * @return this {@link Builder} object to facilitate chaining method calls
+ * @see #setUnderlyingNetworks(Networks[])
+ * @see ConnectivityManager#isActiveNetworkMetered()
+ */
+ @NonNull
+ public Builder setMetered(boolean isMetered) {
+ mConfig.isMetered = isMetered;
+ return this;
+ }
+
+ /**
+ * Create a VPN interface using the parameters supplied to this
+ * builder. The interface works on IP packets, and a file descriptor
+ * is returned for the application to access them. Each read
+ * retrieves an outgoing packet which was routed to the interface.
+ * Each write injects an incoming packet just like it was received
+ * from the interface. The file descriptor is put into non-blocking
+ * mode by default to avoid blocking Java threads. To use the file
+ * descriptor completely in native space, see
+ * {@link ParcelFileDescriptor#detachFd()}. The application MUST
+ * close the file descriptor when the VPN connection is terminated.
+ * The VPN interface will be removed and the network will be
+ * restored by the system automatically.
+ *
+ * <p>To avoid conflicts, there can be only one active VPN interface
+ * at the same time. Usually network parameters are never changed
+ * during the lifetime of a VPN connection. It is also common for an
+ * application to create a new file descriptor after closing the
+ * previous one. However, it is rare but not impossible to have two
+ * interfaces while performing a seamless handover. In this case, the
+ * old interface will be deactivated when the new one is created
+ * successfully. Both file descriptors are valid but now outgoing
+ * packets will be routed to the new interface. Therefore, after
+ * draining the old file descriptor, the application MUST close it
+ * and start using the new file descriptor. If the new interface
+ * cannot be created, the existing interface and its file descriptor
+ * remain untouched.
+ *
+ * <p>An exception will be thrown if the interface cannot be created
+ * for any reason. However, this method returns {@code null} if the
+ * application is not prepared or is revoked. This helps solve
+ * possible race conditions between other VPN applications.
+ *
+ * @return {@link ParcelFileDescriptor} of the VPN interface, or
+ * {@code null} if the application is not prepared.
+ * @throws IllegalArgumentException if a parameter is not accepted
+ * by the operating system.
+ * @throws IllegalStateException if a parameter cannot be applied
+ * by the operating system.
+ * @throws SecurityException if the service is not properly declared
+ * in {@code AndroidManifest.xml}.
+ * @see VpnService
+ */
+ @Nullable
+ public ParcelFileDescriptor establish() {
+ mConfig.addresses = mAddresses;
+ mConfig.routes = mRoutes;
+
+ try {
+ return getService().establishVpn(mConfig);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+}
diff --git a/android/net/WebAddress.java b/android/net/WebAddress.java
new file mode 100644
index 0000000..fbc281f
--- /dev/null
+++ b/android/net/WebAddress.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2006 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;
+
+import static android.util.Patterns.GOOD_IRI_CHAR;
+
+import android.annotation.SystemApi;
+import android.annotation.UnsupportedAppUsage;
+import android.os.Build;
+
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * {@hide}
+ *
+ * Web Address Parser
+ *
+ * This is called WebAddress, rather than URL or URI, because it
+ * attempts to parse the stuff that a user will actually type into a
+ * browser address widget.
+ *
+ * Unlike java.net.uri, this parser will not choke on URIs missing
+ * schemes. It will only throw a ParseException if the input is
+ * really hosed.
+ *
+ * If given an https scheme but no port, fills in port
+ *
+ */
+// TODO(igsolla): remove WebAddress from the system SDK once the WebView apk does not
+// longer need to be binary compatible with the API 21 version of the framework.
+@SystemApi
+public class WebAddress {
+
+ @UnsupportedAppUsage
+ private String mScheme;
+ @UnsupportedAppUsage
+ private String mHost;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private int mPort;
+ @UnsupportedAppUsage
+ private String mPath;
+ private String mAuthInfo;
+
+ static final int MATCH_GROUP_SCHEME = 1;
+ static final int MATCH_GROUP_AUTHORITY = 2;
+ static final int MATCH_GROUP_HOST = 3;
+ static final int MATCH_GROUP_PORT = 4;
+ static final int MATCH_GROUP_PATH = 5;
+
+ static Pattern sAddressPattern = Pattern.compile(
+ /* scheme */ "(?:(http|https|file)\\:\\/\\/)?" +
+ /* authority */ "(?:([-A-Za-z0-9$_.+!*'(),;?&=]+(?:\\:[-A-Za-z0-9$_.+!*'(),;?&=]+)?)@)?" +
+ /* host */ "([" + GOOD_IRI_CHAR + "%_-][" + GOOD_IRI_CHAR + "%_\\.-]*|\\[[0-9a-fA-F:\\.]+\\])?" +
+ /* port */ "(?:\\:([0-9]*))?" +
+ /* path */ "(\\/?[^#]*)?" +
+ /* anchor */ ".*", Pattern.CASE_INSENSITIVE);
+
+ /** parses given uriString. */
+ public WebAddress(String address) throws ParseException {
+ if (address == null) {
+ throw new NullPointerException();
+ }
+
+ // android.util.Log.d(LOGTAG, "WebAddress: " + address);
+
+ mScheme = "";
+ mHost = "";
+ mPort = -1;
+ mPath = "/";
+ mAuthInfo = "";
+
+ Matcher m = sAddressPattern.matcher(address);
+ String t;
+ if (m.matches()) {
+ t = m.group(MATCH_GROUP_SCHEME);
+ if (t != null) mScheme = t.toLowerCase(Locale.ROOT);
+ t = m.group(MATCH_GROUP_AUTHORITY);
+ if (t != null) mAuthInfo = t;
+ t = m.group(MATCH_GROUP_HOST);
+ if (t != null) mHost = t;
+ t = m.group(MATCH_GROUP_PORT);
+ if (t != null && t.length() > 0) {
+ // The ':' character is not returned by the regex.
+ try {
+ mPort = Integer.parseInt(t);
+ } catch (NumberFormatException ex) {
+ throw new ParseException("Bad port");
+ }
+ }
+ t = m.group(MATCH_GROUP_PATH);
+ if (t != null && t.length() > 0) {
+ /* handle busted myspace frontpage redirect with
+ missing initial "/" */
+ if (t.charAt(0) == '/') {
+ mPath = t;
+ } else {
+ mPath = "/" + t;
+ }
+ }
+
+ } else {
+ // nothing found... outa here
+ throw new ParseException("Bad address");
+ }
+
+ /* Get port from scheme or scheme from port, if necessary and
+ possible */
+ if (mPort == 443 && mScheme.equals("")) {
+ mScheme = "https";
+ } else if (mPort == -1) {
+ if (mScheme.equals("https"))
+ mPort = 443;
+ else
+ mPort = 80; // default
+ }
+ if (mScheme.equals("")) mScheme = "http";
+ }
+
+ @Override
+ public String toString() {
+ String port = "";
+ if ((mPort != 443 && mScheme.equals("https")) ||
+ (mPort != 80 && mScheme.equals("http"))) {
+ port = ":" + Integer.toString(mPort);
+ }
+ String authInfo = "";
+ if (mAuthInfo.length() > 0) {
+ authInfo = mAuthInfo + "@";
+ }
+
+ return mScheme + "://" + authInfo + mHost + port + mPath;
+ }
+
+ /** {@hide} */
+ public void setScheme(String scheme) {
+ mScheme = scheme;
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public String getScheme() {
+ return mScheme;
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public void setHost(String host) {
+ mHost = host;
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public String getHost() {
+ return mHost;
+ }
+
+ /** {@hide} */
+ public void setPort(int port) {
+ mPort = port;
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public int getPort() {
+ return mPort;
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public void setPath(String path) {
+ mPath = path;
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public String getPath() {
+ return mPath;
+ }
+
+ /** {@hide} */
+ public void setAuthInfo(String authInfo) {
+ mAuthInfo = authInfo;
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public String getAuthInfo() {
+ return mAuthInfo;
+ }
+}
diff --git a/android/net/WifiKey.java b/android/net/WifiKey.java
new file mode 100644
index 0000000..e3a93a8
--- /dev/null
+++ b/android/net/WifiKey.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2014 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;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+/**
+ * Information identifying a Wi-Fi network.
+ * @see NetworkKey
+ *
+ * @hide
+ */
+@SystemApi
+public class WifiKey implements Parcelable {
+
+ // Patterns used for validation.
+ private static final Pattern SSID_PATTERN = Pattern.compile("(\".*\")|(0x[\\p{XDigit}]+)",
+ Pattern.DOTALL);
+ private static final Pattern BSSID_PATTERN =
+ Pattern.compile("([\\p{XDigit}]{2}:){5}[\\p{XDigit}]{2}");
+
+ /**
+ * The service set identifier (SSID) of an 802.11 network. If the SSID can be decoded as
+ * UTF-8, it will be surrounded by double quotation marks. Otherwise, it will be a string of
+ * hex digits starting with 0x.
+ */
+ public final String ssid;
+
+ /**
+ * The basic service set identifier (BSSID) of an access point for this network. This will
+ * be in the form of a six-byte MAC address: {@code XX:XX:XX:XX:XX:XX}, where each X is a
+ * hexadecimal digit.
+ */
+ public final String bssid;
+
+ /**
+ * Construct a new {@link WifiKey} for the given Wi-Fi SSID/BSSID pair.
+ *
+ * @param ssid the service set identifier (SSID) of an 802.11 network. If the SSID can be
+ * decoded as UTF-8, it should be surrounded by double quotation marks. Otherwise,
+ * it should be a string of hex digits starting with 0x.
+ * @param bssid the basic service set identifier (BSSID) of this network's access point.
+ * This should be in the form of a six-byte MAC address: {@code XX:XX:XX:XX:XX:XX},
+ * where each X is a hexadecimal digit.
+ * @throws IllegalArgumentException if either the SSID or BSSID is invalid.
+ */
+ public WifiKey(String ssid, String bssid) {
+ if (ssid == null || !SSID_PATTERN.matcher(ssid).matches()) {
+ throw new IllegalArgumentException("Invalid ssid: " + ssid);
+ }
+ if (bssid == null || !BSSID_PATTERN.matcher(bssid).matches()) {
+ throw new IllegalArgumentException("Invalid bssid: " + bssid);
+ }
+ this.ssid = ssid;
+ this.bssid = bssid;
+ }
+
+ private WifiKey(Parcel in) {
+ ssid = in.readString();
+ bssid = in.readString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(ssid);
+ out.writeString(bssid);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ WifiKey wifiKey = (WifiKey) o;
+
+ return Objects.equals(ssid, wifiKey.ssid) && Objects.equals(bssid, wifiKey.bssid);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(ssid, bssid);
+ }
+
+ @Override
+ public String toString() {
+ return "WifiKey[SSID=" + ssid + ",BSSID=" + bssid + "]";
+ }
+
+ public static final @android.annotation.NonNull Creator<WifiKey> CREATOR =
+ new Creator<WifiKey>() {
+ @Override
+ public WifiKey createFromParcel(Parcel in) {
+ return new WifiKey(in);
+ }
+
+ @Override
+ public WifiKey[] newArray(int size) {
+ return new WifiKey[size];
+ }
+ };
+}
diff --git a/android/net/WifiLinkQualityInfo.java b/android/net/WifiLinkQualityInfo.java
new file mode 100644
index 0000000..20ec9a7
--- /dev/null
+++ b/android/net/WifiLinkQualityInfo.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2013 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;
+
+import android.os.Parcel;
+
+/**
+ * Class that represents useful attributes of wifi network links
+ * such as the upload/download throughput or error rate etc.
+ * @hide
+ */
+public class WifiLinkQualityInfo extends LinkQualityInfo {
+
+ /* Indicates Wifi network type such as b/g etc*/
+ private int mType = UNKNOWN_INT;
+
+ private String mBssid;
+
+ /* Rssi found by scans */
+ private int mRssi = UNKNOWN_INT;
+
+ /* packet statistics */
+ private long mTxGood = UNKNOWN_LONG;
+ private long mTxBad = UNKNOWN_LONG;
+
+ /**
+ * Implement the Parcelable interface.
+ * @hide
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags, OBJECT_TYPE_WIFI_LINK_QUALITY_INFO);
+
+ dest.writeInt(mType);
+ dest.writeInt(mRssi);
+ dest.writeLong(mTxGood);
+ dest.writeLong(mTxBad);
+
+ dest.writeString(mBssid);
+ }
+
+ /* Un-parceling helper */
+ /**
+ * @hide
+ */
+ public static WifiLinkQualityInfo createFromParcelBody(Parcel in) {
+ WifiLinkQualityInfo li = new WifiLinkQualityInfo();
+
+ li.initializeFromParcel(in);
+
+ li.mType = in.readInt();
+ li.mRssi = in.readInt();
+ li.mTxGood = in.readLong();
+ li.mTxBad = in.readLong();
+
+ li.mBssid = in.readString();
+
+ return li;
+ }
+
+ /**
+ * returns Wifi network type
+ * @return network type or {@link android.net.LinkQualityInfo#UNKNOWN_INT}
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * @hide
+ */
+ public void setType(int type) {
+ mType = type;
+ }
+
+ /**
+ * returns BSSID of the access point
+ * @return the BSSID, in the form of a six-byte MAC address: {@code XX:XX:XX:XX:XX:XX} or null
+ */
+ public String getBssid() {
+ return mBssid;
+ }
+
+ /**
+ * @hide
+ */
+ public void setBssid(String bssid) {
+ mBssid = bssid;
+ }
+
+ /**
+ * returns RSSI of the network in raw form
+ * @return un-normalized RSSI or {@link android.net.LinkQualityInfo#UNKNOWN_INT}
+ */
+ public int getRssi() {
+ return mRssi;
+ }
+
+ /**
+ * @hide
+ */
+ public void setRssi(int rssi) {
+ mRssi = rssi;
+ }
+
+ /**
+ * returns number of packets transmitted without error
+ * @return number of packets or {@link android.net.LinkQualityInfo#UNKNOWN_LONG}
+ */
+ public long getTxGood() {
+ return mTxGood;
+ }
+
+ /**
+ * @hide
+ */
+ public void setTxGood(long txGood) {
+ mTxGood = txGood;
+ }
+
+ /**
+ * returns number of transmitted packets that encountered errors
+ * @return number of packets or {@link android.net.LinkQualityInfo#UNKNOWN_LONG}
+ */
+ public long getTxBad() {
+ return mTxBad;
+ }
+
+ /**
+ * @hide
+ */
+ public void setTxBad(long txBad) {
+ mTxBad = txBad;
+ }
+}
diff --git a/android/net/apf/ApfCapabilities.java b/android/net/apf/ApfCapabilities.java
new file mode 100644
index 0000000..4dd2ace
--- /dev/null
+++ b/android/net/apf/ApfCapabilities.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2016 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.apf;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.content.res.Resources;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.R;
+
+/**
+ * APF program support capabilities. APF stands for Android Packet Filtering and it is a flexible
+ * way to drop unwanted network packets to save power.
+ *
+ * See documentation at hardware/google/apf/apf.h
+ *
+ * This class is immutable.
+ * @hide
+ */
+@SystemApi
+@TestApi
+public final class ApfCapabilities implements Parcelable {
+ /**
+ * Version of APF instruction set supported for packet filtering. 0 indicates no support for
+ * packet filtering using APF programs.
+ */
+ public final int apfVersionSupported;
+
+ /**
+ * Maximum size of APF program allowed.
+ */
+ public final int maximumApfProgramSize;
+
+ /**
+ * Format of packets passed to APF filter. Should be one of ARPHRD_*
+ */
+ public final int apfPacketFormat;
+
+ public ApfCapabilities(
+ int apfVersionSupported, int maximumApfProgramSize, int apfPacketFormat) {
+ this.apfVersionSupported = apfVersionSupported;
+ this.maximumApfProgramSize = maximumApfProgramSize;
+ this.apfPacketFormat = apfPacketFormat;
+ }
+
+ private ApfCapabilities(Parcel in) {
+ apfVersionSupported = in.readInt();
+ maximumApfProgramSize = in.readInt();
+ apfPacketFormat = in.readInt();
+ }
+
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(apfVersionSupported);
+ dest.writeInt(maximumApfProgramSize);
+ dest.writeInt(apfPacketFormat);
+ }
+
+ public static final Creator<ApfCapabilities> CREATOR = new Creator<ApfCapabilities>() {
+ @Override
+ public ApfCapabilities createFromParcel(Parcel in) {
+ return new ApfCapabilities(in);
+ }
+
+ @Override
+ public ApfCapabilities[] newArray(int size) {
+ return new ApfCapabilities[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return String.format("%s{version: %d, maxSize: %d, format: %d}", getClass().getSimpleName(),
+ apfVersionSupported, maximumApfProgramSize, apfPacketFormat);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ApfCapabilities)) return false;
+ final ApfCapabilities other = (ApfCapabilities) obj;
+ return apfVersionSupported == other.apfVersionSupported
+ && maximumApfProgramSize == other.maximumApfProgramSize
+ && apfPacketFormat == other.apfPacketFormat;
+ }
+
+ /**
+ * Determines whether the APF interpreter advertises support for the data buffer access opcodes
+ * LDDW (LoaD Data Word) and STDW (STore Data Word). Full LDDW (LoaD Data Word) and
+ * STDW (STore Data Word) support is present from APFv4 on.
+ *
+ * @return {@code true} if the IWifiStaIface#readApfPacketFilterData is supported.
+ */
+ public boolean hasDataAccess() {
+ return apfVersionSupported >= 4;
+ }
+
+ /**
+ * @return Whether the APF Filter in the device should filter out IEEE 802.3 Frames.
+ */
+ public static boolean getApfDrop8023Frames() {
+ return Resources.getSystem().getBoolean(R.bool.config_apfDrop802_3Frames);
+ }
+
+ /**
+ * @return An array of blacklisted EtherType, packets with EtherTypes within it will be dropped.
+ */
+ public static @NonNull int[] getApfEtherTypeBlackList() {
+ return Resources.getSystem().getIntArray(R.array.config_apfEthTypeBlackList);
+ }
+}
diff --git a/android/net/dhcp/DhcpServerCallbacks.java b/android/net/dhcp/DhcpServerCallbacks.java
new file mode 100644
index 0000000..bb56876
--- /dev/null
+++ b/android/net/dhcp/DhcpServerCallbacks.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp;
+
+/**
+ * Convenience wrapper around IDhcpServerCallbacks.Stub that implements getInterfaceVersion().
+ * @hide
+ */
+public abstract class DhcpServerCallbacks extends IDhcpServerCallbacks.Stub {
+ // TODO: add @Override here once the API is versioned
+
+ /**
+ * Get the version of the aidl interface implemented by the callbacks.
+ */
+ public int getInterfaceVersion() {
+ // TODO: return IDhcpServerCallbacks.VERSION;
+ return 0;
+ }
+}
diff --git a/android/net/dhcp/DhcpServingParamsParcelExt.java b/android/net/dhcp/DhcpServingParamsParcelExt.java
new file mode 100644
index 0000000..1fe2328
--- /dev/null
+++ b/android/net/dhcp/DhcpServingParamsParcelExt.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp;
+
+import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
+
+import android.annotation.NonNull;
+import android.net.LinkAddress;
+
+import com.google.android.collect.Sets;
+
+import java.net.Inet4Address;
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Subclass of {@link DhcpServingParamsParcel} with additional utility methods for building.
+ *
+ * <p>This utility class does not check for validity of the parameters: invalid parameters are
+ * reported by the receiving module when unparceling the parcel.
+ *
+ * @see DhcpServingParams
+ * @hide
+ */
+public class DhcpServingParamsParcelExt extends DhcpServingParamsParcel {
+ public static final int MTU_UNSET = 0;
+
+ /**
+ * Set the server address and served prefix for the DHCP server.
+ *
+ * <p>This parameter is required.
+ */
+ public DhcpServingParamsParcelExt setServerAddr(@NonNull LinkAddress serverAddr) {
+ this.serverAddr = inet4AddressToIntHTH((Inet4Address) serverAddr.getAddress());
+ this.serverAddrPrefixLength = serverAddr.getPrefixLength();
+ return this;
+ }
+
+ /**
+ * Set the default routers to be advertised to DHCP clients.
+ *
+ * <p>Each router must be inside the served prefix. This may be an empty set, but it must
+ * always be set explicitly.
+ */
+ public DhcpServingParamsParcelExt setDefaultRouters(@NonNull Set<Inet4Address> defaultRouters) {
+ this.defaultRouters = toIntArray(defaultRouters);
+ return this;
+ }
+
+ /**
+ * Set the default routers to be advertised to DHCP clients.
+ *
+ * <p>Each router must be inside the served prefix. This may be an empty list of routers,
+ * but it must always be set explicitly.
+ */
+ public DhcpServingParamsParcelExt setDefaultRouters(@NonNull Inet4Address... defaultRouters) {
+ return setDefaultRouters(Sets.newArraySet(defaultRouters));
+ }
+
+ /**
+ * Convenience method to build the parameters with no default router.
+ *
+ * <p>Equivalent to calling {@link #setDefaultRouters(Inet4Address...)} with no address.
+ */
+ public DhcpServingParamsParcelExt setNoDefaultRouter() {
+ return setDefaultRouters();
+ }
+
+ /**
+ * Set the DNS servers to be advertised to DHCP clients.
+ *
+ * <p>This may be an empty set, but it must always be set explicitly.
+ */
+ public DhcpServingParamsParcelExt setDnsServers(@NonNull Set<Inet4Address> dnsServers) {
+ this.dnsServers = toIntArray(dnsServers);
+ return this;
+ }
+
+ /**
+ * Set the DNS servers to be advertised to DHCP clients.
+ *
+ * <p>This may be an empty list of servers, but it must always be set explicitly.
+ */
+ public DhcpServingParamsParcelExt setDnsServers(@NonNull Inet4Address... dnsServers) {
+ return setDnsServers(Sets.newArraySet(dnsServers));
+ }
+
+ /**
+ * Convenience method to build the parameters with no DNS server.
+ *
+ * <p>Equivalent to calling {@link #setDnsServers(Inet4Address...)} with no address.
+ */
+ public DhcpServingParamsParcelExt setNoDnsServer() {
+ return setDnsServers();
+ }
+
+ /**
+ * Set excluded addresses that the DHCP server is not allowed to assign to clients.
+ *
+ * <p>This parameter is optional. DNS servers and default routers are always excluded
+ * and do not need to be set here.
+ */
+ public DhcpServingParamsParcelExt setExcludedAddrs(@NonNull Set<Inet4Address> excludedAddrs) {
+ this.excludedAddrs = toIntArray(excludedAddrs);
+ return this;
+ }
+
+ /**
+ * Set excluded addresses that the DHCP server is not allowed to assign to clients.
+ *
+ * <p>This parameter is optional. DNS servers and default routers are always excluded
+ * and do not need to be set here.
+ */
+ public DhcpServingParamsParcelExt setExcludedAddrs(@NonNull Inet4Address... excludedAddrs) {
+ return setExcludedAddrs(Sets.newArraySet(excludedAddrs));
+ }
+
+ /**
+ * Set the lease time for leases assigned by the DHCP server.
+ *
+ * <p>This parameter is required.
+ */
+ public DhcpServingParamsParcelExt setDhcpLeaseTimeSecs(long dhcpLeaseTimeSecs) {
+ this.dhcpLeaseTimeSecs = dhcpLeaseTimeSecs;
+ return this;
+ }
+
+ /**
+ * Set the link MTU to be advertised to DHCP clients.
+ *
+ * <p>If set to {@link #MTU_UNSET}, no MTU will be advertised to clients. This parameter
+ * is optional and defaults to {@link #MTU_UNSET}.
+ */
+ public DhcpServingParamsParcelExt setLinkMtu(int linkMtu) {
+ this.linkMtu = linkMtu;
+ return this;
+ }
+
+ /**
+ * Set whether the DHCP server should send the ANDROID_METERED vendor-specific option.
+ *
+ * <p>If not set, the default value is false.
+ */
+ public DhcpServingParamsParcelExt setMetered(boolean metered) {
+ this.metered = metered;
+ return this;
+ }
+
+ private static int[] toIntArray(@NonNull Collection<Inet4Address> addrs) {
+ int[] res = new int[addrs.size()];
+ int i = 0;
+ for (Inet4Address addr : addrs) {
+ res[i] = inet4AddressToIntHTH(addr);
+ i++;
+ }
+ return res;
+ }
+}
diff --git a/android/net/http/HttpResponseCache.java b/android/net/http/HttpResponseCache.java
new file mode 100644
index 0000000..7da76d1
--- /dev/null
+++ b/android/net/http/HttpResponseCache.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2011 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.http;
+
+import com.android.okhttp.internalandroidapi.AndroidResponseCacheAdapter;
+import com.android.okhttp.internalandroidapi.HasCacheHolder;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.net.CacheRequest;
+import java.net.CacheResponse;
+import java.net.ResponseCache;
+import java.net.URI;
+import java.net.URLConnection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Caches HTTP and HTTPS responses to the filesystem so they may be reused,
+ * saving time and bandwidth. This class supports {@link
+ * java.net.HttpURLConnection} and {@link javax.net.ssl.HttpsURLConnection};
+ * there is no platform-provided cache for {@code DefaultHttpClient} or
+ * {@code AndroidHttpClient}. Installation and instances are thread
+ * safe.
+ *
+ * <h3>Installing an HTTP response cache</h3>
+ * Enable caching of all of your application's HTTP requests by installing the
+ * cache at application startup. For example, this code installs a 10 MiB cache
+ * in the {@link android.content.Context#getCacheDir() application-specific
+ * cache directory} of the filesystem}: <pre> {@code
+ * protected void onCreate(Bundle savedInstanceState) {
+ * ...
+ *
+ * try {
+ * File httpCacheDir = new File(context.getCacheDir(), "http");
+ * long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
+ * HttpResponseCache.install(httpCacheDir, httpCacheSize);
+ * } catch (IOException e) {
+ * Log.i(TAG, "HTTP response cache installation failed:" + e);
+ * }
+ * }
+ *
+ * protected void onStop() {
+ * ...
+ *
+ * HttpResponseCache cache = HttpResponseCache.getInstalled();
+ * if (cache != null) {
+ * cache.flush();
+ * }
+ * }}</pre>
+ * This cache will evict entries as necessary to keep its size from exceeding
+ * 10 MiB. The best cache size is application specific and depends on the size
+ * and frequency of the files being downloaded. Increasing the limit may improve
+ * the hit rate, but it may also just waste filesystem space!
+ *
+ * <p>For some applications it may be preferable to create the cache in the
+ * external storage directory. <strong>There are no access controls on the
+ * external storage directory so it should not be used for caches that could
+ * contain private data.</strong> Although it often has more free space,
+ * external storage is optional and—even if available—can disappear
+ * during use. Retrieve the external cache directory using {@link
+ * android.content.Context#getExternalCacheDir()}. If this method returns null,
+ * your application should fall back to either not caching or caching on
+ * non-external storage. If the external storage is removed during use, the
+ * cache hit rate will drop to zero and ongoing cache reads will fail.
+ *
+ * <p>Flushing the cache forces its data to the filesystem. This ensures that
+ * all responses written to the cache will be readable the next time the
+ * activity starts.
+ *
+ * <h3>Cache Optimization</h3>
+ * To measure cache effectiveness, this class tracks three statistics:
+ * <ul>
+ * <li><strong>{@link #getRequestCount() Request Count:}</strong> the number
+ * of HTTP requests issued since this cache was created.
+ * <li><strong>{@link #getNetworkCount() Network Count:}</strong> the
+ * number of those requests that required network use.
+ * <li><strong>{@link #getHitCount() Hit Count:}</strong> the number of
+ * those requests whose responses were served by the cache.
+ * </ul>
+ * Sometimes a request will result in a conditional cache hit. If the cache
+ * contains a stale copy of the response, the client will issue a conditional
+ * {@code GET}. The server will then send either the updated response if it has
+ * changed, or a short 'not modified' response if the client's copy is still
+ * valid. Such responses increment both the network count and hit count.
+ *
+ * <p>The best way to improve the cache hit rate is by configuring the web
+ * server to return cacheable responses. Although this client honors all <a
+ * href="http://www.ietf.org/rfc/rfc2616.txt">HTTP/1.1 (RFC 2068)</a> cache
+ * headers, it doesn't cache partial responses.
+ *
+ * <h3>Force a Network Response</h3>
+ * In some situations, such as after a user clicks a 'refresh' button, it may be
+ * necessary to skip the cache, and fetch data directly from the server. To force
+ * a full refresh, add the {@code no-cache} directive: <pre> {@code
+ * connection.addRequestProperty("Cache-Control", "no-cache");
+ * }</pre>
+ * If it is only necessary to force a cached response to be validated by the
+ * server, use the more efficient {@code max-age=0} instead: <pre> {@code
+ * connection.addRequestProperty("Cache-Control", "max-age=0");
+ * }</pre>
+ *
+ * <h3>Force a Cache Response</h3>
+ * Sometimes you'll want to show resources if they are available immediately,
+ * but not otherwise. This can be used so your application can show
+ * <i>something</i> while waiting for the latest data to be downloaded. To
+ * restrict a request to locally-cached resources, add the {@code
+ * only-if-cached} directive: <pre> {@code
+ * try {
+ * connection.addRequestProperty("Cache-Control", "only-if-cached");
+ * InputStream cached = connection.getInputStream();
+ * // the resource was cached! show it
+ * } catch (FileNotFoundException e) {
+ * // the resource was not cached
+ * }
+ * }</pre>
+ * This technique works even better in situations where a stale response is
+ * better than no response. To permit stale cached responses, use the {@code
+ * max-stale} directive with the maximum staleness in seconds: <pre> {@code
+ * int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
+ * connection.addRequestProperty("Cache-Control", "max-stale=" + maxStale);
+ * }</pre>
+ *
+ * <h3>Working With Earlier Releases</h3>
+ * This class was added in Android 4.0 (Ice Cream Sandwich). Use reflection to
+ * enable the response cache without impacting earlier releases: <pre> {@code
+ * try {
+ * File httpCacheDir = new File(context.getCacheDir(), "http");
+ * long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
+ * Class.forName("android.net.http.HttpResponseCache")
+ * .getMethod("install", File.class, long.class)
+ * .invoke(null, httpCacheDir, httpCacheSize);
+ * } catch (Exception httpResponseCacheNotAvailable) {
+ * }}</pre>
+ */
+public final class HttpResponseCache extends ResponseCache implements HasCacheHolder, Closeable {
+
+ private final AndroidResponseCacheAdapter mDelegate;
+
+ private HttpResponseCache(AndroidResponseCacheAdapter delegate) {
+ mDelegate = delegate;
+ }
+
+ /**
+ * Returns the currently-installed {@code HttpResponseCache}, or null if
+ * there is no cache installed or it is not a {@code HttpResponseCache}.
+ */
+ public static HttpResponseCache getInstalled() {
+ ResponseCache installed = ResponseCache.getDefault();
+ if (installed instanceof HttpResponseCache) {
+ return (HttpResponseCache) installed;
+ }
+ return null;
+ }
+
+ /**
+ * Creates a new HTTP response cache and sets it as the system default cache.
+ *
+ * @param directory the directory to hold cache data.
+ * @param maxSize the maximum size of the cache in bytes.
+ * @return the newly-installed cache
+ * @throws IOException if {@code directory} cannot be used for this cache.
+ * Most applications should respond to this exception by logging a
+ * warning.
+ */
+ public static synchronized HttpResponseCache install(File directory, long maxSize)
+ throws IOException {
+ ResponseCache installed = ResponseCache.getDefault();
+ if (installed instanceof HttpResponseCache) {
+ HttpResponseCache installedResponseCache = (HttpResponseCache) installed;
+ CacheHolder cacheHolder = installedResponseCache.getCacheHolder();
+ // don't close and reopen if an equivalent cache is already installed
+ if (cacheHolder.isEquivalent(directory, maxSize)) {
+ return installedResponseCache;
+ } else {
+ // The HttpResponseCache that owns this object is about to be replaced.
+ installedResponseCache.close();
+ }
+ }
+
+ CacheHolder cacheHolder = CacheHolder.create(directory, maxSize);
+ AndroidResponseCacheAdapter androidResponseCacheAdapter =
+ new AndroidResponseCacheAdapter(cacheHolder);
+ HttpResponseCache responseCache = new HttpResponseCache(androidResponseCacheAdapter);
+ ResponseCache.setDefault(responseCache);
+ return responseCache;
+ }
+
+ @Override
+ public CacheResponse get(URI uri, String requestMethod,
+ Map<String, List<String>> requestHeaders) throws IOException {
+ return mDelegate.get(uri, requestMethod, requestHeaders);
+ }
+
+ @Override
+ public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException {
+ return mDelegate.put(uri, urlConnection);
+ }
+
+ /**
+ * Returns the number of bytes currently being used to store the values in
+ * this cache. This may be greater than the {@link #maxSize} if a background
+ * deletion is pending. {@code -1} is returned if the size cannot be determined.
+ */
+ public long size() {
+ try {
+ return mDelegate.getSize();
+ } catch (IOException e) {
+ // This can occur if the cache failed to lazily initialize.
+ return -1;
+ }
+ }
+
+ /**
+ * Returns the maximum number of bytes that this cache should use to store
+ * its data.
+ */
+ public long maxSize() {
+ return mDelegate.getMaxSize();
+ }
+
+ /**
+ * Force buffered operations to the filesystem. This ensures that responses
+ * written to the cache will be available the next time the cache is opened,
+ * even if this process is killed.
+ */
+ public void flush() {
+ try {
+ mDelegate.flush();
+ } catch (IOException ignored) {
+ }
+ }
+
+ /**
+ * Returns the number of HTTP requests that required the network to either
+ * supply a response or validate a locally cached response.
+ */
+ public int getNetworkCount() {
+ return mDelegate.getNetworkCount();
+ }
+
+ /**
+ * Returns the number of HTTP requests whose response was provided by the
+ * cache. This may include conditional {@code GET} requests that were
+ * validated over the network.
+ */
+ public int getHitCount() {
+ return mDelegate.getHitCount();
+ }
+
+ /**
+ * Returns the total number of HTTP requests that were made. This includes
+ * both client requests and requests that were made on the client's behalf
+ * to handle a redirects and retries.
+ */
+ public int getRequestCount() {
+ return mDelegate.getRequestCount();
+ }
+
+ /**
+ * Uninstalls the cache and releases any active resources. Stored contents
+ * will remain on the filesystem.
+ */
+ @Override
+ public void close() throws IOException {
+ if (ResponseCache.getDefault() == this) {
+ ResponseCache.setDefault(null);
+ }
+ mDelegate.close();
+ }
+
+ /**
+ * Uninstalls the cache and deletes all of its stored contents.
+ */
+ public void delete() throws IOException {
+ if (ResponseCache.getDefault() == this) {
+ ResponseCache.setDefault(null);
+ }
+ mDelegate.delete();
+ }
+
+ /** @hide Needed for OkHttp integration. */
+ @Override
+ public CacheHolder getCacheHolder() {
+ return mDelegate.getCacheHolder();
+ }
+}
diff --git a/android/net/http/SslCertificate.java b/android/net/http/SslCertificate.java
new file mode 100644
index 0000000..01dd08f
--- /dev/null
+++ b/android/net/http/SslCertificate.java
@@ -0,0 +1,524 @@
+/*
+ * Copyright (C) 2006 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.http;
+
+import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Bundle;
+import android.text.format.DateFormat;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.internal.util.HexDump;
+import com.android.org.bouncycastle.asn1.x509.X509Name;
+
+import java.io.ByteArrayInputStream;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Vector;
+
+/**
+ * SSL certificate info (certificate details) class
+ */
+public class SslCertificate {
+
+ /**
+ * SimpleDateFormat pattern for an ISO 8601 date
+ */
+ private static String ISO_8601_DATE_FORMAT = "yyyy-MM-dd HH:mm:ssZ";
+
+ /**
+ * Name of the entity this certificate is issued to
+ */
+ private final DName mIssuedTo;
+
+ /**
+ * Name of the entity this certificate is issued by
+ */
+ private final DName mIssuedBy;
+
+ /**
+ * Not-before date from the validity period
+ */
+ private final Date mValidNotBefore;
+
+ /**
+ * Not-after date from the validity period
+ */
+ private final Date mValidNotAfter;
+
+ /**
+ * The original source certificate, if available.
+ *
+ * TODO If deprecated constructors are removed, this should always
+ * be available, and saveState and restoreState can be simplified
+ * to be unconditional.
+ */
+ @UnsupportedAppUsage
+ private final X509Certificate mX509Certificate;
+
+ /**
+ * Bundle key names
+ */
+ private static final String ISSUED_TO = "issued-to";
+ private static final String ISSUED_BY = "issued-by";
+ private static final String VALID_NOT_BEFORE = "valid-not-before";
+ private static final String VALID_NOT_AFTER = "valid-not-after";
+ private static final String X509_CERTIFICATE = "x509-certificate";
+
+ /**
+ * Saves the certificate state to a bundle
+ * @param certificate The SSL certificate to store
+ * @return A bundle with the certificate stored in it or null if fails
+ */
+ public static Bundle saveState(SslCertificate certificate) {
+ if (certificate == null) {
+ return null;
+ }
+ Bundle bundle = new Bundle();
+ bundle.putString(ISSUED_TO, certificate.getIssuedTo().getDName());
+ bundle.putString(ISSUED_BY, certificate.getIssuedBy().getDName());
+ bundle.putString(VALID_NOT_BEFORE, certificate.getValidNotBefore());
+ bundle.putString(VALID_NOT_AFTER, certificate.getValidNotAfter());
+ X509Certificate x509Certificate = certificate.mX509Certificate;
+ if (x509Certificate != null) {
+ try {
+ bundle.putByteArray(X509_CERTIFICATE, x509Certificate.getEncoded());
+ } catch (CertificateEncodingException ignored) {
+ }
+ }
+ return bundle;
+ }
+
+ /**
+ * Restores the certificate stored in the bundle
+ * @param bundle The bundle with the certificate state stored in it
+ * @return The SSL certificate stored in the bundle or null if fails
+ */
+ public static SslCertificate restoreState(Bundle bundle) {
+ if (bundle == null) {
+ return null;
+ }
+ X509Certificate x509Certificate;
+ byte[] bytes = bundle.getByteArray(X509_CERTIFICATE);
+ if (bytes == null) {
+ x509Certificate = null;
+ } else {
+ try {
+ CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(bytes));
+ x509Certificate = (X509Certificate) cert;
+ } catch (CertificateException e) {
+ x509Certificate = null;
+ }
+ }
+ return new SslCertificate(bundle.getString(ISSUED_TO),
+ bundle.getString(ISSUED_BY),
+ parseDate(bundle.getString(VALID_NOT_BEFORE)),
+ parseDate(bundle.getString(VALID_NOT_AFTER)),
+ x509Certificate);
+ }
+
+ /**
+ * Creates a new SSL certificate object
+ * @param issuedTo The entity this certificate is issued to
+ * @param issuedBy The entity that issued this certificate
+ * @param validNotBefore The not-before date from the certificate
+ * validity period in ISO 8601 format
+ * @param validNotAfter The not-after date from the certificate
+ * validity period in ISO 8601 format
+ * @deprecated Use {@link #SslCertificate(X509Certificate)}
+ */
+ @Deprecated
+ public SslCertificate(
+ String issuedTo, String issuedBy, String validNotBefore, String validNotAfter) {
+ this(issuedTo, issuedBy, parseDate(validNotBefore), parseDate(validNotAfter), null);
+ }
+
+ /**
+ * Creates a new SSL certificate object
+ * @param issuedTo The entity this certificate is issued to
+ * @param issuedBy The entity that issued this certificate
+ * @param validNotBefore The not-before date from the certificate validity period
+ * @param validNotAfter The not-after date from the certificate validity period
+ * @deprecated Use {@link #SslCertificate(X509Certificate)}
+ */
+ @Deprecated
+ public SslCertificate(
+ String issuedTo, String issuedBy, Date validNotBefore, Date validNotAfter) {
+ this(issuedTo, issuedBy, validNotBefore, validNotAfter, null);
+ }
+
+ /**
+ * Creates a new SSL certificate object from an X509 certificate
+ * @param certificate X509 certificate
+ */
+ public SslCertificate(X509Certificate certificate) {
+ this(certificate.getSubjectDN().getName(),
+ certificate.getIssuerDN().getName(),
+ certificate.getNotBefore(),
+ certificate.getNotAfter(),
+ certificate);
+ }
+
+ private SslCertificate(
+ String issuedTo, String issuedBy,
+ Date validNotBefore, Date validNotAfter,
+ X509Certificate x509Certificate) {
+ mIssuedTo = new DName(issuedTo);
+ mIssuedBy = new DName(issuedBy);
+ mValidNotBefore = cloneDate(validNotBefore);
+ mValidNotAfter = cloneDate(validNotAfter);
+ mX509Certificate = x509Certificate;
+ }
+
+ /**
+ * @return Not-before date from the certificate validity period or
+ * "" if none has been set
+ */
+ public Date getValidNotBeforeDate() {
+ return cloneDate(mValidNotBefore);
+ }
+
+ /**
+ * @return Not-before date from the certificate validity period in
+ * ISO 8601 format or "" if none has been set
+ *
+ * @deprecated Use {@link #getValidNotBeforeDate()}
+ */
+ @Deprecated
+ public String getValidNotBefore() {
+ return formatDate(mValidNotBefore);
+ }
+
+ /**
+ * @return Not-after date from the certificate validity period or
+ * "" if none has been set
+ */
+ public Date getValidNotAfterDate() {
+ return cloneDate(mValidNotAfter);
+ }
+
+ /**
+ * @return Not-after date from the certificate validity period in
+ * ISO 8601 format or "" if none has been set
+ *
+ * @deprecated Use {@link #getValidNotAfterDate()}
+ */
+ @Deprecated
+ public String getValidNotAfter() {
+ return formatDate(mValidNotAfter);
+ }
+
+ /**
+ * @return Issued-to distinguished name or null if none has been set
+ */
+ public DName getIssuedTo() {
+ return mIssuedTo;
+ }
+
+ /**
+ * @return Issued-by distinguished name or null if none has been set
+ */
+ public DName getIssuedBy() {
+ return mIssuedBy;
+ }
+
+ /**
+ * @return The {@code X509Certificate} used to create this {@code SslCertificate} or
+ * {@code null} if no certificate was provided.
+ */
+ public @Nullable X509Certificate getX509Certificate() {
+ return mX509Certificate;
+ }
+
+ /**
+ * Convenience for UI presentation, not intended as public API.
+ */
+ @UnsupportedAppUsage
+ private static String getSerialNumber(X509Certificate x509Certificate) {
+ if (x509Certificate == null) {
+ return "";
+ }
+ BigInteger serialNumber = x509Certificate.getSerialNumber();
+ if (serialNumber == null) {
+ return "";
+ }
+ return fingerprint(serialNumber.toByteArray());
+ }
+
+ /**
+ * Convenience for UI presentation, not intended as public API.
+ */
+ @UnsupportedAppUsage
+ private static String getDigest(X509Certificate x509Certificate, String algorithm) {
+ if (x509Certificate == null) {
+ return "";
+ }
+ try {
+ byte[] bytes = x509Certificate.getEncoded();
+ MessageDigest md = MessageDigest.getInstance(algorithm);
+ byte[] digest = md.digest(bytes);
+ return fingerprint(digest);
+ } catch (CertificateEncodingException ignored) {
+ return "";
+ } catch (NoSuchAlgorithmException ignored) {
+ return "";
+ }
+ }
+
+ private static final String fingerprint(byte[] bytes) {
+ if (bytes == null) {
+ return "";
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < bytes.length; i++) {
+ byte b = bytes[i];
+ HexDump.appendByteAsHex(sb, b, true);
+ if (i+1 != bytes.length) {
+ sb.append(':');
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * @return A string representation of this certificate for debugging
+ */
+ public String toString() {
+ return ("Issued to: " + mIssuedTo.getDName() + ";\n"
+ + "Issued by: " + mIssuedBy.getDName() + ";\n");
+ }
+
+ /**
+ * Parse an ISO 8601 date converting ParseExceptions to a null result;
+ */
+ private static Date parseDate(String string) {
+ try {
+ return new SimpleDateFormat(ISO_8601_DATE_FORMAT).parse(string);
+ } catch (ParseException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Format a date as an ISO 8601 string, return "" for a null date
+ */
+ private static String formatDate(Date date) {
+ if (date == null) {
+ return "";
+ }
+ return new SimpleDateFormat(ISO_8601_DATE_FORMAT).format(date);
+ }
+
+ /**
+ * Clone a possibly null Date
+ */
+ private static Date cloneDate(Date date) {
+ if (date == null) {
+ return null;
+ }
+ return (Date) date.clone();
+ }
+
+ /**
+ * A distinguished name helper class: a 3-tuple of:
+ * <ul>
+ * <li>the most specific common name (CN)</li>
+ * <li>the most specific organization (O)</li>
+ * <li>the most specific organizational unit (OU)</li>
+ * <ul>
+ */
+ public class DName {
+ /**
+ * Distinguished name (normally includes CN, O, and OU names)
+ */
+ private String mDName;
+
+ /**
+ * Common-name (CN) component of the name
+ */
+ private String mCName;
+
+ /**
+ * Organization (O) component of the name
+ */
+ private String mOName;
+
+ /**
+ * Organizational Unit (OU) component of the name
+ */
+ private String mUName;
+
+ /**
+ * Creates a new {@code DName} from a string. The attributes
+ * are assumed to come in most significant to least
+ * significant order which is true of human readable values
+ * returned by methods such as {@code X500Principal.getName()}.
+ * Be aware that the underlying sources of distinguished names
+ * such as instances of {@code X509Certificate} are encoded in
+ * least significant to most significant order, so make sure
+ * the value passed here has the expected ordering of
+ * attributes.
+ */
+ public DName(String dName) {
+ if (dName != null) {
+ mDName = dName;
+ try {
+ X509Name x509Name = new X509Name(dName);
+
+ Vector val = x509Name.getValues();
+ Vector oid = x509Name.getOIDs();
+
+ for (int i = 0; i < oid.size(); i++) {
+ if (oid.elementAt(i).equals(X509Name.CN)) {
+ if (mCName == null) {
+ mCName = (String) val.elementAt(i);
+ }
+ continue;
+ }
+
+ if (oid.elementAt(i).equals(X509Name.O)) {
+ if (mOName == null) {
+ mOName = (String) val.elementAt(i);
+ continue;
+ }
+ }
+
+ if (oid.elementAt(i).equals(X509Name.OU)) {
+ if (mUName == null) {
+ mUName = (String) val.elementAt(i);
+ continue;
+ }
+ }
+ }
+ } catch (IllegalArgumentException ex) {
+ // thrown if there is an error parsing the string
+ }
+ }
+ }
+
+ /**
+ * @return The distinguished name (normally includes CN, O, and OU names)
+ */
+ public String getDName() {
+ return mDName != null ? mDName : "";
+ }
+
+ /**
+ * @return The most specific Common-name (CN) component of this name
+ */
+ public String getCName() {
+ return mCName != null ? mCName : "";
+ }
+
+ /**
+ * @return The most specific Organization (O) component of this name
+ */
+ public String getOName() {
+ return mOName != null ? mOName : "";
+ }
+
+ /**
+ * @return The most specific Organizational Unit (OU) component of this name
+ */
+ public String getUName() {
+ return mUName != null ? mUName : "";
+ }
+ }
+
+ /**
+ * Inflates the SSL certificate view (helper method).
+ * @return The resultant certificate view with issued-to, issued-by,
+ * issued-on, expires-on, and possibly other fields set.
+ *
+ * @hide Used by Browser and Settings
+ */
+ @UnsupportedAppUsage
+ public View inflateCertificateView(Context context) {
+ LayoutInflater factory = LayoutInflater.from(context);
+
+ View certificateView = factory.inflate(
+ com.android.internal.R.layout.ssl_certificate, null);
+
+ // issued to:
+ SslCertificate.DName issuedTo = getIssuedTo();
+ if (issuedTo != null) {
+ ((TextView) certificateView.findViewById(com.android.internal.R.id.to_common))
+ .setText(issuedTo.getCName());
+ ((TextView) certificateView.findViewById(com.android.internal.R.id.to_org))
+ .setText(issuedTo.getOName());
+ ((TextView) certificateView.findViewById(com.android.internal.R.id.to_org_unit))
+ .setText(issuedTo.getUName());
+ }
+ // serial number:
+ ((TextView) certificateView.findViewById(com.android.internal.R.id.serial_number))
+ .setText(getSerialNumber(mX509Certificate));
+
+ // issued by:
+ SslCertificate.DName issuedBy = getIssuedBy();
+ if (issuedBy != null) {
+ ((TextView) certificateView.findViewById(com.android.internal.R.id.by_common))
+ .setText(issuedBy.getCName());
+ ((TextView) certificateView.findViewById(com.android.internal.R.id.by_org))
+ .setText(issuedBy.getOName());
+ ((TextView) certificateView.findViewById(com.android.internal.R.id.by_org_unit))
+ .setText(issuedBy.getUName());
+ }
+
+ // issued on:
+ String issuedOn = formatCertificateDate(context, getValidNotBeforeDate());
+ ((TextView) certificateView.findViewById(com.android.internal.R.id.issued_on))
+ .setText(issuedOn);
+
+ // expires on:
+ String expiresOn = formatCertificateDate(context, getValidNotAfterDate());
+ ((TextView) certificateView.findViewById(com.android.internal.R.id.expires_on))
+ .setText(expiresOn);
+
+ // fingerprints:
+ ((TextView) certificateView.findViewById(com.android.internal.R.id.sha256_fingerprint))
+ .setText(getDigest(mX509Certificate, "SHA256"));
+ ((TextView) certificateView.findViewById(com.android.internal.R.id.sha1_fingerprint))
+ .setText(getDigest(mX509Certificate, "SHA1"));
+
+ return certificateView;
+ }
+
+ /**
+ * Formats the certificate date to a properly localized date string.
+ * @return Properly localized version of the certificate date string and
+ * the "" if it fails to localize.
+ */
+ private String formatCertificateDate(Context context, Date certificateDate) {
+ if (certificateDate == null) {
+ return "";
+ }
+ return DateFormat.getMediumDateFormat(context).format(certificateDate);
+ }
+}
diff --git a/android/net/http/SslError.java b/android/net/http/SslError.java
new file mode 100644
index 0000000..b3f2fb7
--- /dev/null
+++ b/android/net/http/SslError.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2006 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.http;
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import java.security.cert.X509Certificate;
+
+/**
+ * This class represents a set of one or more SSL errors and the associated SSL
+ * certificate.
+ */
+public class SslError {
+
+ /**
+ * Individual SSL errors (in the order from the least to the most severe):
+ */
+
+ /**
+ * The certificate is not yet valid
+ */
+ public static final int SSL_NOTYETVALID = 0;
+ /**
+ * The certificate has expired
+ */
+ public static final int SSL_EXPIRED = 1;
+ /**
+ * Hostname mismatch
+ */
+ public static final int SSL_IDMISMATCH = 2;
+ /**
+ * The certificate authority is not trusted
+ */
+ public static final int SSL_UNTRUSTED = 3;
+ /**
+ * The date of the certificate is invalid
+ */
+ public static final int SSL_DATE_INVALID = 4;
+ /**
+ * A generic error occurred
+ */
+ public static final int SSL_INVALID = 5;
+
+
+ /**
+ * The number of different SSL errors.
+ * @deprecated This constant is not necessary for using the SslError API and
+ * can change from release to release.
+ */
+ // Update if you add a new SSL error!!!
+ @Deprecated
+ public static final int SSL_MAX_ERROR = 6;
+
+ /**
+ * The SSL error set bitfield (each individual error is a bit index;
+ * multiple individual errors can be OR-ed)
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ int mErrors;
+
+ /**
+ * The SSL certificate associated with the error set
+ */
+ @UnsupportedAppUsage
+ final SslCertificate mCertificate;
+
+ /**
+ * The URL associated with the error set.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ final String mUrl;
+
+ /**
+ * Creates a new SslError object using the supplied error and certificate.
+ * The URL will be set to the empty string.
+ * @param error The SSL error
+ * @param certificate The associated SSL certificate
+ * @deprecated Use {@link #SslError(int, SslCertificate, String)}
+ */
+ @Deprecated
+ public SslError(int error, SslCertificate certificate) {
+ this(error, certificate, "");
+ }
+
+ /**
+ * Creates a new SslError object using the supplied error and certificate.
+ * The URL will be set to the empty string.
+ * @param error The SSL error
+ * @param certificate The associated SSL certificate
+ * @deprecated Use {@link #SslError(int, X509Certificate, String)}
+ */
+ @Deprecated
+ public SslError(int error, X509Certificate certificate) {
+ this(error, certificate, "");
+ }
+
+ /**
+ * Creates a new SslError object using the supplied error, certificate and
+ * URL.
+ * @param error The SSL error
+ * @param certificate The associated SSL certificate
+ * @param url The associated URL
+ */
+ public SslError(int error, SslCertificate certificate, String url) {
+ assert certificate != null;
+ assert url != null;
+ addError(error);
+ mCertificate = certificate;
+ mUrl = url;
+ }
+
+ /**
+ * Creates a new SslError object using the supplied error, certificate and
+ * URL.
+ * @param error The SSL error
+ * @param certificate The associated SSL certificate
+ * @param url The associated URL
+ */
+ public SslError(int error, X509Certificate certificate, String url) {
+ this(error, new SslCertificate(certificate), url);
+ }
+
+ /**
+ * Creates an SslError object from a chromium error code.
+ * @param error The chromium error code
+ * @param certificate The associated SSL certificate
+ * @param url The associated URL.
+ * @hide chromium error codes only available inside the framework
+ */
+ public static SslError SslErrorFromChromiumErrorCode(
+ int error, SslCertificate cert, String url) {
+ // The chromium error codes are in:
+ // external/chromium/net/base/net_error_list.h
+ assert (error >= -299 && error <= -200);
+ if (error == -200)
+ return new SslError(SSL_IDMISMATCH, cert, url);
+ if (error == -201)
+ return new SslError(SSL_DATE_INVALID, cert, url);
+ if (error == -202)
+ return new SslError(SSL_UNTRUSTED, cert, url);
+ // Map all other codes to SSL_INVALID.
+ return new SslError(SSL_INVALID, cert, url);
+ }
+
+ /**
+ * Gets the SSL certificate associated with this object.
+ * @return The SSL certificate, non-null.
+ */
+ public SslCertificate getCertificate() {
+ return mCertificate;
+ }
+
+ /**
+ * Gets the URL associated with this object.
+ * @return The URL, non-null.
+ */
+ public String getUrl() {
+ return mUrl;
+ }
+
+ /**
+ * Adds the supplied SSL error to the set.
+ * @param error The SSL error to add
+ * @return True if the error being added is a known SSL error, otherwise
+ * false.
+ */
+ public boolean addError(int error) {
+ boolean rval = (0 <= error && error < SslError.SSL_MAX_ERROR);
+ if (rval) {
+ mErrors |= (0x1 << error);
+ }
+
+ return rval;
+ }
+
+ /**
+ * Determines whether this object includes the supplied error.
+ * @param error The SSL error to check for
+ * @return True if this object includes the error, otherwise false.
+ */
+ public boolean hasError(int error) {
+ boolean rval = (0 <= error && error < SslError.SSL_MAX_ERROR);
+ if (rval) {
+ rval = ((mErrors & (0x1 << error)) != 0);
+ }
+
+ return rval;
+ }
+
+ /**
+ * Gets the most severe SSL error in this object's set of errors.
+ * Returns -1 if the set is empty.
+ * @return The most severe SSL error, or -1 if the set is empty.
+ */
+ public int getPrimaryError() {
+ if (mErrors != 0) {
+ // go from the most to the least severe errors
+ for (int error = SslError.SSL_MAX_ERROR - 1; error >= 0; --error) {
+ if ((mErrors & (0x1 << error)) != 0) {
+ return error;
+ }
+ }
+ // mErrors should never be set to an invalid value.
+ assert false;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Returns a string representation of this object.
+ * @return A String representation of this object.
+ */
+ public String toString() {
+ return "primary error: " + getPrimaryError() +
+ " certificate: " + getCertificate() +
+ " on URL: " + getUrl();
+ }
+}
diff --git a/android/net/http/X509TrustManagerExtensions.java b/android/net/http/X509TrustManagerExtensions.java
new file mode 100644
index 0000000..280dad0
--- /dev/null
+++ b/android/net/http/X509TrustManagerExtensions.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2012 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.http;
+
+import android.security.net.config.UserCertificateSource;
+
+import com.android.org.conscrypt.TrustManagerImpl;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * X509TrustManager wrapper exposing Android-added features.
+ * <p>
+ * The checkServerTrusted method allows callers to perform additional
+ * verification of certificate chains after they have been successfully verified
+ * by the platform.
+ * </p>
+ */
+public class X509TrustManagerExtensions {
+
+ private final TrustManagerImpl mDelegate;
+ // Methods to use when mDelegate is not a TrustManagerImpl and duck typing is being used.
+ private final X509TrustManager mTrustManager;
+ private final Method mCheckServerTrusted;
+ private final Method mIsSameTrustConfiguration;
+
+ /**
+ * Constructs a new X509TrustManagerExtensions wrapper.
+ *
+ * @param tm A {@link X509TrustManager} as returned by TrustManagerFactory.getInstance();
+ * @throws IllegalArgumentException If tm is an unsupported TrustManager type.
+ */
+ public X509TrustManagerExtensions(X509TrustManager tm) throws IllegalArgumentException {
+ if (tm instanceof TrustManagerImpl) {
+ mDelegate = (TrustManagerImpl) tm;
+ mTrustManager = null;
+ mCheckServerTrusted = null;
+ mIsSameTrustConfiguration = null;
+ return;
+ }
+ // Use duck typing if possible.
+ mDelegate = null;
+ mTrustManager = tm;
+ // Check that the hostname aware checkServerTrusted is present.
+ try {
+ mCheckServerTrusted = tm.getClass().getMethod("checkServerTrusted",
+ X509Certificate[].class,
+ String.class,
+ String.class);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException("Required method"
+ + " checkServerTrusted(X509Certificate[], String, String, String) missing");
+ }
+ // Get the option isSameTrustConfiguration method.
+ Method isSameTrustConfiguration = null;
+ try {
+ isSameTrustConfiguration = tm.getClass().getMethod("isSameTrustConfiguration",
+ String.class,
+ String.class);
+ } catch (ReflectiveOperationException ignored) {
+ }
+ mIsSameTrustConfiguration = isSameTrustConfiguration;
+ }
+
+ /**
+ * Verifies the given certificate chain.
+ *
+ * <p>See {@link X509TrustManager#checkServerTrusted(X509Certificate[], String)} for a
+ * description of the chain and authType parameters. The final parameter, host, should be the
+ * hostname of the server.</p>
+ *
+ * @throws CertificateException if the chain does not verify correctly.
+ * @return the properly ordered chain used for verification as a list of X509Certificates.
+ */
+ public List<X509Certificate> checkServerTrusted(X509Certificate[] chain, String authType,
+ String host) throws CertificateException {
+ if (mDelegate != null) {
+ return mDelegate.checkServerTrusted(chain, authType, host);
+ } else {
+ try {
+ return (List<X509Certificate>) mCheckServerTrusted.invoke(mTrustManager, chain,
+ authType, host);
+ } catch (IllegalAccessException e) {
+ throw new CertificateException("Failed to call checkServerTrusted", e);
+ } catch (InvocationTargetException e) {
+ if (e.getCause() instanceof CertificateException) {
+ throw (CertificateException) e.getCause();
+ }
+ if (e.getCause() instanceof RuntimeException) {
+ throw (RuntimeException) e.getCause();
+ }
+ throw new CertificateException("checkServerTrusted failed", e.getCause());
+ }
+ }
+ }
+
+ /**
+ * Checks whether a CA certificate is added by an user.
+ *
+ * <p>Since {@link X509TrustManager#checkServerTrusted} may allow its parameter {@code chain} to
+ * chain up to user-added CA certificates, this method can be used to perform additional
+ * policies for user-added CA certificates.
+ *
+ * @return {@code true} to indicate that the certificate authority exists in the user added
+ * certificate store, {@code false} otherwise.
+ */
+ public boolean isUserAddedCertificate(X509Certificate cert) {
+ return UserCertificateSource.getInstance().findBySubjectAndPublicKey(cert) != null;
+ }
+
+ /**
+ * Returns {@code true} if the TrustManager uses the same trust configuration for the provided
+ * hostnames.
+ */
+ public boolean isSameTrustConfiguration(String hostname1, String hostname2) {
+ if (mIsSameTrustConfiguration == null) {
+ return true;
+ }
+ try {
+ return (Boolean) mIsSameTrustConfiguration.invoke(mTrustManager, hostname1, hostname2);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException("Failed to call isSameTrustConfiguration", e);
+ } catch (InvocationTargetException e) {
+ if (e.getCause() instanceof RuntimeException) {
+ throw (RuntimeException) e.getCause();
+ } else {
+ throw new RuntimeException("isSameTrustConfiguration failed", e.getCause());
+ }
+ }
+ }
+}
diff --git a/android/net/ip/InterfaceController.java b/android/net/ip/InterfaceController.java
new file mode 100644
index 0000000..970bc9c
--- /dev/null
+++ b/android/net/ip/InterfaceController.java
@@ -0,0 +1,193 @@
+/*
+ * 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 android.net.ip;
+
+import android.net.INetd;
+import android.net.InterfaceConfigurationParcel;
+import android.net.LinkAddress;
+import android.net.util.SharedLog;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.system.OsConstants;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+
+
+/**
+ * Encapsulates the multiple IP configuration operations performed on an interface.
+ *
+ * TODO: refactor/eliminate the redundant ways to set and clear addresses.
+ *
+ * @hide
+ */
+public class InterfaceController {
+ private final static boolean DBG = false;
+
+ private final String mIfName;
+ private final INetd mNetd;
+ private final SharedLog mLog;
+
+ public InterfaceController(String ifname, INetd netd, SharedLog log) {
+ mIfName = ifname;
+ mNetd = netd;
+ mLog = log;
+ }
+
+ private boolean setInterfaceAddress(LinkAddress addr) {
+ final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel();
+ ifConfig.ifName = mIfName;
+ ifConfig.ipv4Addr = addr.getAddress().getHostAddress();
+ ifConfig.prefixLength = addr.getPrefixLength();
+ ifConfig.hwAddr = "";
+ ifConfig.flags = new String[0];
+ try {
+ mNetd.interfaceSetCfg(ifConfig);
+ } catch (RemoteException | ServiceSpecificException e) {
+ logError("Setting IPv4 address to %s/%d failed: %s",
+ ifConfig.ipv4Addr, ifConfig.prefixLength, e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Set the IPv4 address of the interface.
+ */
+ public boolean setIPv4Address(LinkAddress address) {
+ if (!(address.getAddress() instanceof Inet4Address)) {
+ return false;
+ }
+ return setInterfaceAddress(address);
+ }
+
+ /**
+ * Clear the IPv4Address of the interface.
+ */
+ public boolean clearIPv4Address() {
+ return setInterfaceAddress(new LinkAddress("0.0.0.0/0"));
+ }
+
+ private boolean setEnableIPv6(boolean enabled) {
+ try {
+ mNetd.interfaceSetEnableIPv6(mIfName, enabled);
+ } catch (RemoteException | ServiceSpecificException e) {
+ logError("%s IPv6 failed: %s", (enabled ? "enabling" : "disabling"), e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Enable IPv6 on the interface.
+ */
+ public boolean enableIPv6() {
+ return setEnableIPv6(true);
+ }
+
+ /**
+ * Disable IPv6 on the interface.
+ */
+ public boolean disableIPv6() {
+ return setEnableIPv6(false);
+ }
+
+ /**
+ * Enable or disable IPv6 privacy extensions on the interface.
+ * @param enabled Whether the extensions should be enabled.
+ */
+ public boolean setIPv6PrivacyExtensions(boolean enabled) {
+ try {
+ mNetd.interfaceSetIPv6PrivacyExtensions(mIfName, enabled);
+ } catch (RemoteException | ServiceSpecificException e) {
+ logError("error %s IPv6 privacy extensions: %s",
+ (enabled ? "enabling" : "disabling"), e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Set IPv6 address generation mode on the interface.
+ *
+ * <p>IPv6 should be disabled before changing the mode.
+ */
+ public boolean setIPv6AddrGenModeIfSupported(int mode) {
+ try {
+ mNetd.setIPv6AddrGenMode(mIfName, mode);
+ } catch (RemoteException e) {
+ logError("Unable to set IPv6 addrgen mode: %s", e);
+ return false;
+ } catch (ServiceSpecificException e) {
+ if (e.errorCode != OsConstants.EOPNOTSUPP) {
+ logError("Unable to set IPv6 addrgen mode: %s", e);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Add an address to the interface.
+ */
+ public boolean addAddress(LinkAddress addr) {
+ return addAddress(addr.getAddress(), addr.getPrefixLength());
+ }
+
+ /**
+ * Add an address to the interface.
+ */
+ public boolean addAddress(InetAddress ip, int prefixLen) {
+ try {
+ mNetd.interfaceAddAddress(mIfName, ip.getHostAddress(), prefixLen);
+ } catch (ServiceSpecificException | RemoteException e) {
+ logError("failed to add %s/%d: %s", ip, prefixLen, e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Remove an address from the interface.
+ */
+ public boolean removeAddress(InetAddress ip, int prefixLen) {
+ try {
+ mNetd.interfaceDelAddress(mIfName, ip.getHostAddress(), prefixLen);
+ } catch (ServiceSpecificException | RemoteException e) {
+ logError("failed to remove %s/%d: %s", ip, prefixLen, e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Remove all addresses from the interface.
+ */
+ public boolean clearAllAddresses() {
+ try {
+ mNetd.interfaceClearAddrs(mIfName);
+ } catch (Exception e) {
+ logError("Failed to clear addresses: %s", e);
+ return false;
+ }
+ return true;
+ }
+
+ private void logError(String fmt, Object... args) {
+ mLog.e(String.format(fmt, args));
+ }
+}
diff --git a/android/net/ip/IpClientCallbacks.java b/android/net/ip/IpClientCallbacks.java
new file mode 100644
index 0000000..db01ae4
--- /dev/null
+++ b/android/net/ip/IpClientCallbacks.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ip;
+
+import android.net.DhcpResults;
+import android.net.LinkProperties;
+
+/**
+ * Callbacks for handling IpClient events.
+ *
+ * This is a convenience class to allow clients not to override all methods of IIpClientCallbacks,
+ * and avoid unparceling arguments.
+ * These methods are called asynchronously on a Binder thread, as IpClient lives in a different
+ * process.
+ * @hide
+ */
+public class IpClientCallbacks {
+
+ /**
+ * Callback called upon IpClient creation.
+ *
+ * @param ipClient The Binder token to communicate with IpClient.
+ */
+ public void onIpClientCreated(IIpClient ipClient) {}
+
+ /**
+ * Callback called prior to DHCP discovery/renewal.
+ *
+ * <p>In order to receive onPreDhcpAction(), call #withPreDhcpAction() when constructing a
+ * ProvisioningConfiguration.
+ *
+ * <p>Implementations of onPreDhcpAction() must call IpClient#completedPreDhcpAction() to
+ * indicate that DHCP is clear to proceed.
+ */
+ public void onPreDhcpAction() {}
+
+ /**
+ * Callback called after DHCP discovery/renewal.
+ */
+ public void onPostDhcpAction() {}
+
+ /**
+ * Callback called when new DHCP results are available.
+ *
+ * <p>This is purely advisory and not an indication of provisioning success or failure. This is
+ * only here for callers that want to expose DHCPv4 results to other APIs
+ * (e.g., WifiInfo#setInetAddress).
+ *
+ * <p>DHCPv4 or static IPv4 configuration failure or success can be determined by whether or not
+ * the passed-in DhcpResults object is null.
+ */
+ public void onNewDhcpResults(DhcpResults dhcpResults) {}
+
+ /**
+ * Indicates that provisioning was successful.
+ */
+ public void onProvisioningSuccess(LinkProperties newLp) {}
+
+ /**
+ * Indicates that provisioning failed.
+ */
+ public void onProvisioningFailure(LinkProperties newLp) {}
+
+ /**
+ * Invoked on LinkProperties changes.
+ */
+ public void onLinkPropertiesChange(LinkProperties newLp) {}
+
+ /**Called when the internal IpReachabilityMonitor (if enabled) has
+ * detected the loss of a critical number of required neighbors.
+ */
+ public void onReachabilityLost(String logMsg) {}
+
+ /**
+ * Called when the IpClient state machine terminates.
+ */
+ public void onQuit() {}
+
+ /**
+ * Called to indicate that a new APF program must be installed to filter incoming packets.
+ */
+ public void installPacketFilter(byte[] filter) {}
+
+ /**
+ * Called to indicate that the APF Program & data buffer must be read asynchronously from the
+ * wifi driver.
+ *
+ * <p>Due to Wifi HAL limitations, the current implementation only supports dumping the entire
+ * buffer. In response to this request, the driver returns the data buffer asynchronously
+ * by sending an IpClient#EVENT_READ_PACKET_FILTER_COMPLETE message.
+ */
+ public void startReadPacketFilter() {}
+
+ /**
+ * If multicast filtering cannot be accomplished with APF, this function will be called to
+ * actuate multicast filtering using another means.
+ */
+ public void setFallbackMulticastFilter(boolean enabled) {}
+
+ /**
+ * Enabled/disable Neighbor Discover offload functionality. This is called, for example,
+ * whenever 464xlat is being started or stopped.
+ */
+ public void setNeighborDiscoveryOffload(boolean enable) {}
+}
diff --git a/android/net/ip/IpClientManager.java b/android/net/ip/IpClientManager.java
new file mode 100644
index 0000000..1e653cd
--- /dev/null
+++ b/android/net/ip/IpClientManager.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ip;
+
+import android.annotation.NonNull;
+import android.net.NattKeepalivePacketData;
+import android.net.ProxyInfo;
+import android.net.TcpKeepalivePacketData;
+import android.net.shared.ProvisioningConfiguration;
+import android.net.util.KeepalivePacketDataUtil;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A convenience wrapper for IpClient.
+ *
+ * Wraps IIpClient calls, making them a bit more friendly to use. Currently handles:
+ * - Clearing calling identity
+ * - Ignoring RemoteExceptions
+ * - Converting to stable parcelables
+ *
+ * By design, all methods on IIpClient are asynchronous oneway IPCs and are thus void. All the
+ * wrapper methods in this class return a boolean that callers can use to determine whether
+ * RemoteException was thrown.
+ */
+public class IpClientManager {
+ @NonNull private final IIpClient mIpClient;
+ @NonNull private final String mTag;
+
+ public IpClientManager(@NonNull IIpClient ipClient, @NonNull String tag) {
+ mIpClient = ipClient;
+ mTag = tag;
+ }
+
+ public IpClientManager(@NonNull IIpClient ipClient) {
+ this(ipClient, IpClientManager.class.getSimpleName());
+ }
+
+ private void log(String s, Throwable e) {
+ Log.e(mTag, s, e);
+ }
+
+ /**
+ * For clients using {@link ProvisioningConfiguration.Builder#withPreDhcpAction()}, must be
+ * called after {@link IIpClientCallbacks#onPreDhcpAction} to indicate that DHCP is clear to
+ * proceed.
+ */
+ public boolean completedPreDhcpAction() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.completedPreDhcpAction();
+ return true;
+ } catch (RemoteException e) {
+ log("Error completing PreDhcpAction", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Confirm the provisioning configuration.
+ */
+ public boolean confirmConfiguration() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.confirmConfiguration();
+ return true;
+ } catch (RemoteException e) {
+ log("Error confirming IpClient configuration", e);
+ return false;
+ }
+ }
+
+ /**
+ * Indicate that packet filter read is complete.
+ */
+ public boolean readPacketFilterComplete(byte[] data) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.readPacketFilterComplete(data);
+ return true;
+ } catch (RemoteException e) {
+ log("Error notifying IpClient of packet filter read", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Shut down this IpClient instance altogether.
+ */
+ public boolean shutdown() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.shutdown();
+ return true;
+ } catch (RemoteException e) {
+ log("Error shutting down IpClient", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Start provisioning with the provided parameters.
+ */
+ public boolean startProvisioning(ProvisioningConfiguration prov) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.startProvisioning(prov.toStableParcelable());
+ return true;
+ } catch (RemoteException e) {
+ log("Error starting IpClient provisioning", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Stop this IpClient.
+ *
+ * <p>This does not shut down the StateMachine itself, which is handled by {@link #shutdown()}.
+ */
+ public boolean stop() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.stop();
+ return true;
+ } catch (RemoteException e) {
+ log("Error stopping IpClient", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Set the TCP buffer sizes to use.
+ *
+ * This may be called, repeatedly, at any time before or after a call to
+ * #startProvisioning(). The setting is cleared upon calling #stop().
+ */
+ public boolean setTcpBufferSizes(String tcpBufferSizes) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.setTcpBufferSizes(tcpBufferSizes);
+ return true;
+ } catch (RemoteException e) {
+ log("Error setting IpClient TCP buffer sizes", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Set the HTTP Proxy configuration to use.
+ *
+ * This may be called, repeatedly, at any time before or after a call to
+ * #startProvisioning(). The setting is cleared upon calling #stop().
+ */
+ public boolean setHttpProxy(ProxyInfo proxyInfo) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.setHttpProxy(proxyInfo);
+ return true;
+ } catch (RemoteException e) {
+ log("Error setting IpClient proxy", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Enable or disable the multicast filter. Attempts to use APF to accomplish the filtering,
+ * if not, Callback.setFallbackMulticastFilter() is called.
+ */
+ public boolean setMulticastFilter(boolean enabled) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.setMulticastFilter(enabled);
+ return true;
+ } catch (RemoteException e) {
+ log("Error setting multicast filter", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Add a TCP keepalive packet filter before setting up keepalive offload.
+ */
+ public boolean addKeepalivePacketFilter(int slot, TcpKeepalivePacketData pkt) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.addKeepalivePacketFilter(slot, pkt.toStableParcelable());
+ return true;
+ } catch (RemoteException e) {
+ log("Error adding Keepalive Packet Filter ", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Add a NAT-T keepalive packet filter before setting up keepalive offload.
+ */
+ public boolean addKeepalivePacketFilter(int slot, NattKeepalivePacketData pkt) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.addNattKeepalivePacketFilter(
+ slot, KeepalivePacketDataUtil.toStableParcelable(pkt));
+ return true;
+ } catch (RemoteException e) {
+ log("Error adding Keepalive Packet Filter ", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Remove a keepalive packet filter after stopping keepalive offload.
+ */
+ public boolean removeKeepalivePacketFilter(int slot) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.removeKeepalivePacketFilter(slot);
+ return true;
+ } catch (RemoteException e) {
+ log("Error removing Keepalive Packet Filter ", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Set the L2 key and group hint for storing info into the memory store.
+ */
+ public boolean setL2KeyAndGroupHint(String l2Key, String groupHint) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.setL2KeyAndGroupHint(l2Key, groupHint);
+ return true;
+ } catch (RemoteException e) {
+ log("Failed setL2KeyAndGroupHint", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+}
diff --git a/android/net/ip/IpClientUtil.java b/android/net/ip/IpClientUtil.java
new file mode 100644
index 0000000..714ade1
--- /dev/null
+++ b/android/net/ip/IpClientUtil.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ip;
+
+import static android.net.shared.IpConfigurationParcelableUtil.fromStableParcelable;
+
+import android.content.Context;
+import android.net.DhcpResultsParcelable;
+import android.net.LinkProperties;
+import android.net.NetworkStackClient;
+import android.os.ConditionVariable;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+
+/**
+ * Utilities and wrappers to simplify communication with IpClient, which lives in the NetworkStack
+ * process.
+ *
+ * @hide
+ */
+public class IpClientUtil {
+ // TODO: remove with its callers
+ public static final String DUMP_ARG = "ipclient";
+
+ /**
+ * Subclass of {@link IpClientCallbacks} allowing clients to block until provisioning is
+ * complete with {@link WaitForProvisioningCallbacks#waitForProvisioning()}.
+ */
+ public static class WaitForProvisioningCallbacks extends IpClientCallbacks {
+ private final ConditionVariable mCV = new ConditionVariable();
+ private LinkProperties mCallbackLinkProperties;
+
+ /**
+ * Block until either {@link #onProvisioningSuccess(LinkProperties)} or
+ * {@link #onProvisioningFailure(LinkProperties)} is called.
+ */
+ public LinkProperties waitForProvisioning() {
+ mCV.block();
+ return mCallbackLinkProperties;
+ }
+
+ @Override
+ public void onProvisioningSuccess(LinkProperties newLp) {
+ mCallbackLinkProperties = newLp;
+ mCV.open();
+ }
+
+ @Override
+ public void onProvisioningFailure(LinkProperties newLp) {
+ mCallbackLinkProperties = null;
+ mCV.open();
+ }
+ }
+
+ /**
+ * Create a new IpClient.
+ *
+ * <p>This is a convenience method to allow clients to use {@link IpClientCallbacks} instead of
+ * {@link IIpClientCallbacks}.
+ * @see {@link NetworkStackClient#makeIpClient(String, IIpClientCallbacks)}
+ */
+ public static void makeIpClient(Context context, String ifName, IpClientCallbacks callback) {
+ // TODO: migrate clients and remove context argument
+ NetworkStackClient.getInstance().makeIpClient(ifName, new IpClientCallbacksProxy(callback));
+ }
+
+ /**
+ * Wrapper to relay calls from {@link IIpClientCallbacks} to {@link IpClientCallbacks}.
+ */
+ private static class IpClientCallbacksProxy extends IIpClientCallbacks.Stub {
+ protected final IpClientCallbacks mCb;
+
+ /**
+ * Create a new IpClientCallbacksProxy.
+ */
+ public IpClientCallbacksProxy(IpClientCallbacks cb) {
+ mCb = cb;
+ }
+
+ @Override
+ public void onIpClientCreated(IIpClient ipClient) {
+ mCb.onIpClientCreated(ipClient);
+ }
+
+ @Override
+ public void onPreDhcpAction() {
+ mCb.onPreDhcpAction();
+ }
+
+ @Override
+ public void onPostDhcpAction() {
+ mCb.onPostDhcpAction();
+ }
+
+ // This is purely advisory and not an indication of provisioning
+ // success or failure. This is only here for callers that want to
+ // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
+ // DHCPv4 or static IPv4 configuration failure or success can be
+ // determined by whether or not the passed-in DhcpResults object is
+ // null or not.
+ @Override
+ public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {
+ mCb.onNewDhcpResults(fromStableParcelable(dhcpResults));
+ }
+
+ @Override
+ public void onProvisioningSuccess(LinkProperties newLp) {
+ mCb.onProvisioningSuccess(newLp);
+ }
+ @Override
+ public void onProvisioningFailure(LinkProperties newLp) {
+ mCb.onProvisioningFailure(newLp);
+ }
+
+ // Invoked on LinkProperties changes.
+ @Override
+ public void onLinkPropertiesChange(LinkProperties newLp) {
+ mCb.onLinkPropertiesChange(newLp);
+ }
+
+ // Called when the internal IpReachabilityMonitor (if enabled) has
+ // detected the loss of a critical number of required neighbors.
+ @Override
+ public void onReachabilityLost(String logMsg) {
+ mCb.onReachabilityLost(logMsg);
+ }
+
+ // Called when the IpClient state machine terminates.
+ @Override
+ public void onQuit() {
+ mCb.onQuit();
+ }
+
+ // Install an APF program to filter incoming packets.
+ @Override
+ public void installPacketFilter(byte[] filter) {
+ mCb.installPacketFilter(filter);
+ }
+
+ // Asynchronously read back the APF program & data buffer from the wifi driver.
+ // Due to Wifi HAL limitations, the current implementation only supports dumping the entire
+ // buffer. In response to this request, the driver returns the data buffer asynchronously
+ // by sending an IpClient#EVENT_READ_PACKET_FILTER_COMPLETE message.
+ @Override
+ public void startReadPacketFilter() {
+ mCb.startReadPacketFilter();
+ }
+
+ // If multicast filtering cannot be accomplished with APF, this function will be called to
+ // actuate multicast filtering using another means.
+ @Override
+ public void setFallbackMulticastFilter(boolean enabled) {
+ mCb.setFallbackMulticastFilter(enabled);
+ }
+
+ // Enabled/disable Neighbor Discover offload functionality. This is
+ // called, for example, whenever 464xlat is being started or stopped.
+ @Override
+ public void setNeighborDiscoveryOffload(boolean enable) {
+ mCb.setNeighborDiscoveryOffload(enable);
+ }
+
+ @Override
+ public int getInterfaceVersion() {
+ return this.VERSION;
+ }
+ }
+
+ /**
+ * Dump logs for the specified IpClient.
+ * TODO: remove callers and delete
+ */
+ public static void dumpIpClient(
+ IIpClient connector, FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("IpClient logs have moved to dumpsys network_stack");
+ }
+}
diff --git a/android/net/ip/IpServer.java b/android/net/ip/IpServer.java
new file mode 100644
index 0000000..6a6a130
--- /dev/null
+++ b/android/net/ip/IpServer.java
@@ -0,0 +1,1003 @@
+/*
+ * Copyright (C) 2016 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.ip;
+
+import static android.net.NetworkUtils.numericToInetAddress;
+import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
+import static android.net.util.NetworkConstants.FF;
+import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
+import static android.net.util.NetworkConstants.asByte;
+
+import android.net.ConnectivityManager;
+import android.net.INetd;
+import android.net.INetworkStackStatusCallback;
+import android.net.INetworkStatsService;
+import android.net.InterfaceConfiguration;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.NetworkStackClient;
+import android.net.RouteInfo;
+import android.net.dhcp.DhcpServerCallbacks;
+import android.net.dhcp.DhcpServingParamsParcel;
+import android.net.dhcp.DhcpServingParamsParcelExt;
+import android.net.dhcp.IDhcpServer;
+import android.net.ip.RouterAdvertisementDaemon.RaParams;
+import android.net.util.InterfaceParams;
+import android.net.util.InterfaceSet;
+import android.net.util.NetdService;
+import android.net.util.SharedLog;
+import android.os.INetworkManagementService;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.util.MessageUtils;
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * Provides the interface to IP-layer serving functionality for a given network
+ * interface, e.g. for tethering or "local-only hotspot" mode.
+ *
+ * @hide
+ */
+public class IpServer extends StateMachine {
+ public static final int STATE_UNAVAILABLE = 0;
+ public static final int STATE_AVAILABLE = 1;
+ public static final int STATE_TETHERED = 2;
+ public static final int STATE_LOCAL_ONLY = 3;
+
+ public static String getStateString(int state) {
+ switch (state) {
+ case STATE_UNAVAILABLE: return "UNAVAILABLE";
+ case STATE_AVAILABLE: return "AVAILABLE";
+ case STATE_TETHERED: return "TETHERED";
+ case STATE_LOCAL_ONLY: return "LOCAL_ONLY";
+ }
+ return "UNKNOWN: " + state;
+ }
+
+ private static final byte DOUG_ADAMS = (byte) 42;
+
+ private static final String USB_NEAR_IFACE_ADDR = "192.168.42.129";
+ private static final int USB_PREFIX_LENGTH = 24;
+ private static final String WIFI_HOST_IFACE_ADDR = "192.168.43.1";
+ private static final int WIFI_HOST_IFACE_PREFIX_LENGTH = 24;
+
+ // TODO: have PanService use some visible version of this constant
+ private static final String BLUETOOTH_IFACE_ADDR = "192.168.44.1";
+ private static final int BLUETOOTH_DHCP_PREFIX_LENGTH = 24;
+
+ // TODO: have this configurable
+ private static final int DHCP_LEASE_TIME_SECS = 3600;
+
+ private final static String TAG = "IpServer";
+ private final static boolean DBG = false;
+ private final static boolean VDBG = false;
+ private static final Class[] messageClasses = {
+ IpServer.class
+ };
+ private static final SparseArray<String> sMagicDecoderRing =
+ MessageUtils.findMessageNames(messageClasses);
+
+ public static class Callback {
+ /**
+ * Notify that |who| has changed its tethering state.
+ *
+ * @param who the calling instance of IpServer
+ * @param state one of STATE_*
+ * @param lastError one of ConnectivityManager.TETHER_ERROR_*
+ */
+ public void updateInterfaceState(IpServer who, int state, int lastError) {}
+
+ /**
+ * Notify that |who| has new LinkProperties.
+ *
+ * @param who the calling instance of IpServer
+ * @param newLp the new LinkProperties to report
+ */
+ public void updateLinkProperties(IpServer who, LinkProperties newLp) {}
+ }
+
+ public static class Dependencies {
+ public RouterAdvertisementDaemon getRouterAdvertisementDaemon(InterfaceParams ifParams) {
+ return new RouterAdvertisementDaemon(ifParams);
+ }
+
+ public InterfaceParams getInterfaceParams(String ifName) {
+ return InterfaceParams.getByName(ifName);
+ }
+
+ public INetd getNetdService() {
+ return NetdService.getInstance();
+ }
+
+ /**
+ * Create a DhcpServer instance to be used by IpServer.
+ */
+ public void makeDhcpServer(String ifName, DhcpServingParamsParcel params,
+ DhcpServerCallbacks cb) {
+ NetworkStackClient.getInstance().makeDhcpServer(ifName, params, cb);
+ }
+ }
+
+ private static final int BASE_IFACE = Protocol.BASE_TETHERING + 100;
+ // request from the user that it wants to tether
+ public static final int CMD_TETHER_REQUESTED = BASE_IFACE + 2;
+ // request from the user that it wants to untether
+ public static final int CMD_TETHER_UNREQUESTED = BASE_IFACE + 3;
+ // notification that this interface is down
+ public static final int CMD_INTERFACE_DOWN = BASE_IFACE + 4;
+ // notification from the master SM that it had trouble enabling IP Forwarding
+ public static final int CMD_IP_FORWARDING_ENABLE_ERROR = BASE_IFACE + 7;
+ // notification from the master SM that it had trouble disabling IP Forwarding
+ public static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IFACE + 8;
+ // notification from the master SM that it had trouble starting tethering
+ public static final int CMD_START_TETHERING_ERROR = BASE_IFACE + 9;
+ // notification from the master SM that it had trouble stopping tethering
+ public static final int CMD_STOP_TETHERING_ERROR = BASE_IFACE + 10;
+ // notification from the master SM that it had trouble setting the DNS forwarders
+ public static final int CMD_SET_DNS_FORWARDERS_ERROR = BASE_IFACE + 11;
+ // the upstream connection has changed
+ public static final int CMD_TETHER_CONNECTION_CHANGED = BASE_IFACE + 12;
+ // new IPv6 tethering parameters need to be processed
+ public static final int CMD_IPV6_TETHER_UPDATE = BASE_IFACE + 13;
+
+ private final State mInitialState;
+ private final State mLocalHotspotState;
+ private final State mTetheredState;
+ private final State mUnavailableState;
+
+ private final SharedLog mLog;
+ private final INetworkManagementService mNMService;
+ private final INetd mNetd;
+ private final INetworkStatsService mStatsService;
+ private final Callback mCallback;
+ private final InterfaceController mInterfaceCtrl;
+
+ private final String mIfaceName;
+ private final int mInterfaceType;
+ private final LinkProperties mLinkProperties;
+ private final boolean mUsingLegacyDhcp;
+
+ private final Dependencies mDeps;
+
+ private int mLastError;
+ private int mServingMode;
+ private InterfaceSet mUpstreamIfaceSet; // may change over time
+ private InterfaceParams mInterfaceParams;
+ // TODO: De-duplicate this with mLinkProperties above. Currently, these link
+ // properties are those selected by the IPv6TetheringCoordinator and relayed
+ // to us. By comparison, mLinkProperties contains the addresses and directly
+ // connected routes that have been formed from these properties iff. we have
+ // succeeded in configuring them and are able to announce them within Router
+ // Advertisements (otherwise, we do not add them to mLinkProperties at all).
+ private LinkProperties mLastIPv6LinkProperties;
+ private RouterAdvertisementDaemon mRaDaemon;
+
+ // To be accessed only on the handler thread
+ private int mDhcpServerStartIndex = 0;
+ private IDhcpServer mDhcpServer;
+ private RaParams mLastRaParams;
+
+ public IpServer(
+ String ifaceName, Looper looper, int interfaceType, SharedLog log,
+ INetworkManagementService nMService, INetworkStatsService statsService,
+ Callback callback, boolean usingLegacyDhcp, Dependencies deps) {
+ super(ifaceName, looper);
+ mLog = log.forSubComponent(ifaceName);
+ mNMService = nMService;
+ mNetd = deps.getNetdService();
+ mStatsService = statsService;
+ mCallback = callback;
+ mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog);
+ mIfaceName = ifaceName;
+ mInterfaceType = interfaceType;
+ mLinkProperties = new LinkProperties();
+ mUsingLegacyDhcp = usingLegacyDhcp;
+ mDeps = deps;
+ resetLinkProperties();
+ mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+ mServingMode = STATE_AVAILABLE;
+
+ mInitialState = new InitialState();
+ mLocalHotspotState = new LocalHotspotState();
+ mTetheredState = new TetheredState();
+ mUnavailableState = new UnavailableState();
+ addState(mInitialState);
+ addState(mLocalHotspotState);
+ addState(mTetheredState);
+ addState(mUnavailableState);
+
+ setInitialState(mInitialState);
+ }
+
+ public String interfaceName() { return mIfaceName; }
+
+ public int interfaceType() { return mInterfaceType; }
+
+ public int lastError() { return mLastError; }
+
+ public int servingMode() { return mServingMode; }
+
+ public LinkProperties linkProperties() { return new LinkProperties(mLinkProperties); }
+
+ public void stop() { sendMessage(CMD_INTERFACE_DOWN); }
+
+ public void unwanted() { sendMessage(CMD_TETHER_UNREQUESTED); }
+
+ /**
+ * Internals.
+ */
+
+ private boolean startIPv4() { return configureIPv4(true); }
+
+ /**
+ * Convenience wrapper around INetworkStackStatusCallback to run callbacks on the IpServer
+ * handler.
+ *
+ * <p>Different instances of this class can be created for each call to IDhcpServer methods,
+ * with different implementations of the callback, to differentiate handling of success/error in
+ * each call.
+ */
+ private abstract class OnHandlerStatusCallback extends INetworkStackStatusCallback.Stub {
+ @Override
+ public void onStatusAvailable(int statusCode) {
+ getHandler().post(() -> callback(statusCode));
+ }
+
+ public abstract void callback(int statusCode);
+
+ @Override
+ public int getInterfaceVersion() {
+ return this.VERSION;
+ }
+ }
+
+ private class DhcpServerCallbacksImpl extends DhcpServerCallbacks {
+ private final int mStartIndex;
+
+ private DhcpServerCallbacksImpl(int startIndex) {
+ mStartIndex = startIndex;
+ }
+
+ @Override
+ public void onDhcpServerCreated(int statusCode, IDhcpServer server) throws RemoteException {
+ getHandler().post(() -> {
+ // We are on the handler thread: mDhcpServerStartIndex can be read safely.
+ if (mStartIndex != mDhcpServerStartIndex) {
+ // This start request is obsolete. When the |server| binder token goes out of
+ // scope, the garbage collector will finalize it, which causes the network stack
+ // process garbage collector to collect the server itself.
+ return;
+ }
+
+ if (statusCode != STATUS_SUCCESS) {
+ mLog.e("Error obtaining DHCP server: " + statusCode);
+ handleError();
+ return;
+ }
+
+ mDhcpServer = server;
+ try {
+ mDhcpServer.start(new OnHandlerStatusCallback() {
+ @Override
+ public void callback(int startStatusCode) {
+ if (startStatusCode != STATUS_SUCCESS) {
+ mLog.e("Error starting DHCP server: " + startStatusCode);
+ handleError();
+ }
+ }
+ });
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ });
+ }
+
+ private void handleError() {
+ mLastError = ConnectivityManager.TETHER_ERROR_DHCPSERVER_ERROR;
+ transitionTo(mInitialState);
+ }
+ }
+
+ private boolean startDhcp(Inet4Address addr, int prefixLen) {
+ if (mUsingLegacyDhcp) {
+ return true;
+ }
+ final DhcpServingParamsParcel params;
+ params = new DhcpServingParamsParcelExt()
+ .setDefaultRouters(addr)
+ .setDhcpLeaseTimeSecs(DHCP_LEASE_TIME_SECS)
+ .setDnsServers(addr)
+ .setServerAddr(new LinkAddress(addr, prefixLen))
+ .setMetered(true);
+ // TODO: also advertise link MTU
+
+ mDhcpServerStartIndex++;
+ mDeps.makeDhcpServer(
+ mIfaceName, params, new DhcpServerCallbacksImpl(mDhcpServerStartIndex));
+ return true;
+ }
+
+ private void stopDhcp() {
+ // Make all previous start requests obsolete so servers are not started later
+ mDhcpServerStartIndex++;
+
+ if (mDhcpServer != null) {
+ try {
+ mDhcpServer.stop(new OnHandlerStatusCallback() {
+ @Override
+ public void callback(int statusCode) {
+ if (statusCode != STATUS_SUCCESS) {
+ mLog.e("Error stopping DHCP server: " + statusCode);
+ mLastError = ConnectivityManager.TETHER_ERROR_DHCPSERVER_ERROR;
+ // Not much more we can do here
+ }
+ }
+ });
+ mDhcpServer = null;
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ private boolean configureDhcp(boolean enable, Inet4Address addr, int prefixLen) {
+ if (enable) {
+ return startDhcp(addr, prefixLen);
+ } else {
+ stopDhcp();
+ return true;
+ }
+ }
+
+ private void stopIPv4() {
+ configureIPv4(false);
+ // NOTE: All of configureIPv4() will be refactored out of existence
+ // into calls to InterfaceController, shared with startIPv4().
+ mInterfaceCtrl.clearIPv4Address();
+ }
+
+ // TODO: Refactor this in terms of calls to InterfaceController.
+ private boolean configureIPv4(boolean enabled) {
+ if (VDBG) Log.d(TAG, "configureIPv4(" + enabled + ")");
+
+ // TODO: Replace this hard-coded information with dynamically selected
+ // config passed down to us by a higher layer IP-coordinating element.
+ String ipAsString = null;
+ int prefixLen = 0;
+ if (mInterfaceType == ConnectivityManager.TETHERING_USB) {
+ ipAsString = USB_NEAR_IFACE_ADDR;
+ prefixLen = USB_PREFIX_LENGTH;
+ } else if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) {
+ ipAsString = getRandomWifiIPv4Address();
+ prefixLen = WIFI_HOST_IFACE_PREFIX_LENGTH;
+ } else {
+ // BT configures the interface elsewhere: only start DHCP.
+ final Inet4Address srvAddr = (Inet4Address) numericToInetAddress(BLUETOOTH_IFACE_ADDR);
+ return configureDhcp(enabled, srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH);
+ }
+
+ final LinkAddress linkAddr;
+ try {
+ final InterfaceConfiguration ifcg = mNMService.getInterfaceConfig(mIfaceName);
+ if (ifcg == null) {
+ mLog.e("Received null interface config");
+ return false;
+ }
+
+ InetAddress addr = numericToInetAddress(ipAsString);
+ linkAddr = new LinkAddress(addr, prefixLen);
+ ifcg.setLinkAddress(linkAddr);
+ if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) {
+ // The WiFi stack has ownership of the interface up/down state.
+ // It is unclear whether the Bluetooth or USB stacks will manage their own
+ // state.
+ ifcg.ignoreInterfaceUpDownStatus();
+ } else {
+ if (enabled) {
+ ifcg.setInterfaceUp();
+ } else {
+ ifcg.setInterfaceDown();
+ }
+ }
+ ifcg.clearFlag("running");
+
+ // TODO: this may throw if the interface is already gone. Do proper handling and
+ // simplify the DHCP server start/stop.
+ mNMService.setInterfaceConfig(mIfaceName, ifcg);
+
+ if (!configureDhcp(enabled, (Inet4Address) addr, prefixLen)) {
+ return false;
+ }
+ } catch (Exception e) {
+ mLog.e("Error configuring interface " + e);
+ if (!enabled) {
+ try {
+ // Calling stopDhcp several times is fine
+ stopDhcp();
+ } catch (Exception dhcpError) {
+ mLog.e("Error stopping DHCP", dhcpError);
+ }
+ }
+ return false;
+ }
+
+ // Directly-connected route.
+ final RouteInfo route = new RouteInfo(linkAddr);
+ if (enabled) {
+ mLinkProperties.addLinkAddress(linkAddr);
+ mLinkProperties.addRoute(route);
+ } else {
+ mLinkProperties.removeLinkAddress(linkAddr);
+ mLinkProperties.removeRoute(route);
+ }
+ return true;
+ }
+
+ private String getRandomWifiIPv4Address() {
+ try {
+ byte[] bytes = numericToInetAddress(WIFI_HOST_IFACE_ADDR).getAddress();
+ bytes[3] = getRandomSanitizedByte(DOUG_ADAMS, asByte(0), asByte(1), FF);
+ return InetAddress.getByAddress(bytes).getHostAddress();
+ } catch (Exception e) {
+ return WIFI_HOST_IFACE_ADDR;
+ }
+ }
+
+ private boolean startIPv6() {
+ mInterfaceParams = mDeps.getInterfaceParams(mIfaceName);
+ if (mInterfaceParams == null) {
+ mLog.e("Failed to find InterfaceParams");
+ stopIPv6();
+ return false;
+ }
+
+ mRaDaemon = mDeps.getRouterAdvertisementDaemon(mInterfaceParams);
+ if (!mRaDaemon.start()) {
+ stopIPv6();
+ return false;
+ }
+
+ return true;
+ }
+
+ private void stopIPv6() {
+ mInterfaceParams = null;
+ setRaParams(null);
+
+ if (mRaDaemon != null) {
+ mRaDaemon.stop();
+ mRaDaemon = null;
+ }
+ }
+
+ // IPv6TetheringCoordinator sends updates with carefully curated IPv6-only
+ // LinkProperties. These have extraneous data filtered out and only the
+ // necessary prefixes included (per its prefix distribution policy).
+ //
+ // TODO: Evaluate using a data structure than is more directly suited to
+ // communicating only the relevant information.
+ private void updateUpstreamIPv6LinkProperties(LinkProperties v6only) {
+ if (mRaDaemon == null) return;
+
+ // Avoid unnecessary work on spurious updates.
+ if (Objects.equals(mLastIPv6LinkProperties, v6only)) {
+ return;
+ }
+
+ RaParams params = null;
+
+ if (v6only != null) {
+ params = new RaParams();
+ params.mtu = v6only.getMtu();
+ params.hasDefaultRoute = v6only.hasIpv6DefaultRoute();
+
+ if (params.hasDefaultRoute) params.hopLimit = getHopLimit(v6only.getInterfaceName());
+
+ for (LinkAddress linkAddr : v6only.getLinkAddresses()) {
+ if (linkAddr.getPrefixLength() != RFC7421_PREFIX_LENGTH) continue;
+
+ final IpPrefix prefix = new IpPrefix(
+ linkAddr.getAddress(), linkAddr.getPrefixLength());
+ params.prefixes.add(prefix);
+
+ final Inet6Address dnsServer = getLocalDnsIpFor(prefix);
+ if (dnsServer != null) {
+ params.dnses.add(dnsServer);
+ }
+ }
+ }
+ // If v6only is null, we pass in null to setRaParams(), which handles
+ // deprecation of any existing RA data.
+
+ setRaParams(params);
+ mLastIPv6LinkProperties = v6only;
+ }
+
+ private void configureLocalIPv6Routes(
+ HashSet<IpPrefix> deprecatedPrefixes, HashSet<IpPrefix> newPrefixes) {
+ // [1] Remove the routes that are deprecated.
+ if (!deprecatedPrefixes.isEmpty()) {
+ final ArrayList<RouteInfo> toBeRemoved =
+ getLocalRoutesFor(mIfaceName, deprecatedPrefixes);
+ try {
+ final int removalFailures = mNMService.removeRoutesFromLocalNetwork(toBeRemoved);
+ if (removalFailures > 0) {
+ mLog.e(String.format("Failed to remove %d IPv6 routes from local table.",
+ removalFailures));
+ }
+ } catch (RemoteException e) {
+ mLog.e("Failed to remove IPv6 routes from local table: " + e);
+ }
+
+ for (RouteInfo route : toBeRemoved) mLinkProperties.removeRoute(route);
+ }
+
+ // [2] Add only the routes that have not previously been added.
+ if (newPrefixes != null && !newPrefixes.isEmpty()) {
+ HashSet<IpPrefix> addedPrefixes = (HashSet) newPrefixes.clone();
+ if (mLastRaParams != null) {
+ addedPrefixes.removeAll(mLastRaParams.prefixes);
+ }
+
+ if (!addedPrefixes.isEmpty()) {
+ final ArrayList<RouteInfo> toBeAdded =
+ getLocalRoutesFor(mIfaceName, addedPrefixes);
+ try {
+ // It's safe to call addInterfaceToLocalNetwork() even if
+ // the interface is already in the local_network. Note also
+ // that adding routes that already exist does not cause an
+ // error (EEXIST is silently ignored).
+ mNMService.addInterfaceToLocalNetwork(mIfaceName, toBeAdded);
+ } catch (Exception e) {
+ mLog.e("Failed to add IPv6 routes to local table: " + e);
+ }
+
+ for (RouteInfo route : toBeAdded) mLinkProperties.addRoute(route);
+ }
+ }
+ }
+
+ private void configureLocalIPv6Dns(
+ HashSet<Inet6Address> deprecatedDnses, HashSet<Inet6Address> newDnses) {
+ // TODO: Is this really necessary? Can we not fail earlier if INetd cannot be located?
+ if (mNetd == null) {
+ if (newDnses != null) newDnses.clear();
+ mLog.e("No netd service instance available; not setting local IPv6 addresses");
+ return;
+ }
+
+ // [1] Remove deprecated local DNS IP addresses.
+ if (!deprecatedDnses.isEmpty()) {
+ for (Inet6Address dns : deprecatedDnses) {
+ if (!mInterfaceCtrl.removeAddress(dns, RFC7421_PREFIX_LENGTH)) {
+ mLog.e("Failed to remove local dns IP " + dns);
+ }
+
+ mLinkProperties.removeLinkAddress(new LinkAddress(dns, RFC7421_PREFIX_LENGTH));
+ }
+ }
+
+ // [2] Add only the local DNS IP addresses that have not previously been added.
+ if (newDnses != null && !newDnses.isEmpty()) {
+ final HashSet<Inet6Address> addedDnses = (HashSet) newDnses.clone();
+ if (mLastRaParams != null) {
+ addedDnses.removeAll(mLastRaParams.dnses);
+ }
+
+ for (Inet6Address dns : addedDnses) {
+ if (!mInterfaceCtrl.addAddress(dns, RFC7421_PREFIX_LENGTH)) {
+ mLog.e("Failed to add local dns IP " + dns);
+ newDnses.remove(dns);
+ }
+
+ mLinkProperties.addLinkAddress(new LinkAddress(dns, RFC7421_PREFIX_LENGTH));
+ }
+ }
+
+ try {
+ mNetd.tetherApplyDnsInterfaces();
+ } catch (ServiceSpecificException | RemoteException e) {
+ mLog.e("Failed to update local DNS caching server");
+ if (newDnses != null) newDnses.clear();
+ }
+ }
+
+ private byte getHopLimit(String upstreamIface) {
+ try {
+ int upstreamHopLimit = Integer.parseUnsignedInt(
+ mNetd.getProcSysNet(INetd.IPV6, INetd.CONF, upstreamIface, "hop_limit"));
+ // Add one hop to account for this forwarding device
+ upstreamHopLimit++;
+ // Cap the hop limit to 255.
+ return (byte) Integer.min(upstreamHopLimit, 255);
+ } catch (Exception e) {
+ mLog.e("Failed to find upstream interface hop limit", e);
+ }
+ return RaParams.DEFAULT_HOPLIMIT;
+ }
+
+ private void setRaParams(RaParams newParams) {
+ if (mRaDaemon != null) {
+ final RaParams deprecatedParams =
+ RaParams.getDeprecatedRaParams(mLastRaParams, newParams);
+
+ configureLocalIPv6Routes(deprecatedParams.prefixes,
+ (newParams != null) ? newParams.prefixes : null);
+
+ configureLocalIPv6Dns(deprecatedParams.dnses,
+ (newParams != null) ? newParams.dnses : null);
+
+ mRaDaemon.buildNewRa(deprecatedParams, newParams);
+ }
+
+ mLastRaParams = newParams;
+ }
+
+ private void logMessage(State state, int what) {
+ mLog.log(state.getName() + " got " + sMagicDecoderRing.get(what, Integer.toString(what)));
+ }
+
+ private void sendInterfaceState(int newInterfaceState) {
+ mServingMode = newInterfaceState;
+ mCallback.updateInterfaceState(this, newInterfaceState, mLastError);
+ sendLinkProperties();
+ }
+
+ private void sendLinkProperties() {
+ mCallback.updateLinkProperties(this, new LinkProperties(mLinkProperties));
+ }
+
+ private void resetLinkProperties() {
+ mLinkProperties.clear();
+ mLinkProperties.setInterfaceName(mIfaceName);
+ }
+
+ class InitialState extends State {
+ @Override
+ public void enter() {
+ sendInterfaceState(STATE_AVAILABLE);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ logMessage(this, message.what);
+ switch (message.what) {
+ case CMD_TETHER_REQUESTED:
+ mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+ switch (message.arg1) {
+ case STATE_LOCAL_ONLY:
+ transitionTo(mLocalHotspotState);
+ break;
+ case STATE_TETHERED:
+ transitionTo(mTetheredState);
+ break;
+ default:
+ mLog.e("Invalid tethering interface serving state specified.");
+ }
+ break;
+ case CMD_INTERFACE_DOWN:
+ transitionTo(mUnavailableState);
+ break;
+ case CMD_IPV6_TETHER_UPDATE:
+ updateUpstreamIPv6LinkProperties((LinkProperties) message.obj);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class BaseServingState extends State {
+ @Override
+ public void enter() {
+ if (!startIPv4()) {
+ mLastError = ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR;
+ return;
+ }
+
+ try {
+ mNMService.tetherInterface(mIfaceName);
+ } catch (Exception e) {
+ mLog.e("Error Tethering: " + e);
+ mLastError = ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+ return;
+ }
+
+ if (!startIPv6()) {
+ mLog.e("Failed to startIPv6");
+ // TODO: Make this a fatal error once Bluetooth IPv6 is sorted.
+ return;
+ }
+ }
+
+ @Override
+ public void exit() {
+ // Note that at this point, we're leaving the tethered state. We can fail any
+ // of these operations, but it doesn't really change that we have to try them
+ // all in sequence.
+ stopIPv6();
+
+ try {
+ mNMService.untetherInterface(mIfaceName);
+ } catch (Exception e) {
+ mLastError = ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
+ mLog.e("Failed to untether interface: " + e);
+ }
+
+ stopIPv4();
+
+ resetLinkProperties();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ logMessage(this, message.what);
+ switch (message.what) {
+ case CMD_TETHER_UNREQUESTED:
+ transitionTo(mInitialState);
+ if (DBG) Log.d(TAG, "Untethered (unrequested)" + mIfaceName);
+ break;
+ case CMD_INTERFACE_DOWN:
+ transitionTo(mUnavailableState);
+ if (DBG) Log.d(TAG, "Untethered (ifdown)" + mIfaceName);
+ break;
+ case CMD_IPV6_TETHER_UPDATE:
+ updateUpstreamIPv6LinkProperties((LinkProperties) message.obj);
+ sendLinkProperties();
+ break;
+ case CMD_IP_FORWARDING_ENABLE_ERROR:
+ case CMD_IP_FORWARDING_DISABLE_ERROR:
+ case CMD_START_TETHERING_ERROR:
+ case CMD_STOP_TETHERING_ERROR:
+ case CMD_SET_DNS_FORWARDERS_ERROR:
+ mLastError = ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
+ transitionTo(mInitialState);
+ break;
+ default:
+ return false;
+ }
+ return true;
+ }
+ }
+
+ // Handling errors in BaseServingState.enter() by transitioning is
+ // problematic because transitioning during a multi-state jump yields
+ // a Log.wtf(). Ultimately, there should be only one ServingState,
+ // and forwarding and NAT rules should be handled by a coordinating
+ // functional element outside of IpServer.
+ class LocalHotspotState extends BaseServingState {
+ @Override
+ public void enter() {
+ super.enter();
+ if (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+ transitionTo(mInitialState);
+ }
+
+ if (DBG) Log.d(TAG, "Local hotspot " + mIfaceName);
+ sendInterfaceState(STATE_LOCAL_ONLY);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (super.processMessage(message)) return true;
+
+ logMessage(this, message.what);
+ switch (message.what) {
+ case CMD_TETHER_REQUESTED:
+ mLog.e("CMD_TETHER_REQUESTED while in local-only hotspot mode.");
+ break;
+ case CMD_TETHER_CONNECTION_CHANGED:
+ // Ignored in local hotspot state.
+ break;
+ default:
+ return false;
+ }
+ return true;
+ }
+ }
+
+ // Handling errors in BaseServingState.enter() by transitioning is
+ // problematic because transitioning during a multi-state jump yields
+ // a Log.wtf(). Ultimately, there should be only one ServingState,
+ // and forwarding and NAT rules should be handled by a coordinating
+ // functional element outside of IpServer.
+ class TetheredState extends BaseServingState {
+ @Override
+ public void enter() {
+ super.enter();
+ if (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+ transitionTo(mInitialState);
+ }
+
+ if (DBG) Log.d(TAG, "Tethered " + mIfaceName);
+ sendInterfaceState(STATE_TETHERED);
+ }
+
+ @Override
+ public void exit() {
+ cleanupUpstream();
+ super.exit();
+ }
+
+ private void cleanupUpstream() {
+ if (mUpstreamIfaceSet == null) return;
+
+ for (String ifname : mUpstreamIfaceSet.ifnames) cleanupUpstreamInterface(ifname);
+ mUpstreamIfaceSet = null;
+ }
+
+ private void cleanupUpstreamInterface(String upstreamIface) {
+ // Note that we don't care about errors here.
+ // Sometimes interfaces are gone before we get
+ // to remove their rules, which generates errors.
+ // Just do the best we can.
+ try {
+ // About to tear down NAT; gather remaining statistics.
+ mStatsService.forceUpdate();
+ } catch (Exception e) {
+ if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString());
+ }
+ try {
+ mNMService.stopInterfaceForwarding(mIfaceName, upstreamIface);
+ } catch (Exception e) {
+ if (VDBG) Log.e(TAG, "Exception in removeInterfaceForward: " + e.toString());
+ }
+ try {
+ mNMService.disableNat(mIfaceName, upstreamIface);
+ } catch (Exception e) {
+ if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString());
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (super.processMessage(message)) return true;
+
+ logMessage(this, message.what);
+ switch (message.what) {
+ case CMD_TETHER_REQUESTED:
+ mLog.e("CMD_TETHER_REQUESTED while already tethering.");
+ break;
+ case CMD_TETHER_CONNECTION_CHANGED:
+ final InterfaceSet newUpstreamIfaceSet = (InterfaceSet) message.obj;
+ if (noChangeInUpstreamIfaceSet(newUpstreamIfaceSet)) {
+ if (VDBG) Log.d(TAG, "Connection changed noop - dropping");
+ break;
+ }
+
+ if (newUpstreamIfaceSet == null) {
+ cleanupUpstream();
+ break;
+ }
+
+ for (String removed : upstreamInterfacesRemoved(newUpstreamIfaceSet)) {
+ cleanupUpstreamInterface(removed);
+ }
+
+ final Set<String> added = upstreamInterfacesAdd(newUpstreamIfaceSet);
+ // This makes the call to cleanupUpstream() in the error
+ // path for any interface neatly cleanup all the interfaces.
+ mUpstreamIfaceSet = newUpstreamIfaceSet;
+
+ for (String ifname : added) {
+ try {
+ mNMService.enableNat(mIfaceName, ifname);
+ mNMService.startInterfaceForwarding(mIfaceName, ifname);
+ } catch (Exception e) {
+ mLog.e("Exception enabling NAT: " + e);
+ cleanupUpstream();
+ mLastError = ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
+ transitionTo(mInitialState);
+ return true;
+ }
+ }
+ break;
+ default:
+ return false;
+ }
+ return true;
+ }
+
+ private boolean noChangeInUpstreamIfaceSet(InterfaceSet newIfaces) {
+ if (mUpstreamIfaceSet == null && newIfaces == null) return true;
+ if (mUpstreamIfaceSet != null && newIfaces != null) {
+ return mUpstreamIfaceSet.equals(newIfaces);
+ }
+ return false;
+ }
+
+ private Set<String> upstreamInterfacesRemoved(InterfaceSet newIfaces) {
+ if (mUpstreamIfaceSet == null) return new HashSet<>();
+
+ final HashSet<String> removed = new HashSet<>(mUpstreamIfaceSet.ifnames);
+ removed.removeAll(newIfaces.ifnames);
+ return removed;
+ }
+
+ private Set<String> upstreamInterfacesAdd(InterfaceSet newIfaces) {
+ final HashSet<String> added = new HashSet<>(newIfaces.ifnames);
+ if (mUpstreamIfaceSet != null) added.removeAll(mUpstreamIfaceSet.ifnames);
+ return added;
+ }
+ }
+
+ /**
+ * This state is terminal for the per interface state machine. At this
+ * point, the master state machine should have removed this interface
+ * specific state machine from its list of possible recipients of
+ * tethering requests. The state machine itself will hang around until
+ * the garbage collector finds it.
+ */
+ class UnavailableState extends State {
+ @Override
+ public void enter() {
+ mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+ sendInterfaceState(STATE_UNAVAILABLE);
+ }
+ }
+
+ // Accumulate routes representing "prefixes to be assigned to the local
+ // interface", for subsequent modification of local_network routing.
+ private static ArrayList<RouteInfo> getLocalRoutesFor(
+ String ifname, HashSet<IpPrefix> prefixes) {
+ final ArrayList<RouteInfo> localRoutes = new ArrayList<RouteInfo>();
+ for (IpPrefix ipp : prefixes) {
+ localRoutes.add(new RouteInfo(ipp, null, ifname));
+ }
+ return localRoutes;
+ }
+
+ // Given a prefix like 2001:db8::/64 return an address like 2001:db8::1.
+ private static Inet6Address getLocalDnsIpFor(IpPrefix localPrefix) {
+ final byte[] dnsBytes = localPrefix.getRawAddress();
+ dnsBytes[dnsBytes.length - 1] = getRandomSanitizedByte(DOUG_ADAMS, asByte(0), asByte(1));
+ try {
+ return Inet6Address.getByAddress(null, dnsBytes, 0);
+ } catch (UnknownHostException e) {
+ Slog.wtf(TAG, "Failed to construct Inet6Address from: " + localPrefix);
+ return null;
+ }
+ }
+
+ private static byte getRandomSanitizedByte(byte dflt, byte... excluded) {
+ final byte random = (byte) (new Random()).nextInt();
+ for (int value : excluded) {
+ if (random == value) return dflt;
+ }
+ return random;
+ }
+}
diff --git a/android/net/ip/RouterAdvertisementDaemon.java b/android/net/ip/RouterAdvertisementDaemon.java
new file mode 100644
index 0000000..59aea21
--- /dev/null
+++ b/android/net/ip/RouterAdvertisementDaemon.java
@@ -0,0 +1,744 @@
+/*
+ * Copyright (C) 2016 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.ip;
+
+import static android.net.util.NetworkConstants.IPV6_MIN_MTU;
+import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static android.system.OsConstants.SOCK_RAW;
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_BINDTODEVICE;
+import static android.system.OsConstants.SO_SNDTIMEO;
+
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.NetworkUtils;
+import android.net.TrafficStats;
+import android.net.util.InterfaceParams;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructTimeval;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.TrafficStatsConstants;
+
+import libcore.io.IoBridge;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+/**
+ * Basic IPv6 Router Advertisement Daemon.
+ *
+ * TODO:
+ *
+ * - Rewrite using Handler (and friends) so that AlarmManager can deliver
+ * "kick" messages when it's time to send a multicast RA.
+ *
+ * @hide
+ */
+public class RouterAdvertisementDaemon {
+ private static final String TAG = RouterAdvertisementDaemon.class.getSimpleName();
+ private static final byte ICMPV6_ND_ROUTER_SOLICIT = asByte(133);
+ private static final byte ICMPV6_ND_ROUTER_ADVERT = asByte(134);
+ private static final int MIN_RA_HEADER_SIZE = 16;
+
+ // Summary of various timers and lifetimes.
+ private static final int MIN_RTR_ADV_INTERVAL_SEC = 300;
+ private static final int MAX_RTR_ADV_INTERVAL_SEC = 600;
+ // In general, router, prefix, and DNS lifetimes are all advised to be
+ // greater than or equal to 3 * MAX_RTR_ADV_INTERVAL. Here, we double
+ // that to allow for multicast packet loss.
+ //
+ // This MAX_RTR_ADV_INTERVAL_SEC and DEFAULT_LIFETIME are also consistent
+ // with the https://tools.ietf.org/html/rfc7772#section-4 discussion of
+ // "approximately 7 RAs per hour".
+ private static final int DEFAULT_LIFETIME = 6 * MAX_RTR_ADV_INTERVAL_SEC;
+ // From https://tools.ietf.org/html/rfc4861#section-10 .
+ private static final int MIN_DELAY_BETWEEN_RAS_SEC = 3;
+ // Both initial and final RAs, but also for changes in RA contents.
+ // From https://tools.ietf.org/html/rfc4861#section-10 .
+ private static final int MAX_URGENT_RTR_ADVERTISEMENTS = 5;
+
+ private static final int DAY_IN_SECONDS = 86_400;
+
+ private static final byte[] ALL_NODES = new byte[] {
+ (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
+ };
+
+ private final InterfaceParams mInterface;
+ private final InetSocketAddress mAllNodes;
+
+ // This lock is to protect the RA from being updated while being
+ // transmitted on another thread (multicast or unicast).
+ //
+ // TODO: This should be handled with a more RCU-like approach.
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final byte[] mRA = new byte[IPV6_MIN_MTU];
+ @GuardedBy("mLock")
+ private int mRaLength;
+ @GuardedBy("mLock")
+ private final DeprecatedInfoTracker mDeprecatedInfoTracker;
+ @GuardedBy("mLock")
+ private RaParams mRaParams;
+
+ private volatile FileDescriptor mSocket;
+ private volatile MulticastTransmitter mMulticastTransmitter;
+ private volatile UnicastResponder mUnicastResponder;
+
+ public static class RaParams {
+ // Tethered traffic will have the hop limit properly decremented.
+ // Consequently, set the hoplimit greater by one than the upstream
+ // unicast hop limit.
+ //
+ // TODO: Dynamically pass down the IPV6_UNICAST_HOPS value from the
+ // upstream interface for more correct behaviour.
+ static final byte DEFAULT_HOPLIMIT = 65;
+
+ public boolean hasDefaultRoute;
+ public byte hopLimit;
+ public int mtu;
+ public HashSet<IpPrefix> prefixes;
+ public HashSet<Inet6Address> dnses;
+
+ public RaParams() {
+ hasDefaultRoute = false;
+ hopLimit = DEFAULT_HOPLIMIT;
+ mtu = IPV6_MIN_MTU;
+ prefixes = new HashSet<IpPrefix>();
+ dnses = new HashSet<Inet6Address>();
+ }
+
+ public RaParams(RaParams other) {
+ hasDefaultRoute = other.hasDefaultRoute;
+ hopLimit = other.hopLimit;
+ mtu = other.mtu;
+ prefixes = (HashSet) other.prefixes.clone();
+ dnses = (HashSet) other.dnses.clone();
+ }
+
+ // Returns the subset of RA parameters that become deprecated when
+ // moving from announcing oldRa to announcing newRa.
+ //
+ // Currently only tracks differences in |prefixes| and |dnses|.
+ public static RaParams getDeprecatedRaParams(RaParams oldRa, RaParams newRa) {
+ RaParams newlyDeprecated = new RaParams();
+
+ if (oldRa != null) {
+ for (IpPrefix ipp : oldRa.prefixes) {
+ if (newRa == null || !newRa.prefixes.contains(ipp)) {
+ newlyDeprecated.prefixes.add(ipp);
+ }
+ }
+
+ for (Inet6Address dns : oldRa.dnses) {
+ if (newRa == null || !newRa.dnses.contains(dns)) {
+ newlyDeprecated.dnses.add(dns);
+ }
+ }
+ }
+
+ return newlyDeprecated;
+ }
+ }
+
+ private static class DeprecatedInfoTracker {
+ private final HashMap<IpPrefix, Integer> mPrefixes = new HashMap<>();
+ private final HashMap<Inet6Address, Integer> mDnses = new HashMap<>();
+
+ Set<IpPrefix> getPrefixes() { return mPrefixes.keySet(); }
+
+ void putPrefixes(Set<IpPrefix> prefixes) {
+ for (IpPrefix ipp : prefixes) {
+ mPrefixes.put(ipp, MAX_URGENT_RTR_ADVERTISEMENTS);
+ }
+ }
+
+ void removePrefixes(Set<IpPrefix> prefixes) {
+ for (IpPrefix ipp : prefixes) {
+ mPrefixes.remove(ipp);
+ }
+ }
+
+ Set<Inet6Address> getDnses() { return mDnses.keySet(); }
+
+ void putDnses(Set<Inet6Address> dnses) {
+ for (Inet6Address dns : dnses) {
+ mDnses.put(dns, MAX_URGENT_RTR_ADVERTISEMENTS);
+ }
+ }
+
+ void removeDnses(Set<Inet6Address> dnses) {
+ for (Inet6Address dns : dnses) {
+ mDnses.remove(dns);
+ }
+ }
+
+ boolean isEmpty() { return mPrefixes.isEmpty() && mDnses.isEmpty(); }
+
+ private boolean decrementCounters() {
+ boolean removed = decrementCounter(mPrefixes);
+ removed |= decrementCounter(mDnses);
+ return removed;
+ }
+
+ private <T> boolean decrementCounter(HashMap<T, Integer> map) {
+ boolean removed = false;
+
+ for (Iterator<Map.Entry<T, Integer>> it = map.entrySet().iterator();
+ it.hasNext();) {
+ Map.Entry<T, Integer> kv = it.next();
+ if (kv.getValue() == 0) {
+ it.remove();
+ removed = true;
+ } else {
+ kv.setValue(kv.getValue() - 1);
+ }
+ }
+
+ return removed;
+ }
+ }
+
+
+ public RouterAdvertisementDaemon(InterfaceParams ifParams) {
+ mInterface = ifParams;
+ mAllNodes = new InetSocketAddress(getAllNodesForScopeId(mInterface.index), 0);
+ mDeprecatedInfoTracker = new DeprecatedInfoTracker();
+ }
+
+ public void buildNewRa(RaParams deprecatedParams, RaParams newParams) {
+ synchronized (mLock) {
+ if (deprecatedParams != null) {
+ mDeprecatedInfoTracker.putPrefixes(deprecatedParams.prefixes);
+ mDeprecatedInfoTracker.putDnses(deprecatedParams.dnses);
+ }
+
+ if (newParams != null) {
+ // Process information that is no longer deprecated.
+ mDeprecatedInfoTracker.removePrefixes(newParams.prefixes);
+ mDeprecatedInfoTracker.removeDnses(newParams.dnses);
+ }
+
+ mRaParams = newParams;
+ assembleRaLocked();
+ }
+
+ maybeNotifyMulticastTransmitter();
+ }
+
+ public boolean start() {
+ if (!createSocket()) {
+ return false;
+ }
+
+ mMulticastTransmitter = new MulticastTransmitter();
+ mMulticastTransmitter.start();
+
+ mUnicastResponder = new UnicastResponder();
+ mUnicastResponder.start();
+
+ return true;
+ }
+
+ public void stop() {
+ closeSocket();
+ // Wake up mMulticastTransmitter thread to interrupt a potential 1 day sleep before
+ // the thread's termination.
+ maybeNotifyMulticastTransmitter();
+ mMulticastTransmitter = null;
+ mUnicastResponder = null;
+ }
+
+ @GuardedBy("mLock")
+ private void assembleRaLocked() {
+ final ByteBuffer ra = ByteBuffer.wrap(mRA);
+ ra.order(ByteOrder.BIG_ENDIAN);
+
+ final boolean haveRaParams = (mRaParams != null);
+ boolean shouldSendRA = false;
+
+ try {
+ putHeader(ra, haveRaParams && mRaParams.hasDefaultRoute,
+ haveRaParams ? mRaParams.hopLimit : RaParams.DEFAULT_HOPLIMIT);
+ putSlla(ra, mInterface.macAddr.toByteArray());
+ mRaLength = ra.position();
+
+ // https://tools.ietf.org/html/rfc5175#section-4 says:
+ //
+ // "MUST NOT be added to a Router Advertisement message
+ // if no flags in the option are set."
+ //
+ // putExpandedFlagsOption(ra);
+
+ if (haveRaParams) {
+ putMtu(ra, mRaParams.mtu);
+ mRaLength = ra.position();
+
+ for (IpPrefix ipp : mRaParams.prefixes) {
+ putPio(ra, ipp, DEFAULT_LIFETIME, DEFAULT_LIFETIME);
+ mRaLength = ra.position();
+ shouldSendRA = true;
+ }
+
+ if (mRaParams.dnses.size() > 0) {
+ putRdnss(ra, mRaParams.dnses, DEFAULT_LIFETIME);
+ mRaLength = ra.position();
+ shouldSendRA = true;
+ }
+ }
+
+ for (IpPrefix ipp : mDeprecatedInfoTracker.getPrefixes()) {
+ putPio(ra, ipp, 0, 0);
+ mRaLength = ra.position();
+ shouldSendRA = true;
+ }
+
+ final Set<Inet6Address> deprecatedDnses = mDeprecatedInfoTracker.getDnses();
+ if (!deprecatedDnses.isEmpty()) {
+ putRdnss(ra, deprecatedDnses, 0);
+ mRaLength = ra.position();
+ shouldSendRA = true;
+ }
+ } catch (BufferOverflowException e) {
+ // The packet up to mRaLength is valid, since it has been updated
+ // progressively as the RA was built. Log an error, and continue
+ // on as best as possible.
+ Log.e(TAG, "Could not construct new RA: " + e);
+ }
+
+ // We have nothing worth announcing; indicate as much to maybeSendRA().
+ if (!shouldSendRA) {
+ mRaLength = 0;
+ }
+ }
+
+ private void maybeNotifyMulticastTransmitter() {
+ final MulticastTransmitter m = mMulticastTransmitter;
+ if (m != null) {
+ m.hup();
+ }
+ }
+
+ private static Inet6Address getAllNodesForScopeId(int scopeId) {
+ try {
+ return Inet6Address.getByAddress("ff02::1", ALL_NODES, scopeId);
+ } catch (UnknownHostException uhe) {
+ Log.wtf(TAG, "Failed to construct ff02::1 InetAddress: " + uhe);
+ return null;
+ }
+ }
+
+ private static byte asByte(int value) { return (byte) value; }
+ private static short asShort(int value) { return (short) value; }
+
+ private static void putHeader(ByteBuffer ra, boolean hasDefaultRoute, byte hopLimit) {
+ /**
+ Router Advertisement Message Format
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Code | Checksum |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Cur Hop Limit |M|O|H|Prf|P|R|R| Router Lifetime |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Reachable Time |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Retrans Timer |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Options ...
+ +-+-+-+-+-+-+-+-+-+-+-+-
+ */
+ ra.put(ICMPV6_ND_ROUTER_ADVERT)
+ .put(asByte(0))
+ .putShort(asShort(0))
+ .put(hopLimit)
+ // RFC 4191 "high" preference, iff. advertising a default route.
+ .put(hasDefaultRoute ? asByte(0x08) : asByte(0))
+ .putShort(hasDefaultRoute ? asShort(DEFAULT_LIFETIME) : asShort(0))
+ .putInt(0)
+ .putInt(0);
+ }
+
+ private static void putSlla(ByteBuffer ra, byte[] slla) {
+ /**
+ Source/Target Link-layer Address
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Length | Link-Layer Address ...
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+ if (slla == null || slla.length != 6) {
+ // Only IEEE 802.3 6-byte addresses are supported.
+ return;
+ }
+ final byte ND_OPTION_SLLA = 1;
+ final byte SLLA_NUM_8OCTETS = 1;
+ ra.put(ND_OPTION_SLLA)
+ .put(SLLA_NUM_8OCTETS)
+ .put(slla);
+ }
+
+ private static void putExpandedFlagsOption(ByteBuffer ra) {
+ /**
+ Router Advertisement Expanded Flags Option
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Length | Bit fields available ..
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ ... for assignment |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+
+ final byte ND_OPTION_EFO = 26;
+ final byte EFO_NUM_8OCTETS = 1;
+
+ ra.put(ND_OPTION_EFO)
+ .put(EFO_NUM_8OCTETS)
+ .putShort(asShort(0))
+ .putInt(0);
+ }
+
+ private static void putMtu(ByteBuffer ra, int mtu) {
+ /**
+ MTU
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Length | Reserved |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | MTU |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+ final byte ND_OPTION_MTU = 5;
+ final byte MTU_NUM_8OCTETS = 1;
+ ra.put(ND_OPTION_MTU)
+ .put(MTU_NUM_8OCTETS)
+ .putShort(asShort(0))
+ .putInt((mtu < IPV6_MIN_MTU) ? IPV6_MIN_MTU : mtu);
+ }
+
+ private static void putPio(ByteBuffer ra, IpPrefix ipp,
+ int validTime, int preferredTime) {
+ /**
+ Prefix Information
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Length | Prefix Length |L|A| Reserved1 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Valid Lifetime |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Preferred Lifetime |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Reserved2 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ + +
+ | |
+ + Prefix +
+ | |
+ + +
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+ final int prefixLength = ipp.getPrefixLength();
+ if (prefixLength != 64) {
+ return;
+ }
+ final byte ND_OPTION_PIO = 3;
+ final byte PIO_NUM_8OCTETS = 4;
+
+ if (validTime < 0) validTime = 0;
+ if (preferredTime < 0) preferredTime = 0;
+ if (preferredTime > validTime) preferredTime = validTime;
+
+ final byte[] addr = ipp.getAddress().getAddress();
+ ra.put(ND_OPTION_PIO)
+ .put(PIO_NUM_8OCTETS)
+ .put(asByte(prefixLength))
+ .put(asByte(0xc0)) /* L & A set */
+ .putInt(validTime)
+ .putInt(preferredTime)
+ .putInt(0)
+ .put(addr);
+ }
+
+ private static void putRio(ByteBuffer ra, IpPrefix ipp) {
+ /**
+ Route Information Option
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Length | Prefix Length |Resvd|Prf|Resvd|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Route Lifetime |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Prefix (Variable Length) |
+ . .
+ . .
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+ final int prefixLength = ipp.getPrefixLength();
+ if (prefixLength > 64) {
+ return;
+ }
+ final byte ND_OPTION_RIO = 24;
+ final byte RIO_NUM_8OCTETS = asByte(
+ (prefixLength == 0) ? 1 : (prefixLength <= 8) ? 2 : 3);
+
+ final byte[] addr = ipp.getAddress().getAddress();
+ ra.put(ND_OPTION_RIO)
+ .put(RIO_NUM_8OCTETS)
+ .put(asByte(prefixLength))
+ .put(asByte(0x18))
+ .putInt(DEFAULT_LIFETIME);
+
+ // Rely upon an IpPrefix's address being properly zeroed.
+ if (prefixLength > 0) {
+ ra.put(addr, 0, (prefixLength <= 64) ? 8 : 16);
+ }
+ }
+
+ private static void putRdnss(ByteBuffer ra, Set<Inet6Address> dnses, int lifetime) {
+ /**
+ Recursive DNS Server (RDNSS) Option
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Length | Reserved |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Lifetime |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ : Addresses of IPv6 Recursive DNS Servers :
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+
+ final HashSet<Inet6Address> filteredDnses = new HashSet<>();
+ for (Inet6Address dns : dnses) {
+ if ((new LinkAddress(dns, RFC7421_PREFIX_LENGTH)).isGlobalPreferred()) {
+ filteredDnses.add(dns);
+ }
+ }
+ if (filteredDnses.isEmpty()) return;
+
+ final byte ND_OPTION_RDNSS = 25;
+ final byte RDNSS_NUM_8OCTETS = asByte(dnses.size() * 2 + 1);
+ ra.put(ND_OPTION_RDNSS)
+ .put(RDNSS_NUM_8OCTETS)
+ .putShort(asShort(0))
+ .putInt(lifetime);
+
+ for (Inet6Address dns : filteredDnses) {
+ // NOTE: If the full of list DNS servers doesn't fit in the packet,
+ // this code will cause a buffer overflow and the RA won't include
+ // this instance of the option at all.
+ //
+ // TODO: Consider looking at ra.remaining() to determine how many
+ // DNS servers will fit, and adding only those.
+ ra.put(dns.getAddress());
+ }
+ }
+
+ private boolean createSocket() {
+ final int SEND_TIMEOUT_MS = 300;
+
+ final int oldTag = TrafficStats.getAndSetThreadStatsTag(
+ TrafficStatsConstants.TAG_SYSTEM_NEIGHBOR);
+ try {
+ mSocket = Os.socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
+ // Setting SNDTIMEO is purely for defensive purposes.
+ Os.setsockoptTimeval(
+ mSocket, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(SEND_TIMEOUT_MS));
+ Os.setsockoptIfreq(mSocket, SOL_SOCKET, SO_BINDTODEVICE, mInterface.name);
+ NetworkUtils.protectFromVpn(mSocket);
+ NetworkUtils.setupRaSocket(mSocket, mInterface.index);
+ } catch (ErrnoException | IOException e) {
+ Log.e(TAG, "Failed to create RA daemon socket: " + e);
+ return false;
+ } finally {
+ TrafficStats.setThreadStatsTag(oldTag);
+ }
+
+ return true;
+ }
+
+ private void closeSocket() {
+ if (mSocket != null) {
+ try {
+ IoBridge.closeAndSignalBlockedThreads(mSocket);
+ } catch (IOException ignored) {}
+ }
+ mSocket = null;
+ }
+
+ private boolean isSocketValid() {
+ final FileDescriptor s = mSocket;
+ return (s != null) && s.valid();
+ }
+
+ private boolean isSuitableDestination(InetSocketAddress dest) {
+ if (mAllNodes.equals(dest)) {
+ return true;
+ }
+
+ final InetAddress destip = dest.getAddress();
+ return (destip instanceof Inet6Address) &&
+ destip.isLinkLocalAddress() &&
+ (((Inet6Address) destip).getScopeId() == mInterface.index);
+ }
+
+ private void maybeSendRA(InetSocketAddress dest) {
+ if (dest == null || !isSuitableDestination(dest)) {
+ dest = mAllNodes;
+ }
+
+ try {
+ synchronized (mLock) {
+ if (mRaLength < MIN_RA_HEADER_SIZE) {
+ // No actual RA to send.
+ return;
+ }
+ Os.sendto(mSocket, mRA, 0, mRaLength, 0, dest);
+ }
+ Log.d(TAG, "RA sendto " + dest.getAddress().getHostAddress());
+ } catch (ErrnoException | SocketException e) {
+ if (isSocketValid()) {
+ Log.e(TAG, "sendto error: " + e);
+ }
+ }
+ }
+
+ private final class UnicastResponder extends Thread {
+ private final InetSocketAddress solicitor = new InetSocketAddress();
+ // The recycled buffer for receiving Router Solicitations from clients.
+ // If the RS is larger than IPV6_MIN_MTU the packets are truncated.
+ // This is fine since currently only byte 0 is examined anyway.
+ private final byte mSolication[] = new byte[IPV6_MIN_MTU];
+
+ @Override
+ public void run() {
+ while (isSocketValid()) {
+ try {
+ // Blocking receive.
+ final int rval = Os.recvfrom(
+ mSocket, mSolication, 0, mSolication.length, 0, solicitor);
+ // Do the least possible amount of validation.
+ if (rval < 1 || mSolication[0] != ICMPV6_ND_ROUTER_SOLICIT) {
+ continue;
+ }
+ } catch (ErrnoException | SocketException e) {
+ if (isSocketValid()) {
+ Log.e(TAG, "recvfrom error: " + e);
+ }
+ continue;
+ }
+
+ maybeSendRA(solicitor);
+ }
+ }
+ }
+
+ // TODO: Consider moving this to run on a provided Looper as a Handler,
+ // with WakeupMessage-style messages providing the timer driven input.
+ private final class MulticastTransmitter extends Thread {
+ private final Random mRandom = new Random();
+ private final AtomicInteger mUrgentAnnouncements = new AtomicInteger(0);
+
+ @Override
+ public void run() {
+ while (isSocketValid()) {
+ try {
+ Thread.sleep(getNextMulticastTransmitDelayMs());
+ } catch (InterruptedException ignored) {
+ // Stop sleeping, immediately send an RA, and continue.
+ }
+
+ maybeSendRA(mAllNodes);
+ synchronized (mLock) {
+ if (mDeprecatedInfoTracker.decrementCounters()) {
+ // At least one deprecated PIO has been removed;
+ // reassemble the RA.
+ assembleRaLocked();
+ }
+ }
+ }
+ }
+
+ public void hup() {
+ // Set to one fewer that the desired number, because as soon as
+ // the thread interrupt is processed we immediately send an RA
+ // and mUrgentAnnouncements is not examined until the subsequent
+ // sleep interval computation (i.e. this way we send 3 and not 4).
+ mUrgentAnnouncements.set(MAX_URGENT_RTR_ADVERTISEMENTS - 1);
+ interrupt();
+ }
+
+ private int getNextMulticastTransmitDelaySec() {
+ boolean deprecationInProgress = false;
+ synchronized (mLock) {
+ if (mRaLength < MIN_RA_HEADER_SIZE) {
+ // No actual RA to send; just sleep for 1 day.
+ return DAY_IN_SECONDS;
+ }
+ deprecationInProgress = !mDeprecatedInfoTracker.isEmpty();
+ }
+
+ final int urgentPending = mUrgentAnnouncements.getAndDecrement();
+ if ((urgentPending > 0) || deprecationInProgress) {
+ return MIN_DELAY_BETWEEN_RAS_SEC;
+ }
+
+ return MIN_RTR_ADV_INTERVAL_SEC + mRandom.nextInt(
+ MAX_RTR_ADV_INTERVAL_SEC - MIN_RTR_ADV_INTERVAL_SEC);
+ }
+
+ private long getNextMulticastTransmitDelayMs() {
+ return 1000 * (long) getNextMulticastTransmitDelaySec();
+ }
+ }
+}
diff --git a/android/net/ipmemorystore/NetworkAttributes.java b/android/net/ipmemorystore/NetworkAttributes.java
new file mode 100644
index 0000000..818515a
--- /dev/null
+++ b/android/net/ipmemorystore/NetworkAttributes.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.StringJoiner;
+
+/**
+ * A POD object to represent attributes of a single L2 network entry.
+ * @hide
+ */
+public class NetworkAttributes {
+ private static final boolean DBG = true;
+
+ // Weight cutoff for grouping. To group, a similarity score is computed with the following
+ // algorithm : if both fields are non-null and equals() then add their assigned weight, else if
+ // both are null then add a portion of their assigned weight (see NULL_MATCH_WEIGHT),
+ // otherwise add nothing.
+ // As a guideline, this should be something like 60~75% of the total weights in this class. The
+ // design states "in essence a reader should imagine that if two important columns don't match,
+ // or one important and several unimportant columns don't match then the two records are
+ // considered a different group".
+ private static final float TOTAL_WEIGHT_CUTOFF = 520.0f;
+ // The portion of the weight that is earned when scoring group-sameness by having both columns
+ // being null. This is because some networks rightfully don't have some attributes (e.g. a
+ // V6-only network won't have an assigned V4 address) and both being null should count for
+ // something, but attributes may also be null just because data is unavailable.
+ private static final float NULL_MATCH_WEIGHT = 0.25f;
+
+ // The v4 address that was assigned to this device the last time it joined this network.
+ // This typically comes from DHCP but could be something else like static configuration.
+ // This does not apply to IPv6.
+ // TODO : add a list of v6 prefixes for the v6 case.
+ @Nullable
+ public final Inet4Address assignedV4Address;
+ private static final float WEIGHT_ASSIGNEDV4ADDR = 300.0f;
+
+ // The lease expiry timestamp of v4 address allocated from DHCP server, in milliseconds.
+ @Nullable
+ public final Long assignedV4AddressExpiry;
+ // lease expiry doesn't imply any correlation between "the same lease expiry value" and "the
+ // same L3 network".
+ private static final float WEIGHT_ASSIGNEDV4ADDREXPIRY = 0.0f;
+
+ // Optionally supplied by the client if it has an opinion on L3 network. For example, this
+ // could be a hash of the SSID + security type on WiFi.
+ @Nullable
+ public final String groupHint;
+ private static final float WEIGHT_GROUPHINT = 300.0f;
+
+ // The list of DNS server addresses.
+ @Nullable
+ public final List<InetAddress> dnsAddresses;
+ private static final float WEIGHT_DNSADDRESSES = 200.0f;
+
+ // The mtu on this network.
+ @Nullable
+ public final Integer mtu;
+ private static final float WEIGHT_MTU = 50.0f;
+
+ // The sum of all weights in this class. Tests ensure that this stays equal to the total of
+ // all weights.
+ /** @hide */
+ @VisibleForTesting
+ public static final float TOTAL_WEIGHT = WEIGHT_ASSIGNEDV4ADDR
+ + WEIGHT_ASSIGNEDV4ADDREXPIRY
+ + WEIGHT_GROUPHINT
+ + WEIGHT_DNSADDRESSES
+ + WEIGHT_MTU;
+
+ /** @hide */
+ @VisibleForTesting
+ public NetworkAttributes(
+ @Nullable final Inet4Address assignedV4Address,
+ @Nullable final Long assignedV4AddressExpiry,
+ @Nullable final String groupHint,
+ @Nullable final List<InetAddress> dnsAddresses,
+ @Nullable final Integer mtu) {
+ if (mtu != null && mtu < 0) throw new IllegalArgumentException("MTU can't be negative");
+ if (assignedV4AddressExpiry != null && assignedV4AddressExpiry <= 0) {
+ throw new IllegalArgumentException("lease expiry can't be negative or zero");
+ }
+ this.assignedV4Address = assignedV4Address;
+ this.assignedV4AddressExpiry = assignedV4AddressExpiry;
+ this.groupHint = groupHint;
+ this.dnsAddresses = null == dnsAddresses ? null :
+ Collections.unmodifiableList(new ArrayList<>(dnsAddresses));
+ this.mtu = mtu;
+ }
+
+ @VisibleForTesting
+ public NetworkAttributes(@NonNull final NetworkAttributesParcelable parcelable) {
+ // The call to the other constructor must be the first statement of this constructor,
+ // so everything has to be inline
+ this((Inet4Address) getByAddressOrNull(parcelable.assignedV4Address),
+ parcelable.assignedV4AddressExpiry > 0
+ ? parcelable.assignedV4AddressExpiry : null,
+ parcelable.groupHint,
+ blobArrayToInetAddressList(parcelable.dnsAddresses),
+ parcelable.mtu >= 0 ? parcelable.mtu : null);
+ }
+
+ @Nullable
+ private static InetAddress getByAddressOrNull(@Nullable final byte[] address) {
+ if (null == address) return null;
+ try {
+ return InetAddress.getByAddress(address);
+ } catch (UnknownHostException e) {
+ return null;
+ }
+ }
+
+ @Nullable
+ private static List<InetAddress> blobArrayToInetAddressList(@Nullable final Blob[] blobs) {
+ if (null == blobs) return null;
+ final ArrayList<InetAddress> list = new ArrayList<>(blobs.length);
+ for (final Blob b : blobs) {
+ final InetAddress addr = getByAddressOrNull(b.data);
+ if (null != addr) list.add(addr);
+ }
+ return list;
+ }
+
+ @Nullable
+ private static Blob[] inetAddressListToBlobArray(@Nullable final List<InetAddress> addresses) {
+ if (null == addresses) return null;
+ final ArrayList<Blob> blobs = new ArrayList<>();
+ for (int i = 0; i < addresses.size(); ++i) {
+ final InetAddress addr = addresses.get(i);
+ if (null == addr) continue;
+ final Blob b = new Blob();
+ b.data = addr.getAddress();
+ blobs.add(b);
+ }
+ return blobs.toArray(new Blob[0]);
+ }
+
+ /** Converts this NetworkAttributes to a parcelable object */
+ @NonNull
+ public NetworkAttributesParcelable toParcelable() {
+ final NetworkAttributesParcelable parcelable = new NetworkAttributesParcelable();
+ parcelable.assignedV4Address =
+ (null == assignedV4Address) ? null : assignedV4Address.getAddress();
+ parcelable.assignedV4AddressExpiry =
+ (null == assignedV4AddressExpiry) ? 0 : assignedV4AddressExpiry;
+ parcelable.groupHint = groupHint;
+ parcelable.dnsAddresses = inetAddressListToBlobArray(dnsAddresses);
+ parcelable.mtu = (null == mtu) ? -1 : mtu;
+ return parcelable;
+ }
+
+ private float samenessContribution(final float weight,
+ @Nullable final Object o1, @Nullable final Object o2) {
+ if (null == o1) {
+ return (null == o2) ? weight * NULL_MATCH_WEIGHT : 0f;
+ }
+ return Objects.equals(o1, o2) ? weight : 0f;
+ }
+
+ /** @hide */
+ public float getNetworkGroupSamenessConfidence(@NonNull final NetworkAttributes o) {
+ final float samenessScore =
+ samenessContribution(WEIGHT_ASSIGNEDV4ADDR, assignedV4Address, o.assignedV4Address)
+ + samenessContribution(WEIGHT_ASSIGNEDV4ADDREXPIRY, assignedV4AddressExpiry,
+ o.assignedV4AddressExpiry)
+ + samenessContribution(WEIGHT_GROUPHINT, groupHint, o.groupHint)
+ + samenessContribution(WEIGHT_DNSADDRESSES, dnsAddresses, o.dnsAddresses)
+ + samenessContribution(WEIGHT_MTU, mtu, o.mtu);
+ // The minimum is 0, the max is TOTAL_WEIGHT and should be represented by 1.0, and
+ // TOTAL_WEIGHT_CUTOFF should represent 0.5, but there is no requirement that
+ // TOTAL_WEIGHT_CUTOFF would be half of TOTAL_WEIGHT (indeed, it should not be).
+ // So scale scores under the cutoff between 0 and 0.5, and the scores over the cutoff
+ // between 0.5 and 1.0.
+ if (samenessScore < TOTAL_WEIGHT_CUTOFF) {
+ return samenessScore / (TOTAL_WEIGHT_CUTOFF * 2);
+ } else {
+ return (samenessScore - TOTAL_WEIGHT_CUTOFF) / (TOTAL_WEIGHT - TOTAL_WEIGHT_CUTOFF) / 2
+ + 0.5f;
+ }
+ }
+
+ /** @hide */
+ public static class Builder {
+ @Nullable
+ private Inet4Address mAssignedAddress;
+ @Nullable
+ private Long mAssignedAddressExpiry;
+ @Nullable
+ private String mGroupHint;
+ @Nullable
+ private List<InetAddress> mDnsAddresses;
+ @Nullable
+ private Integer mMtu;
+
+ /**
+ * Set the assigned address.
+ * @param assignedV4Address The assigned address.
+ * @return This builder.
+ */
+ public Builder setAssignedV4Address(@Nullable final Inet4Address assignedV4Address) {
+ mAssignedAddress = assignedV4Address;
+ return this;
+ }
+
+ /**
+ * Set the lease expiry timestamp of assigned v4 address. Long.MAX_VALUE is used
+ * to represent "infinite lease".
+ *
+ * @param assignedV4AddressExpiry The lease expiry timestamp of assigned v4 address.
+ * @return This builder.
+ */
+ public Builder setAssignedV4AddressExpiry(
+ @Nullable final Long assignedV4AddressExpiry) {
+ if (null != assignedV4AddressExpiry && assignedV4AddressExpiry <= 0) {
+ throw new IllegalArgumentException("lease expiry can't be negative or zero");
+ }
+ mAssignedAddressExpiry = assignedV4AddressExpiry;
+ return this;
+ }
+
+ /**
+ * Set the group hint.
+ * @param groupHint The group hint.
+ * @return This builder.
+ */
+ public Builder setGroupHint(@Nullable final String groupHint) {
+ mGroupHint = groupHint;
+ return this;
+ }
+
+ /**
+ * Set the DNS addresses.
+ * @param dnsAddresses The DNS addresses.
+ * @return This builder.
+ */
+ public Builder setDnsAddresses(@Nullable final List<InetAddress> dnsAddresses) {
+ if (DBG && null != dnsAddresses) {
+ // Parceling code crashes if one of the addresses is null, therefore validate
+ // them when running in debug.
+ for (final InetAddress address : dnsAddresses) {
+ if (null == address) throw new IllegalArgumentException("Null DNS address");
+ }
+ }
+ this.mDnsAddresses = dnsAddresses;
+ return this;
+ }
+
+ /**
+ * Set the MTU.
+ * @param mtu The MTU.
+ * @return This builder.
+ */
+ public Builder setMtu(@Nullable final Integer mtu) {
+ if (null != mtu && mtu < 0) throw new IllegalArgumentException("MTU can't be negative");
+ mMtu = mtu;
+ return this;
+ }
+
+ /**
+ * Return the built NetworkAttributes object.
+ * @return The built NetworkAttributes object.
+ */
+ public NetworkAttributes build() {
+ return new NetworkAttributes(mAssignedAddress, mAssignedAddressExpiry,
+ mGroupHint, mDnsAddresses, mMtu);
+ }
+ }
+
+ /** @hide */
+ public boolean isEmpty() {
+ return (null == assignedV4Address) && (null == assignedV4AddressExpiry)
+ && (null == groupHint) && (null == dnsAddresses) && (null == mtu);
+ }
+
+ @Override
+ public boolean equals(@Nullable final Object o) {
+ if (!(o instanceof NetworkAttributes)) return false;
+ final NetworkAttributes other = (NetworkAttributes) o;
+ return Objects.equals(assignedV4Address, other.assignedV4Address)
+ && Objects.equals(assignedV4AddressExpiry, other.assignedV4AddressExpiry)
+ && Objects.equals(groupHint, other.groupHint)
+ && Objects.equals(dnsAddresses, other.dnsAddresses)
+ && Objects.equals(mtu, other.mtu);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(assignedV4Address, assignedV4AddressExpiry,
+ groupHint, dnsAddresses, mtu);
+ }
+
+ /** Pretty print */
+ @Override
+ public String toString() {
+ final StringJoiner resultJoiner = new StringJoiner(" ", "{", "}");
+ final ArrayList<String> nullFields = new ArrayList<>();
+
+ if (null != assignedV4Address) {
+ resultJoiner.add("assignedV4Addr :");
+ resultJoiner.add(assignedV4Address.toString());
+ } else {
+ nullFields.add("assignedV4Addr");
+ }
+
+ if (null != assignedV4AddressExpiry) {
+ resultJoiner.add("assignedV4AddressExpiry :");
+ resultJoiner.add(assignedV4AddressExpiry.toString());
+ } else {
+ nullFields.add("assignedV4AddressExpiry");
+ }
+
+ if (null != groupHint) {
+ resultJoiner.add("groupHint :");
+ resultJoiner.add(groupHint);
+ } else {
+ nullFields.add("groupHint");
+ }
+
+ if (null != dnsAddresses) {
+ resultJoiner.add("dnsAddr : [");
+ for (final InetAddress addr : dnsAddresses) {
+ resultJoiner.add(addr.getHostAddress());
+ }
+ resultJoiner.add("]");
+ } else {
+ nullFields.add("dnsAddr");
+ }
+
+ if (null != mtu) {
+ resultJoiner.add("mtu :");
+ resultJoiner.add(mtu.toString());
+ } else {
+ nullFields.add("mtu");
+ }
+
+ if (!nullFields.isEmpty()) {
+ resultJoiner.add("; Null fields : [");
+ for (final String field : nullFields) {
+ resultJoiner.add(field);
+ }
+ resultJoiner.add("]");
+ }
+
+ return resultJoiner.toString();
+ }
+}
diff --git a/android/net/ipmemorystore/OnBlobRetrievedListener.java b/android/net/ipmemorystore/OnBlobRetrievedListener.java
new file mode 100644
index 0000000..a17483a
--- /dev/null
+++ b/android/net/ipmemorystore/OnBlobRetrievedListener.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+import android.annotation.NonNull;
+
+/**
+ * A listener for the IpMemoryStore to return a blob.
+ * @hide
+ */
+public interface OnBlobRetrievedListener {
+ /**
+ * The memory store has come up with the answer to a query that was sent.
+ */
+ void onBlobRetrieved(Status status, String l2Key, String name, Blob blob);
+
+ /** Converts this OnBlobRetrievedListener to a parcelable object */
+ @NonNull
+ static IOnBlobRetrievedListener toAIDL(@NonNull final OnBlobRetrievedListener listener) {
+ return new IOnBlobRetrievedListener.Stub() {
+ @Override
+ public void onBlobRetrieved(final StatusParcelable statusParcelable, final String l2Key,
+ final String name, final Blob blob) {
+ // NonNull, but still don't crash the system server if null
+ if (null != listener) {
+ listener.onBlobRetrieved(new Status(statusParcelable), l2Key, name, blob);
+ }
+ }
+
+ @Override
+ public int getInterfaceVersion() {
+ return this.VERSION;
+ }
+ };
+ }
+}
diff --git a/android/net/ipmemorystore/OnL2KeyResponseListener.java b/android/net/ipmemorystore/OnL2KeyResponseListener.java
new file mode 100644
index 0000000..e608aec
--- /dev/null
+++ b/android/net/ipmemorystore/OnL2KeyResponseListener.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+import android.annotation.NonNull;
+
+/**
+ * A listener for the IpMemoryStore to return a L2 key.
+ * @hide
+ */
+public interface OnL2KeyResponseListener {
+ /**
+ * The operation has completed with the specified status.
+ */
+ void onL2KeyResponse(Status status, String l2Key);
+
+ /** Converts this OnL2KeyResponseListener to a parcelable object */
+ @NonNull
+ static IOnL2KeyResponseListener toAIDL(@NonNull final OnL2KeyResponseListener listener) {
+ return new IOnL2KeyResponseListener.Stub() {
+ @Override
+ public void onL2KeyResponse(final StatusParcelable statusParcelable,
+ final String l2Key) {
+ // NonNull, but still don't crash the system server if null
+ if (null != listener) {
+ listener.onL2KeyResponse(new Status(statusParcelable), l2Key);
+ }
+ }
+
+ @Override
+ public int getInterfaceVersion() {
+ return this.VERSION;
+ }
+ };
+ }
+}
diff --git a/android/net/ipmemorystore/OnNetworkAttributesRetrievedListener.java b/android/net/ipmemorystore/OnNetworkAttributesRetrievedListener.java
new file mode 100644
index 0000000..395ad98
--- /dev/null
+++ b/android/net/ipmemorystore/OnNetworkAttributesRetrievedListener.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+import android.annotation.NonNull;
+
+/**
+ * A listener for the IpMemoryStore to return network attributes.
+ * @hide
+ */
+public interface OnNetworkAttributesRetrievedListener {
+ /**
+ * The memory store has come up with the answer to a query that was sent.
+ */
+ void onNetworkAttributesRetrieved(Status status, String l2Key, NetworkAttributes attributes);
+
+ /** Converts this OnNetworkAttributesRetrievedListener to a parcelable object */
+ @NonNull
+ static IOnNetworkAttributesRetrievedListener toAIDL(
+ @NonNull final OnNetworkAttributesRetrievedListener listener) {
+ return new IOnNetworkAttributesRetrievedListener.Stub() {
+ @Override
+ public void onNetworkAttributesRetrieved(final StatusParcelable statusParcelable,
+ final String l2Key,
+ final NetworkAttributesParcelable networkAttributesParcelable) {
+ // NonNull, but still don't crash the system server if null
+ if (null != listener) {
+ listener.onNetworkAttributesRetrieved(
+ new Status(statusParcelable), l2Key, null == networkAttributesParcelable
+ ? null : new NetworkAttributes(networkAttributesParcelable));
+ }
+ }
+
+ @Override
+ public int getInterfaceVersion() {
+ return this.VERSION;
+ }
+ };
+ }
+}
diff --git a/android/net/ipmemorystore/OnSameL3NetworkResponseListener.java b/android/net/ipmemorystore/OnSameL3NetworkResponseListener.java
new file mode 100644
index 0000000..67f8da8
--- /dev/null
+++ b/android/net/ipmemorystore/OnSameL3NetworkResponseListener.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+import android.annotation.NonNull;
+
+/**
+ * A listener for the IpMemoryStore to return a response about network sameness.
+ * @hide
+ */
+public interface OnSameL3NetworkResponseListener {
+ /**
+ * The memory store has come up with the answer to a query that was sent.
+ */
+ void onSameL3NetworkResponse(Status status, SameL3NetworkResponse response);
+
+ /** Converts this OnSameL3NetworkResponseListener to a parcelable object */
+ @NonNull
+ static IOnSameL3NetworkResponseListener toAIDL(
+ @NonNull final OnSameL3NetworkResponseListener listener) {
+ return new IOnSameL3NetworkResponseListener.Stub() {
+ @Override
+ public void onSameL3NetworkResponse(final StatusParcelable statusParcelable,
+ final SameL3NetworkResponseParcelable sameL3NetworkResponseParcelable) {
+ // NonNull, but still don't crash the system server if null
+ if (null != listener) {
+ listener.onSameL3NetworkResponse(
+ new Status(statusParcelable),
+ new SameL3NetworkResponse(sameL3NetworkResponseParcelable));
+ }
+ }
+
+ @Override
+ public int getInterfaceVersion() {
+ return this.VERSION;
+ }
+ };
+ }
+}
diff --git a/android/net/ipmemorystore/OnStatusListener.java b/android/net/ipmemorystore/OnStatusListener.java
new file mode 100644
index 0000000..4262efd
--- /dev/null
+++ b/android/net/ipmemorystore/OnStatusListener.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * A listener for the IpMemoryStore to return a status to a client.
+ * @hide
+ */
+public interface OnStatusListener {
+ /**
+ * The operation has completed with the specified status.
+ */
+ void onComplete(Status status);
+
+ /** Converts this OnStatusListener to a parcelable object */
+ @NonNull
+ static IOnStatusListener toAIDL(@Nullable final OnStatusListener listener) {
+ return new IOnStatusListener.Stub() {
+ @Override
+ public void onComplete(final StatusParcelable statusParcelable) {
+ if (null != listener) {
+ listener.onComplete(new Status(statusParcelable));
+ }
+ }
+
+ @Override
+ public int getInterfaceVersion() {
+ return this.VERSION;
+ }
+ };
+ }
+}
diff --git a/android/net/ipmemorystore/SameL3NetworkResponse.java b/android/net/ipmemorystore/SameL3NetworkResponse.java
new file mode 100644
index 0000000..291aca8
--- /dev/null
+++ b/android/net/ipmemorystore/SameL3NetworkResponse.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * An object representing the answer to a query whether two given L2 networks represent the
+ * same L3 network. Parcels as a SameL3NetworkResponseParceled object.
+ * @hide
+ */
+public class SameL3NetworkResponse {
+ @IntDef(prefix = "NETWORK_",
+ value = {NETWORK_SAME, NETWORK_DIFFERENT, NETWORK_NEVER_CONNECTED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface NetworkSameness {}
+
+ /**
+ * Both L2 networks represent the same L3 network.
+ */
+ public static final int NETWORK_SAME = 1;
+
+ /**
+ * The two L2 networks represent a different L3 network.
+ */
+ public static final int NETWORK_DIFFERENT = 2;
+
+ /**
+ * The device has never connected to at least one of these two L2 networks, or data
+ * has been wiped. Therefore the device has never seen the L3 network behind at least
+ * one of these two L2 networks, and can't evaluate whether it's the same as the other.
+ */
+ public static final int NETWORK_NEVER_CONNECTED = 3;
+
+ /**
+ * The first L2 key specified in the query.
+ */
+ @NonNull
+ public final String l2Key1;
+
+ /**
+ * The second L2 key specified in the query.
+ */
+ @NonNull
+ public final String l2Key2;
+
+ /**
+ * A confidence value indicating whether the two L2 networks represent the same L3 network.
+ *
+ * If both L2 networks were known, this value will be between 0.0 and 1.0, with 0.0
+ * representing complete confidence that the given L2 networks represent a different
+ * L3 network, and 1.0 representing complete confidence that the given L2 networks
+ * represent the same L3 network.
+ * If at least one of the L2 networks was not known, this value will be outside of the
+ * 0.0~1.0 range.
+ *
+ * Most apps should not be interested in this, and are encouraged to use the collapsing
+ * {@link #getNetworkSameness()} function below.
+ */
+ public final float confidence;
+
+ /**
+ * @return whether the two L2 networks represent the same L3 network. Either
+ * {@code NETWORK_SAME}, {@code NETWORK_DIFFERENT} or {@code NETWORK_NEVER_CONNECTED}.
+ */
+ @NetworkSameness
+ public final int getNetworkSameness() {
+ if (confidence > 1.0 || confidence < 0.0) return NETWORK_NEVER_CONNECTED;
+ return confidence > 0.5 ? NETWORK_SAME : NETWORK_DIFFERENT;
+ }
+
+ /** @hide */
+ public SameL3NetworkResponse(@NonNull final String l2Key1, @NonNull final String l2Key2,
+ final float confidence) {
+ this.l2Key1 = l2Key1;
+ this.l2Key2 = l2Key2;
+ this.confidence = confidence;
+ }
+
+ /** Builds a SameL3NetworkResponse from a parcelable object */
+ @VisibleForTesting
+ public SameL3NetworkResponse(@NonNull final SameL3NetworkResponseParcelable parceled) {
+ this(parceled.l2Key1, parceled.l2Key2, parceled.confidence);
+ }
+
+ /** Converts this SameL3NetworkResponse to a parcelable object */
+ @NonNull
+ public SameL3NetworkResponseParcelable toParcelable() {
+ final SameL3NetworkResponseParcelable parcelable = new SameL3NetworkResponseParcelable();
+ parcelable.l2Key1 = l2Key1;
+ parcelable.l2Key2 = l2Key2;
+ parcelable.confidence = confidence;
+ return parcelable;
+ }
+
+ // Note key1 and key2 have to match each other for this to return true. If
+ // key1 matches o.key2 and the other way around this returns false.
+ @Override
+ public boolean equals(@Nullable final Object o) {
+ if (!(o instanceof SameL3NetworkResponse)) return false;
+ final SameL3NetworkResponse other = (SameL3NetworkResponse) o;
+ return l2Key1.equals(other.l2Key1) && l2Key2.equals(other.l2Key2)
+ && confidence == other.confidence;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(l2Key1, l2Key2, confidence);
+ }
+
+ @Override
+ /** Pretty print */
+ public String toString() {
+ switch (getNetworkSameness()) {
+ case NETWORK_SAME:
+ return "\"" + l2Key1 + "\" same L3 network as \"" + l2Key2 + "\"";
+ case NETWORK_DIFFERENT:
+ return "\"" + l2Key1 + "\" different L3 network from \"" + l2Key2 + "\"";
+ case NETWORK_NEVER_CONNECTED:
+ return "\"" + l2Key1 + "\" can't be tested against \"" + l2Key2 + "\"";
+ default:
+ return "Buggy sameness value ? \"" + l2Key1 + "\", \"" + l2Key2 + "\"";
+ }
+ }
+}
diff --git a/android/net/ipmemorystore/Status.java b/android/net/ipmemorystore/Status.java
new file mode 100644
index 0000000..13242c0
--- /dev/null
+++ b/android/net/ipmemorystore/Status.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * A parcelable status representing the result of an operation.
+ * Parcels as StatusParceled.
+ * @hide
+ */
+public class Status {
+ public static final int SUCCESS = 0;
+
+ public static final int ERROR_GENERIC = -1;
+ public static final int ERROR_ILLEGAL_ARGUMENT = -2;
+ public static final int ERROR_DATABASE_CANNOT_BE_OPENED = -3;
+ public static final int ERROR_STORAGE = -4;
+ public static final int ERROR_UNKNOWN = -5;
+
+ public final int resultCode;
+
+ public Status(final int resultCode) {
+ this.resultCode = resultCode;
+ }
+
+ @VisibleForTesting
+ public Status(@NonNull final StatusParcelable parcelable) {
+ this(parcelable.resultCode);
+ }
+
+ /** Converts this Status to a parcelable object */
+ @NonNull
+ public StatusParcelable toParcelable() {
+ final StatusParcelable parcelable = new StatusParcelable();
+ parcelable.resultCode = resultCode;
+ return parcelable;
+ }
+
+ public boolean isSuccess() {
+ return SUCCESS == resultCode;
+ }
+
+ /** Pretty print */
+ @Override
+ public String toString() {
+ switch (resultCode) {
+ case SUCCESS: return "SUCCESS";
+ case ERROR_GENERIC: return "GENERIC ERROR";
+ case ERROR_ILLEGAL_ARGUMENT: return "ILLEGAL ARGUMENT";
+ case ERROR_DATABASE_CANNOT_BE_OPENED: return "DATABASE CANNOT BE OPENED";
+ // "DB storage error" is not very helpful but SQLite does not provide specific error
+ // codes upon store failure. Thus this indicates SQLite returned some error upon store
+ case ERROR_STORAGE: return "DATABASE STORAGE ERROR";
+ default: return "Unknown value ?!";
+ }
+ }
+}
diff --git a/android/net/lowpan/InterfaceDisabledException.java b/android/net/lowpan/InterfaceDisabledException.java
new file mode 100644
index 0000000..e917d45
--- /dev/null
+++ b/android/net/lowpan/InterfaceDisabledException.java
@@ -0,0 +1,41 @@
+/*
+ * 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 android.net.lowpan;
+
+/**
+ * Exception indicating this operation requires the interface to be enabled.
+ *
+ * @see LowpanInterface
+ * @hide
+ */
+// @SystemApi
+public class InterfaceDisabledException extends LowpanException {
+
+ public InterfaceDisabledException() {}
+
+ public InterfaceDisabledException(String message) {
+ super(message);
+ }
+
+ public InterfaceDisabledException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ protected InterfaceDisabledException(Exception cause) {
+ super(cause);
+ }
+}
diff --git a/android/net/lowpan/JoinFailedAtAuthException.java b/android/net/lowpan/JoinFailedAtAuthException.java
new file mode 100644
index 0000000..7aceb71
--- /dev/null
+++ b/android/net/lowpan/JoinFailedAtAuthException.java
@@ -0,0 +1,41 @@
+/*
+ * 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 android.net.lowpan;
+
+/**
+ * Exception indicating the join operation was unable to find the given network.
+ *
+ * @see LowpanInterface
+ * @hide
+ */
+// @SystemApi
+public class JoinFailedAtAuthException extends JoinFailedException {
+
+ public JoinFailedAtAuthException() {}
+
+ public JoinFailedAtAuthException(String message) {
+ super(message);
+ }
+
+ public JoinFailedAtAuthException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public JoinFailedAtAuthException(Exception cause) {
+ super(cause);
+ }
+}
diff --git a/android/net/lowpan/JoinFailedAtScanException.java b/android/net/lowpan/JoinFailedAtScanException.java
new file mode 100644
index 0000000..a4346f9
--- /dev/null
+++ b/android/net/lowpan/JoinFailedAtScanException.java
@@ -0,0 +1,41 @@
+/*
+ * 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 android.net.lowpan;
+
+/**
+ * Exception indicating the join operation was unable to find the given network.
+ *
+ * @see LowpanInterface
+ * @hide
+ */
+// @SystemApi
+public class JoinFailedAtScanException extends JoinFailedException {
+
+ public JoinFailedAtScanException() {}
+
+ public JoinFailedAtScanException(String message) {
+ super(message);
+ }
+
+ public JoinFailedAtScanException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public JoinFailedAtScanException(Exception cause) {
+ super(cause);
+ }
+}
diff --git a/android/net/lowpan/JoinFailedException.java b/android/net/lowpan/JoinFailedException.java
new file mode 100644
index 0000000..e51d382
--- /dev/null
+++ b/android/net/lowpan/JoinFailedException.java
@@ -0,0 +1,41 @@
+/*
+ * 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 android.net.lowpan;
+
+/**
+ * Exception indicating the join operation has failed.
+ *
+ * @see LowpanInterface
+ * @hide
+ */
+// @SystemApi
+public class JoinFailedException extends LowpanException {
+
+ public JoinFailedException() {}
+
+ public JoinFailedException(String message) {
+ super(message);
+ }
+
+ public JoinFailedException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ protected JoinFailedException(Exception cause) {
+ super(cause);
+ }
+}
diff --git a/android/net/lowpan/LowpanBeaconInfo.java b/android/net/lowpan/LowpanBeaconInfo.java
new file mode 100644
index 0000000..5d4a3a0
--- /dev/null
+++ b/android/net/lowpan/LowpanBeaconInfo.java
@@ -0,0 +1,234 @@
+/*
+ * 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 android.net.lowpan;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import com.android.internal.util.HexDump;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Objects;
+import java.util.TreeSet;
+
+/**
+ * Describes a LoWPAN Beacon
+ *
+ * @hide
+ */
+// @SystemApi
+public class LowpanBeaconInfo implements Parcelable {
+ public static final int UNKNOWN_RSSI = Integer.MAX_VALUE;
+ public static final int UNKNOWN_LQI = 0;
+
+ private LowpanIdentity mIdentity;
+ private int mRssi = UNKNOWN_RSSI;
+ private int mLqi = UNKNOWN_LQI;
+ private byte[] mBeaconAddress = null;
+ private final TreeSet<Integer> mFlags = new TreeSet<>();
+
+ public static final int FLAG_CAN_ASSIST = 1;
+
+ /** @hide */
+ public static class Builder {
+ final LowpanIdentity.Builder mIdentityBuilder = new LowpanIdentity.Builder();
+ final LowpanBeaconInfo mBeaconInfo = new LowpanBeaconInfo();
+
+ public Builder setLowpanIdentity(LowpanIdentity x) {
+ mIdentityBuilder.setLowpanIdentity(x);
+ return this;
+ }
+
+ public Builder setName(String x) {
+ mIdentityBuilder.setName(x);
+ return this;
+ }
+
+ public Builder setXpanid(byte x[]) {
+ mIdentityBuilder.setXpanid(x);
+ return this;
+ }
+
+ public Builder setPanid(int x) {
+ mIdentityBuilder.setPanid(x);
+ return this;
+ }
+
+ public Builder setChannel(int x) {
+ mIdentityBuilder.setChannel(x);
+ return this;
+ }
+
+ public Builder setType(String x) {
+ mIdentityBuilder.setType(x);
+ return this;
+ }
+
+ public Builder setRssi(int x) {
+ mBeaconInfo.mRssi = x;
+ return this;
+ }
+
+ public Builder setLqi(int x) {
+ mBeaconInfo.mLqi = x;
+ return this;
+ }
+
+ public Builder setBeaconAddress(byte x[]) {
+ mBeaconInfo.mBeaconAddress = (x != null ? x.clone() : null);
+ return this;
+ }
+
+ public Builder setFlag(int x) {
+ mBeaconInfo.mFlags.add(x);
+ return this;
+ }
+
+ public Builder setFlags(Collection<Integer> x) {
+ mBeaconInfo.mFlags.addAll(x);
+ return this;
+ }
+
+ public LowpanBeaconInfo build() {
+ mBeaconInfo.mIdentity = mIdentityBuilder.build();
+ if (mBeaconInfo.mBeaconAddress == null) {
+ mBeaconInfo.mBeaconAddress = new byte[0];
+ }
+ return mBeaconInfo;
+ }
+ }
+
+ private LowpanBeaconInfo() {}
+
+ public LowpanIdentity getLowpanIdentity() {
+ return mIdentity;
+ }
+
+ public int getRssi() {
+ return mRssi;
+ }
+
+ public int getLqi() {
+ return mLqi;
+ }
+
+ public byte[] getBeaconAddress() {
+ return mBeaconAddress.clone();
+ }
+
+ public Collection<Integer> getFlags() {
+ return (Collection<Integer>) mFlags.clone();
+ }
+
+ public boolean isFlagSet(int flag) {
+ return mFlags.contains(flag);
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append(mIdentity.toString());
+
+ if (mRssi != UNKNOWN_RSSI) {
+ sb.append(", RSSI:").append(mRssi).append("dBm");
+ }
+
+ if (mLqi != UNKNOWN_LQI) {
+ sb.append(", LQI:").append(mLqi);
+ }
+
+ if (mBeaconAddress.length > 0) {
+ sb.append(", BeaconAddress:").append(HexDump.toHexString(mBeaconAddress));
+ }
+
+ for (Integer flag : mFlags) {
+ switch (flag.intValue()) {
+ case FLAG_CAN_ASSIST:
+ sb.append(", CAN_ASSIST");
+ break;
+ default:
+ sb.append(", FLAG_").append(Integer.toHexString(flag));
+ break;
+ }
+ }
+
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mIdentity, mRssi, mLqi, Arrays.hashCode(mBeaconAddress), mFlags);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof LowpanBeaconInfo)) {
+ return false;
+ }
+ LowpanBeaconInfo rhs = (LowpanBeaconInfo) obj;
+ return mIdentity.equals(rhs.mIdentity)
+ && Arrays.equals(mBeaconAddress, rhs.mBeaconAddress)
+ && mRssi == rhs.mRssi
+ && mLqi == rhs.mLqi
+ && mFlags.equals(rhs.mFlags);
+ }
+
+ /** Implement the Parcelable interface. */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ mIdentity.writeToParcel(dest, flags);
+ dest.writeInt(mRssi);
+ dest.writeInt(mLqi);
+ dest.writeByteArray(mBeaconAddress);
+
+ dest.writeInt(mFlags.size());
+ for (Integer val : mFlags) {
+ dest.writeInt(val);
+ }
+ }
+
+ /** Implement the Parcelable interface. */
+ public static final @android.annotation.NonNull Creator<LowpanBeaconInfo> CREATOR =
+ new Creator<LowpanBeaconInfo>() {
+ public LowpanBeaconInfo createFromParcel(Parcel in) {
+ Builder builder = new Builder();
+
+ builder.setLowpanIdentity(LowpanIdentity.CREATOR.createFromParcel(in));
+
+ builder.setRssi(in.readInt());
+ builder.setLqi(in.readInt());
+
+ builder.setBeaconAddress(in.createByteArray());
+
+ for (int i = in.readInt(); i > 0; i--) {
+ builder.setFlag(in.readInt());
+ }
+
+ return builder.build();
+ }
+
+ public LowpanBeaconInfo[] newArray(int size) {
+ return new LowpanBeaconInfo[size];
+ }
+ };
+}
diff --git a/android/net/lowpan/LowpanChannelInfo.java b/android/net/lowpan/LowpanChannelInfo.java
new file mode 100644
index 0000000..12c98b6
--- /dev/null
+++ b/android/net/lowpan/LowpanChannelInfo.java
@@ -0,0 +1,216 @@
+/*
+ * 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 android.net.lowpan;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import java.util.Objects;
+
+/**
+ * Provides detailed information about a given channel.
+ *
+ * @hide
+ */
+// @SystemApi
+public class LowpanChannelInfo implements Parcelable {
+
+ public static final int UNKNOWN_POWER = Integer.MAX_VALUE;
+ public static final float UNKNOWN_FREQUENCY = 0.0f;
+ public static final float UNKNOWN_BANDWIDTH = 0.0f;
+
+ private int mIndex = 0;
+ private String mName = null;
+ private float mSpectrumCenterFrequency = UNKNOWN_FREQUENCY;
+ private float mSpectrumBandwidth = UNKNOWN_BANDWIDTH;
+ private int mMaxTransmitPower = UNKNOWN_POWER;
+ private boolean mIsMaskedByRegulatoryDomain = false;
+
+ /** @hide */
+ public static LowpanChannelInfo getChannelInfoForIeee802154Page0(int index) {
+ LowpanChannelInfo info = new LowpanChannelInfo();
+
+ if (index < 0) {
+ info = null;
+
+ } else if (index == 0) {
+ info.mSpectrumCenterFrequency = 868300000.0f;
+ info.mSpectrumBandwidth = 600000.0f;
+
+ } else if (index < 11) {
+ info.mSpectrumCenterFrequency = 906000000.0f - (2000000.0f * 1) + 2000000.0f * (index);
+ info.mSpectrumBandwidth = 0; // Unknown
+
+ } else if (index < 26) {
+ info.mSpectrumCenterFrequency =
+ 2405000000.0f - (5000000.0f * 11) + 5000000.0f * (index);
+ info.mSpectrumBandwidth = 2000000.0f;
+
+ } else {
+ info = null;
+ }
+
+ info.mName = Integer.toString(index);
+
+ return info;
+ }
+
+ private LowpanChannelInfo() {}
+
+ private LowpanChannelInfo(int index, String name, float cf, float bw) {
+ mIndex = index;
+ mName = name;
+ mSpectrumCenterFrequency = cf;
+ mSpectrumBandwidth = bw;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public int getIndex() {
+ return mIndex;
+ }
+
+ public int getMaxTransmitPower() {
+ return mMaxTransmitPower;
+ }
+
+ public boolean isMaskedByRegulatoryDomain() {
+ return mIsMaskedByRegulatoryDomain;
+ }
+
+ public float getSpectrumCenterFrequency() {
+ return mSpectrumCenterFrequency;
+ }
+
+ public float getSpectrumBandwidth() {
+ return mSpectrumBandwidth;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append("Channel ").append(mIndex);
+
+ if (mName != null && !mName.equals(Integer.toString(mIndex))) {
+ sb.append(" (").append(mName).append(")");
+ }
+
+ if (mSpectrumCenterFrequency > 0.0f) {
+ if (mSpectrumCenterFrequency > 1000000000.0f) {
+ sb.append(", SpectrumCenterFrequency: ")
+ .append(mSpectrumCenterFrequency / 1000000000.0f)
+ .append("GHz");
+ } else if (mSpectrumCenterFrequency > 1000000.0f) {
+ sb.append(", SpectrumCenterFrequency: ")
+ .append(mSpectrumCenterFrequency / 1000000.0f)
+ .append("MHz");
+ } else {
+ sb.append(", SpectrumCenterFrequency: ")
+ .append(mSpectrumCenterFrequency / 1000.0f)
+ .append("kHz");
+ }
+ }
+
+ if (mSpectrumBandwidth > 0.0f) {
+ if (mSpectrumBandwidth > 1000000000.0f) {
+ sb.append(", SpectrumBandwidth: ")
+ .append(mSpectrumBandwidth / 1000000000.0f)
+ .append("GHz");
+ } else if (mSpectrumBandwidth > 1000000.0f) {
+ sb.append(", SpectrumBandwidth: ")
+ .append(mSpectrumBandwidth / 1000000.0f)
+ .append("MHz");
+ } else {
+ sb.append(", SpectrumBandwidth: ")
+ .append(mSpectrumBandwidth / 1000.0f)
+ .append("kHz");
+ }
+ }
+
+ if (mMaxTransmitPower != UNKNOWN_POWER) {
+ sb.append(", MaxTransmitPower: ").append(mMaxTransmitPower).append("dBm");
+ }
+
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof LowpanChannelInfo)) {
+ return false;
+ }
+ LowpanChannelInfo rhs = (LowpanChannelInfo) obj;
+ return Objects.equals(mName, rhs.mName)
+ && mIndex == rhs.mIndex
+ && mIsMaskedByRegulatoryDomain == rhs.mIsMaskedByRegulatoryDomain
+ && mSpectrumCenterFrequency == rhs.mSpectrumCenterFrequency
+ && mSpectrumBandwidth == rhs.mSpectrumBandwidth
+ && mMaxTransmitPower == rhs.mMaxTransmitPower;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mName,
+ mIndex,
+ mIsMaskedByRegulatoryDomain,
+ mSpectrumCenterFrequency,
+ mSpectrumBandwidth,
+ mMaxTransmitPower);
+ }
+
+ /** Implement the Parcelable interface. */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mIndex);
+ dest.writeString(mName);
+ dest.writeFloat(mSpectrumCenterFrequency);
+ dest.writeFloat(mSpectrumBandwidth);
+ dest.writeInt(mMaxTransmitPower);
+ dest.writeBoolean(mIsMaskedByRegulatoryDomain);
+ }
+
+ /** Implement the Parcelable interface. */
+ public static final @android.annotation.NonNull Creator<LowpanChannelInfo> CREATOR =
+ new Creator<LowpanChannelInfo>() {
+
+ public LowpanChannelInfo createFromParcel(Parcel in) {
+ LowpanChannelInfo info = new LowpanChannelInfo();
+
+ info.mIndex = in.readInt();
+ info.mName = in.readString();
+ info.mSpectrumCenterFrequency = in.readFloat();
+ info.mSpectrumBandwidth = in.readFloat();
+ info.mMaxTransmitPower = in.readInt();
+ info.mIsMaskedByRegulatoryDomain = in.readBoolean();
+
+ return info;
+ }
+
+ public LowpanChannelInfo[] newArray(int size) {
+ return new LowpanChannelInfo[size];
+ }
+ };
+}
diff --git a/android/net/lowpan/LowpanCommissioningSession.java b/android/net/lowpan/LowpanCommissioningSession.java
new file mode 100644
index 0000000..8f75e8d
--- /dev/null
+++ b/android/net/lowpan/LowpanCommissioningSession.java
@@ -0,0 +1,223 @@
+/*
+ * 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 android.net.lowpan;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.IpPrefix;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+
+/**
+ * Commissioning Session.
+ *
+ * <p>This class enables a device to learn the credential needed to join a network using a technique
+ * called "in-band commissioning".
+ *
+ * @hide
+ */
+// @SystemApi
+public class LowpanCommissioningSession {
+
+ private final ILowpanInterface mBinder;
+ private final LowpanBeaconInfo mBeaconInfo;
+ private final ILowpanInterfaceListener mInternalCallback = new InternalCallback();
+ private final Looper mLooper;
+ private Handler mHandler;
+ private Callback mCallback = null;
+ private volatile boolean mIsClosed = false;
+
+ /**
+ * Callback base class for {@link LowpanCommissioningSession}
+ *
+ * @hide
+ */
+ // @SystemApi
+ public abstract static class Callback {
+ public void onReceiveFromCommissioner(@NonNull byte[] packet) {};
+
+ public void onClosed() {};
+ }
+
+ private class InternalCallback extends ILowpanInterfaceListener.Stub {
+ @Override
+ public void onStateChanged(String value) {
+ if (!mIsClosed) {
+ switch (value) {
+ case ILowpanInterface.STATE_OFFLINE:
+ case ILowpanInterface.STATE_FAULT:
+ synchronized (LowpanCommissioningSession.this) {
+ lockedCleanup();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onReceiveFromCommissioner(byte[] packet) {
+ mHandler.post(
+ () -> {
+ synchronized (LowpanCommissioningSession.this) {
+ if (!mIsClosed && (mCallback != null)) {
+ mCallback.onReceiveFromCommissioner(packet);
+ }
+ }
+ });
+ }
+
+ // We ignore all other callbacks.
+ @Override
+ public void onEnabledChanged(boolean value) {}
+
+ @Override
+ public void onConnectedChanged(boolean value) {}
+
+ @Override
+ public void onUpChanged(boolean value) {}
+
+ @Override
+ public void onRoleChanged(String value) {}
+
+ @Override
+ public void onLowpanIdentityChanged(LowpanIdentity value) {}
+
+ @Override
+ public void onLinkNetworkAdded(IpPrefix value) {}
+
+ @Override
+ public void onLinkNetworkRemoved(IpPrefix value) {}
+
+ @Override
+ public void onLinkAddressAdded(String value) {}
+
+ @Override
+ public void onLinkAddressRemoved(String value) {}
+ }
+
+ LowpanCommissioningSession(
+ ILowpanInterface binder, LowpanBeaconInfo beaconInfo, Looper looper) {
+ mBinder = binder;
+ mBeaconInfo = beaconInfo;
+ mLooper = looper;
+
+ if (mLooper != null) {
+ mHandler = new Handler(mLooper);
+ } else {
+ mHandler = new Handler();
+ }
+
+ try {
+ mBinder.addListener(mInternalCallback);
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+ }
+ }
+
+ private void lockedCleanup() {
+ // Note: this method is only called from synchronized contexts.
+
+ if (!mIsClosed) {
+ try {
+ mBinder.removeListener(mInternalCallback);
+
+ } catch (DeadObjectException x) {
+ /* We don't care if we receive a DOE at this point.
+ * DOE is as good as success as far as we are concerned.
+ */
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+ }
+
+ if (mCallback != null) {
+ mHandler.post(() -> mCallback.onClosed());
+ }
+ }
+
+ mCallback = null;
+ mIsClosed = true;
+ }
+
+ /** TODO: doc */
+ @NonNull
+ public LowpanBeaconInfo getBeaconInfo() {
+ return mBeaconInfo;
+ }
+
+ /** TODO: doc */
+ public void sendToCommissioner(@NonNull byte[] packet) {
+ if (!mIsClosed) {
+ try {
+ mBinder.sendToCommissioner(packet);
+
+ } catch (DeadObjectException x) {
+ /* This method is a best-effort delivery.
+ * We don't care if we receive a DOE at this point.
+ */
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+ }
+ }
+ }
+
+ /** TODO: doc */
+ public synchronized void setCallback(@Nullable Callback cb, @Nullable Handler handler) {
+ if (!mIsClosed) {
+ /* This class can be created with or without a default looper.
+ * Also, this method can be called with or without a specific
+ * handler. If a handler is specified, it is to always be used.
+ * Otherwise, if there was a Looper specified when this object
+ * was created, we create a new handle based on that looper.
+ * Otherwise we just create a default handler object. Since we
+ * don't really know how the previous handler was created, we
+ * end up always replacing it here. This isn't a huge problem
+ * because this method should be called infrequently.
+ */
+ if (handler != null) {
+ mHandler = handler;
+ } else if (mLooper != null) {
+ mHandler = new Handler(mLooper);
+ } else {
+ mHandler = new Handler();
+ }
+ mCallback = cb;
+ }
+ }
+
+ /** TODO: doc */
+ public synchronized void close() {
+ if (!mIsClosed) {
+ try {
+ mBinder.closeCommissioningSession();
+
+ lockedCleanup();
+
+ } catch (DeadObjectException x) {
+ /* We don't care if we receive a DOE at this point.
+ * DOE is as good as success as far as we are concerned.
+ */
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+ }
+ }
+ }
+}
diff --git a/android/net/lowpan/LowpanCredential.java b/android/net/lowpan/LowpanCredential.java
new file mode 100644
index 0000000..dcbb831
--- /dev/null
+++ b/android/net/lowpan/LowpanCredential.java
@@ -0,0 +1,172 @@
+/*
+ * 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 android.net.lowpan;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import com.android.internal.util.HexDump;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Describes a credential for a LoWPAN network.
+ *
+ * @hide
+ */
+// @SystemApi
+public class LowpanCredential implements Parcelable {
+
+ public static final int UNSPECIFIED_KEY_INDEX = 0;
+
+ private byte[] mMasterKey = null;
+ private int mMasterKeyIndex = UNSPECIFIED_KEY_INDEX;
+
+ LowpanCredential() {}
+
+ private LowpanCredential(byte[] masterKey, int keyIndex) {
+ setMasterKey(masterKey, keyIndex);
+ }
+
+ private LowpanCredential(byte[] masterKey) {
+ setMasterKey(masterKey);
+ }
+
+ public static LowpanCredential createMasterKey(byte[] masterKey) {
+ return new LowpanCredential(masterKey);
+ }
+
+ public static LowpanCredential createMasterKey(byte[] masterKey, int keyIndex) {
+ return new LowpanCredential(masterKey, keyIndex);
+ }
+
+ void setMasterKey(byte[] masterKey) {
+ if (masterKey != null) {
+ masterKey = masterKey.clone();
+ }
+ mMasterKey = masterKey;
+ }
+
+ void setMasterKeyIndex(int keyIndex) {
+ mMasterKeyIndex = keyIndex;
+ }
+
+ void setMasterKey(byte[] masterKey, int keyIndex) {
+ setMasterKey(masterKey);
+ setMasterKeyIndex(keyIndex);
+ }
+
+ public byte[] getMasterKey() {
+ if (mMasterKey != null) {
+ return mMasterKey.clone();
+ }
+ return null;
+ }
+
+ public int getMasterKeyIndex() {
+ return mMasterKeyIndex;
+ }
+
+ public boolean isMasterKey() {
+ return mMasterKey != null;
+ }
+
+ public String toSensitiveString() {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append("<LowpanCredential");
+
+ if (isMasterKey()) {
+ sb.append(" MasterKey:").append(HexDump.toHexString(mMasterKey));
+ if (mMasterKeyIndex != UNSPECIFIED_KEY_INDEX) {
+ sb.append(", Index:").append(mMasterKeyIndex);
+ }
+ } else {
+ sb.append(" empty");
+ }
+
+ sb.append(">");
+
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append("<LowpanCredential");
+
+ if (isMasterKey()) {
+ // We don't print out the contents of the key here,
+ // we only do that in toSensitiveString.
+ sb.append(" MasterKey");
+ if (mMasterKeyIndex != UNSPECIFIED_KEY_INDEX) {
+ sb.append(", Index:").append(mMasterKeyIndex);
+ }
+ } else {
+ sb.append(" empty");
+ }
+
+ sb.append(">");
+
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof LowpanCredential)) {
+ return false;
+ }
+ LowpanCredential rhs = (LowpanCredential) obj;
+ return Arrays.equals(mMasterKey, rhs.mMasterKey) && mMasterKeyIndex == rhs.mMasterKeyIndex;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(Arrays.hashCode(mMasterKey), mMasterKeyIndex);
+ }
+
+ /** Implement the Parcelable interface. */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeByteArray(mMasterKey);
+ dest.writeInt(mMasterKeyIndex);
+ }
+
+ /** Implement the Parcelable interface. */
+ public static final @android.annotation.NonNull Creator<LowpanCredential> CREATOR =
+ new Creator<LowpanCredential>() {
+
+ public LowpanCredential createFromParcel(Parcel in) {
+ LowpanCredential credential = new LowpanCredential();
+
+ credential.mMasterKey = in.createByteArray();
+ credential.mMasterKeyIndex = in.readInt();
+
+ return credential;
+ }
+
+ public LowpanCredential[] newArray(int size) {
+ return new LowpanCredential[size];
+ }
+ };
+}
diff --git a/android/net/lowpan/LowpanEnergyScanResult.java b/android/net/lowpan/LowpanEnergyScanResult.java
new file mode 100644
index 0000000..da87752
--- /dev/null
+++ b/android/net/lowpan/LowpanEnergyScanResult.java
@@ -0,0 +1,53 @@
+/*
+ * 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 android.net.lowpan;
+
+/**
+ * Describes the result from one channel of an energy scan.
+ *
+ * @hide
+ */
+// @SystemApi
+public class LowpanEnergyScanResult {
+ public static final int UNKNOWN = Integer.MAX_VALUE;
+
+ private int mChannel = UNKNOWN;
+ private int mMaxRssi = UNKNOWN;
+
+ LowpanEnergyScanResult() {}
+
+ public int getChannel() {
+ return mChannel;
+ }
+
+ public int getMaxRssi() {
+ return mMaxRssi;
+ }
+
+ void setChannel(int x) {
+ mChannel = x;
+ }
+
+ void setMaxRssi(int x) {
+ mMaxRssi = x;
+ }
+
+ @Override
+ public String toString() {
+ return "LowpanEnergyScanResult(channel: " + mChannel + ", maxRssi:" + mMaxRssi + ")";
+ }
+}
diff --git a/android/net/lowpan/LowpanException.java b/android/net/lowpan/LowpanException.java
new file mode 100644
index 0000000..5dfce48
--- /dev/null
+++ b/android/net/lowpan/LowpanException.java
@@ -0,0 +1,92 @@
+/*
+ * 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 android.net.lowpan;
+
+import android.os.ServiceSpecificException;
+import android.util.AndroidException;
+
+/**
+ * <code>LowpanException</code> is thrown if an action to a LoWPAN interface could not be performed
+ * or a LoWPAN interface property could not be fetched or changed.
+ *
+ * @see LowpanInterface
+ * @hide
+ */
+// @SystemApi
+public class LowpanException extends AndroidException {
+ public LowpanException() {}
+
+ public LowpanException(String message) {
+ super(message);
+ }
+
+ public LowpanException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public LowpanException(Exception cause) {
+ super(cause);
+ }
+
+ /* This method returns LowpanException so that the caller
+ * can add "throw" before the invocation of this method.
+ * This might seem superfluous, but it is actually to
+ * help provide a hint to the java compiler that this
+ * function will not return.
+ */
+ static LowpanException rethrowFromServiceSpecificException(ServiceSpecificException e)
+ throws LowpanException {
+ switch (e.errorCode) {
+ case ILowpanInterface.ERROR_DISABLED:
+ throw new InterfaceDisabledException(e);
+
+ case ILowpanInterface.ERROR_WRONG_STATE:
+ throw new WrongStateException(e);
+
+ case ILowpanInterface.ERROR_CANCELED:
+ throw new OperationCanceledException(e);
+
+ case ILowpanInterface.ERROR_JOIN_FAILED_UNKNOWN:
+ throw new JoinFailedException(e);
+
+ case ILowpanInterface.ERROR_JOIN_FAILED_AT_SCAN:
+ throw new JoinFailedAtScanException(e);
+
+ case ILowpanInterface.ERROR_JOIN_FAILED_AT_AUTH:
+ throw new JoinFailedAtAuthException(e);
+
+ case ILowpanInterface.ERROR_FORM_FAILED_AT_SCAN:
+ throw new NetworkAlreadyExistsException(e);
+
+ case ILowpanInterface.ERROR_FEATURE_NOT_SUPPORTED:
+ throw new LowpanException(
+ e.getMessage() != null ? e.getMessage() : "Feature not supported", e);
+
+ case ILowpanInterface.ERROR_NCP_PROBLEM:
+ throw new LowpanRuntimeException(
+ e.getMessage() != null ? e.getMessage() : "NCP problem", e);
+
+ case ILowpanInterface.ERROR_INVALID_ARGUMENT:
+ throw new LowpanRuntimeException(
+ e.getMessage() != null ? e.getMessage() : "Invalid argument", e);
+
+ case ILowpanInterface.ERROR_UNSPECIFIED:
+ default:
+ throw new LowpanRuntimeException(e);
+ }
+ }
+}
diff --git a/android/net/lowpan/LowpanIdentity.java b/android/net/lowpan/LowpanIdentity.java
new file mode 100644
index 0000000..1997bc4
--- /dev/null
+++ b/android/net/lowpan/LowpanIdentity.java
@@ -0,0 +1,255 @@
+/*
+ * 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 android.net.lowpan;
+
+import android.annotation.NonNull;
+import android.icu.text.StringPrep;
+import android.icu.text.StringPrepParseException;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import com.android.internal.util.HexDump;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Describes an instance of a LoWPAN network.
+ *
+ * @hide
+ */
+// @SystemApi
+public class LowpanIdentity implements Parcelable {
+ private static final String TAG = LowpanIdentity.class.getSimpleName();
+
+ // Constants
+ public static final int UNSPECIFIED_CHANNEL = -1;
+ public static final int UNSPECIFIED_PANID = 0xFFFFFFFF;
+ // Builder
+
+ /** @hide */
+ // @SystemApi
+ public static class Builder {
+ private static final StringPrep stringPrep =
+ StringPrep.getInstance(StringPrep.RFC3920_RESOURCEPREP);
+
+ final LowpanIdentity mIdentity = new LowpanIdentity();
+
+ private static String escape(@NonNull byte[] bytes) {
+ StringBuffer sb = new StringBuffer();
+ for (byte b : bytes) {
+ if (b >= 32 && b <= 126) {
+ sb.append((char) b);
+ } else {
+ sb.append(String.format("\\0x%02x", b & 0xFF));
+ }
+ }
+ return sb.toString();
+ }
+
+ public Builder setLowpanIdentity(@NonNull LowpanIdentity x) {
+ Objects.requireNonNull(x);
+ setRawName(x.getRawName());
+ setXpanid(x.getXpanid());
+ setPanid(x.getPanid());
+ setChannel(x.getChannel());
+ setType(x.getType());
+ return this;
+ }
+
+ public Builder setName(@NonNull String name) {
+ Objects.requireNonNull(name);
+ try {
+ mIdentity.mName = stringPrep.prepare(name, StringPrep.DEFAULT);
+ mIdentity.mRawName = mIdentity.mName.getBytes(StandardCharsets.UTF_8);
+ mIdentity.mIsNameValid = true;
+ } catch (StringPrepParseException x) {
+ Log.w(TAG, x.toString());
+ setRawName(name.getBytes(StandardCharsets.UTF_8));
+ }
+ return this;
+ }
+
+ public Builder setRawName(@NonNull byte[] name) {
+ Objects.requireNonNull(name);
+ mIdentity.mRawName = name.clone();
+ mIdentity.mName = new String(name, StandardCharsets.UTF_8);
+ try {
+ String nameCheck = stringPrep.prepare(mIdentity.mName, StringPrep.DEFAULT);
+ mIdentity.mIsNameValid =
+ Arrays.equals(nameCheck.getBytes(StandardCharsets.UTF_8), name);
+ } catch (StringPrepParseException x) {
+ Log.w(TAG, x.toString());
+ mIdentity.mIsNameValid = false;
+ }
+
+ // Non-normal names must be rendered differently to avoid confusion.
+ if (!mIdentity.mIsNameValid) {
+ mIdentity.mName = "«" + escape(name) + "»";
+ }
+
+ return this;
+ }
+
+ public Builder setXpanid(byte x[]) {
+ mIdentity.mXpanid = (x != null ? x.clone() : null);
+ return this;
+ }
+
+ public Builder setPanid(int x) {
+ mIdentity.mPanid = x;
+ return this;
+ }
+
+ public Builder setType(@NonNull String x) {
+ mIdentity.mType = x;
+ return this;
+ }
+
+ public Builder setChannel(int x) {
+ mIdentity.mChannel = x;
+ return this;
+ }
+
+ public LowpanIdentity build() {
+ return mIdentity;
+ }
+ }
+
+ LowpanIdentity() {}
+
+ // Instance Variables
+
+ private String mName = "";
+ private boolean mIsNameValid = true;
+ private byte[] mRawName = new byte[0];
+ private String mType = "";
+ private byte[] mXpanid = new byte[0];
+ private int mPanid = UNSPECIFIED_PANID;
+ private int mChannel = UNSPECIFIED_CHANNEL;
+
+ // Public Getters
+
+ public String getName() {
+ return mName;
+ }
+
+ public boolean isNameValid() {
+ return mIsNameValid;
+ }
+
+ public byte[] getRawName() {
+ return mRawName.clone();
+ }
+
+ public byte[] getXpanid() {
+ return mXpanid.clone();
+ }
+
+ public int getPanid() {
+ return mPanid;
+ }
+
+ public String getType() {
+ return mType;
+ }
+
+ public int getChannel() {
+ return mChannel;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append("Name:").append(getName());
+
+ if (mType.length() > 0) {
+ sb.append(", Type:").append(mType);
+ }
+
+ if (mXpanid.length > 0) {
+ sb.append(", XPANID:").append(HexDump.toHexString(mXpanid));
+ }
+
+ if (mPanid != UNSPECIFIED_PANID) {
+ sb.append(", PANID:").append(String.format("0x%04X", mPanid));
+ }
+
+ if (mChannel != UNSPECIFIED_CHANNEL) {
+ sb.append(", Channel:").append(mChannel);
+ }
+
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof LowpanIdentity)) {
+ return false;
+ }
+ LowpanIdentity rhs = (LowpanIdentity) obj;
+ return Arrays.equals(mRawName, rhs.mRawName)
+ && Arrays.equals(mXpanid, rhs.mXpanid)
+ && mType.equals(rhs.mType)
+ && mPanid == rhs.mPanid
+ && mChannel == rhs.mChannel;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ Arrays.hashCode(mRawName), mType, Arrays.hashCode(mXpanid), mPanid, mChannel);
+ }
+
+ /** Implement the Parcelable interface. */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeByteArray(mRawName);
+ dest.writeString(mType);
+ dest.writeByteArray(mXpanid);
+ dest.writeInt(mPanid);
+ dest.writeInt(mChannel);
+ }
+
+ /** Implement the Parcelable interface. */
+ public static final @android.annotation.NonNull Creator<LowpanIdentity> CREATOR =
+ new Creator<LowpanIdentity>() {
+
+ public LowpanIdentity createFromParcel(Parcel in) {
+ Builder builder = new Builder();
+
+ builder.setRawName(in.createByteArray());
+ builder.setType(in.readString());
+ builder.setXpanid(in.createByteArray());
+ builder.setPanid(in.readInt());
+ builder.setChannel(in.readInt());
+
+ return builder.build();
+ }
+
+ public LowpanIdentity[] newArray(int size) {
+ return new LowpanIdentity[size];
+ }
+ };
+}
diff --git a/android/net/lowpan/LowpanInterface.java b/android/net/lowpan/LowpanInterface.java
new file mode 100644
index 0000000..57e9135
--- /dev/null
+++ b/android/net/lowpan/LowpanInterface.java
@@ -0,0 +1,824 @@
+/*
+ * 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 android.net.lowpan;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+import java.util.HashMap;
+
+/**
+ * Class for managing a specific Low-power Wireless Personal Area Network (LoWPAN) interface.
+ *
+ * @hide
+ */
+// @SystemApi
+public class LowpanInterface {
+ private static final String TAG = LowpanInterface.class.getSimpleName();
+
+ /** Detached role. The interface is not currently attached to a network. */
+ public static final String ROLE_DETACHED = ILowpanInterface.ROLE_DETACHED;
+
+ /** End-device role. End devices do not route traffic for other nodes. */
+ public static final String ROLE_END_DEVICE = ILowpanInterface.ROLE_END_DEVICE;
+
+ /** Router role. Routers help route traffic around the mesh network. */
+ public static final String ROLE_ROUTER = ILowpanInterface.ROLE_ROUTER;
+
+ /**
+ * Sleepy End-Device role.
+ *
+ * <p>End devices with this role are nominally asleep, waking up periodically to check in with
+ * their parent to see if there are packets destined for them. Such devices are capable of
+ * extraordinarilly low power consumption, but packet latency can be on the order of dozens of
+ * seconds(depending on how the node is configured).
+ */
+ public static final String ROLE_SLEEPY_END_DEVICE = ILowpanInterface.ROLE_SLEEPY_END_DEVICE;
+
+ /**
+ * Sleepy-router role.
+ *
+ * <p>Routers with this role are nominally asleep, waking up periodically to check in with other
+ * routers and their children.
+ */
+ public static final String ROLE_SLEEPY_ROUTER = ILowpanInterface.ROLE_SLEEPY_ROUTER;
+
+ /** TODO: doc */
+ public static final String ROLE_LEADER = ILowpanInterface.ROLE_LEADER;
+
+ /** TODO: doc */
+ public static final String ROLE_COORDINATOR = ILowpanInterface.ROLE_COORDINATOR;
+
+ /**
+ * Offline state.
+ *
+ * <p>This is the initial state of the LoWPAN interface when the underlying driver starts. In
+ * this state the NCP is idle and not connected to any network.
+ *
+ * <p>This state can be explicitly entered by calling {@link #reset()}, {@link #leave()}, or
+ * <code>setUp(false)</code>, with the later two only working if we were not previously in the
+ * {@link #STATE_FAULT} state.
+ *
+ * @see #getState()
+ * @see #STATE_FAULT
+ */
+ public static final String STATE_OFFLINE = ILowpanInterface.STATE_OFFLINE;
+
+ /**
+ * Commissioning state.
+ *
+ * <p>The interface enters this state after a call to {@link #startCommissioningSession()}. This
+ * state may only be entered directly from the {@link #STATE_OFFLINE} state.
+ *
+ * @see #startCommissioningSession()
+ * @see #getState()
+ * @hide
+ */
+ public static final String STATE_COMMISSIONING = ILowpanInterface.STATE_COMMISSIONING;
+
+ /**
+ * Attaching state.
+ *
+ * <p>The interface enters this state when it starts the process of trying to find other nodes
+ * so that it can attach to any pre-existing network fragment, or when it is in the process of
+ * calculating the optimal values for unspecified parameters when forming a new network.
+ *
+ * <p>The interface may stay in this state for a prolonged period of time (or may spontaneously
+ * enter this state from {@link #STATE_ATTACHED}) if the underlying network technology is
+ * heirarchical (like ZigBeeIP) or if the device role is that of an "end-device" ({@link
+ * #ROLE_END_DEVICE} or {@link #ROLE_SLEEPY_END_DEVICE}). This is because such roles cannot
+ * create their own network fragments.
+ *
+ * @see #STATE_ATTACHED
+ * @see #getState()
+ */
+ public static final String STATE_ATTACHING = ILowpanInterface.STATE_ATTACHING;
+
+ /**
+ * Attached state.
+ *
+ * <p>The interface enters this state from {@link #STATE_ATTACHING} once it is actively
+ * participating on a network fragment.
+ *
+ * @see #STATE_ATTACHING
+ * @see #getState()
+ */
+ public static final String STATE_ATTACHED = ILowpanInterface.STATE_ATTACHED;
+
+ /**
+ * Fault state.
+ *
+ * <p>The interface will enter this state when the driver has detected some sort of problem from
+ * which it was not immediately able to recover.
+ *
+ * <p>This state can be entered spontaneously from any other state. Calling {@link #reset} will
+ * cause the device to return to the {@link #STATE_OFFLINE} state.
+ *
+ * @see #getState
+ * @see #STATE_OFFLINE
+ */
+ public static final String STATE_FAULT = ILowpanInterface.STATE_FAULT;
+
+ /**
+ * Network type for Thread 1.x networks.
+ *
+ * @see android.net.lowpan.LowpanIdentity#getType
+ * @see #getLowpanIdentity
+ * @hide
+ */
+ public static final String NETWORK_TYPE_THREAD_V1 = ILowpanInterface.NETWORK_TYPE_THREAD_V1;
+
+ public static final String EMPTY_PARTITION_ID = "";
+
+ /**
+ * Callback base class for LowpanInterface
+ *
+ * @hide
+ */
+ // @SystemApi
+ public abstract static class Callback {
+ public void onConnectedChanged(boolean value) {}
+
+ public void onEnabledChanged(boolean value) {}
+
+ public void onUpChanged(boolean value) {}
+
+ public void onRoleChanged(@NonNull String value) {}
+
+ public void onStateChanged(@NonNull String state) {}
+
+ public void onLowpanIdentityChanged(@NonNull LowpanIdentity value) {}
+
+ public void onLinkNetworkAdded(IpPrefix prefix) {}
+
+ public void onLinkNetworkRemoved(IpPrefix prefix) {}
+
+ public void onLinkAddressAdded(LinkAddress address) {}
+
+ public void onLinkAddressRemoved(LinkAddress address) {}
+ }
+
+ private final ILowpanInterface mBinder;
+ private final Looper mLooper;
+ private final HashMap<Integer, ILowpanInterfaceListener> mListenerMap = new HashMap<>();
+
+ /**
+ * Create a new LowpanInterface instance. Applications will almost always want to use {@link
+ * LowpanManager#getInterface LowpanManager.getInterface()} instead of this.
+ *
+ * @param context the application context
+ * @param service the Binder interface
+ * @param looper the Binder interface
+ * @hide
+ */
+ public LowpanInterface(Context context, ILowpanInterface service, Looper looper) {
+ /* We aren't currently using the context, but if we need
+ * it later on we can easily add it to the class.
+ */
+
+ mBinder = service;
+ mLooper = looper;
+ }
+
+ /**
+ * Returns the ILowpanInterface object associated with this interface.
+ *
+ * @hide
+ */
+ public ILowpanInterface getService() {
+ return mBinder;
+ }
+
+ // Public Actions
+
+ /**
+ * Form a new network with the given network information optional credential. Unspecified fields
+ * in the network information will be filled in with reasonable values. If the network
+ * credential is unspecified, one will be generated automatically.
+ *
+ * <p>This method will block until either the network was successfully formed or an error
+ * prevents the network form being formed.
+ *
+ * <p>Upon success, the interface will be up and attached to the newly formed network.
+ *
+ * @see #join(LowpanProvision)
+ */
+ public void form(@NonNull LowpanProvision provision) throws LowpanException {
+ try {
+ mBinder.form(provision);
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+
+ } catch (ServiceSpecificException x) {
+ throw LowpanException.rethrowFromServiceSpecificException(x);
+ }
+ }
+
+ /**
+ * Attempts to join a new network with the given network information. This method will block
+ * until either the network was successfully joined or an error prevented the network from being
+ * formed. Upon success, the interface will be up and attached to the newly joined network.
+ *
+ * <p>Note that “joining” is distinct from “attaching”: Joining requires at least one other peer
+ * device to be present in order for the operation to complete successfully.
+ */
+ public void join(@NonNull LowpanProvision provision) throws LowpanException {
+ try {
+ mBinder.join(provision);
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+
+ } catch (ServiceSpecificException x) {
+ throw LowpanException.rethrowFromServiceSpecificException(x);
+ }
+ }
+
+ /**
+ * Attaches to the network described by identity and credential. This is similar to {@link
+ * #join}, except that (assuming the identity and credential are valid) it will always succeed
+ * and provision the interface, even if there are no peers nearby.
+ *
+ * <p>This method will block execution until the operation has completed.
+ */
+ public void attach(@NonNull LowpanProvision provision) throws LowpanException {
+ try {
+ mBinder.attach(provision);
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+
+ } catch (ServiceSpecificException x) {
+ throw LowpanException.rethrowFromServiceSpecificException(x);
+ }
+ }
+
+ /**
+ * Bring down the network interface and forget all non-volatile details about the current
+ * network.
+ *
+ * <p>This method will block execution until the operation has completed.
+ */
+ public void leave() throws LowpanException {
+ try {
+ mBinder.leave();
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+
+ } catch (ServiceSpecificException x) {
+ throw LowpanException.rethrowFromServiceSpecificException(x);
+ }
+ }
+
+ /**
+ * Start a new commissioning session. Will fail if the interface is attached to a network or if
+ * the interface is disabled.
+ */
+ public @NonNull LowpanCommissioningSession startCommissioningSession(
+ @NonNull LowpanBeaconInfo beaconInfo) throws LowpanException {
+ try {
+ mBinder.startCommissioningSession(beaconInfo);
+
+ return new LowpanCommissioningSession(mBinder, beaconInfo, mLooper);
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+
+ } catch (ServiceSpecificException x) {
+ throw LowpanException.rethrowFromServiceSpecificException(x);
+ }
+ }
+
+ /**
+ * Reset this network interface as if it has been power cycled. Will bring the network interface
+ * down if it was previously up. Will not erase any non-volatile settings.
+ *
+ * <p>This method will block execution until the operation has completed.
+ *
+ * @hide
+ */
+ public void reset() throws LowpanException {
+ try {
+ mBinder.reset();
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+
+ } catch (ServiceSpecificException x) {
+ throw LowpanException.rethrowFromServiceSpecificException(x);
+ }
+ }
+
+ // Public Getters and Setters
+
+ /** Returns the name of this network interface. */
+ @NonNull
+ public String getName() {
+ try {
+ return mBinder.getName();
+
+ } catch (DeadObjectException x) {
+ return "";
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Indicates if the interface is enabled or disabled.
+ *
+ * @see #setEnabled
+ * @see android.net.lowpan.LowpanException#LOWPAN_DISABLED
+ */
+ public boolean isEnabled() {
+ try {
+ return mBinder.isEnabled();
+
+ } catch (DeadObjectException x) {
+ return false;
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Enables or disables the LoWPAN interface. When disabled, the interface is put into a
+ * low-power state and all commands that require the NCP to be queried will fail with {@link
+ * android.net.lowpan.LowpanException#LOWPAN_DISABLED}.
+ *
+ * @see #isEnabled
+ * @see android.net.lowpan.LowpanException#LOWPAN_DISABLED
+ * @hide
+ */
+ public void setEnabled(boolean enabled) throws LowpanException {
+ try {
+ mBinder.setEnabled(enabled);
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+
+ } catch (ServiceSpecificException x) {
+ throw LowpanException.rethrowFromServiceSpecificException(x);
+ }
+ }
+
+ /**
+ * Indicates if the network interface is up or down.
+ *
+ * @hide
+ */
+ public boolean isUp() {
+ try {
+ return mBinder.isUp();
+
+ } catch (DeadObjectException x) {
+ return false;
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Indicates if there is at least one peer in range.
+ *
+ * @return <code>true</code> if we have at least one other peer in range, <code>false</code>
+ * otherwise.
+ */
+ public boolean isConnected() {
+ try {
+ return mBinder.isConnected();
+
+ } catch (DeadObjectException x) {
+ return false;
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Indicates if this interface is currently commissioned onto an existing network. If the
+ * interface is commissioned, the interface may be brought up using setUp().
+ */
+ public boolean isCommissioned() {
+ try {
+ return mBinder.isCommissioned();
+
+ } catch (DeadObjectException x) {
+ return false;
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Get interface state
+ *
+ * <h3>State Diagram</h3>
+ *
+ * <img src="LowpanInterface-1.png" />
+ *
+ * @return The current state of the interface.
+ * @see #STATE_OFFLINE
+ * @see #STATE_COMMISSIONING
+ * @see #STATE_ATTACHING
+ * @see #STATE_ATTACHED
+ * @see #STATE_FAULT
+ */
+ public String getState() {
+ try {
+ return mBinder.getState();
+
+ } catch (DeadObjectException x) {
+ return STATE_FAULT;
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+ }
+ }
+
+ /** Get network partition/fragment identifier. */
+ public String getPartitionId() {
+ try {
+ return mBinder.getPartitionId();
+
+ } catch (DeadObjectException x) {
+ return EMPTY_PARTITION_ID;
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+ }
+ }
+
+ /** TODO: doc */
+ public LowpanIdentity getLowpanIdentity() {
+ try {
+ return mBinder.getLowpanIdentity();
+
+ } catch (DeadObjectException x) {
+ return new LowpanIdentity();
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+ }
+ }
+
+ /** TODO: doc */
+ @NonNull
+ public String getRole() {
+ try {
+ return mBinder.getRole();
+
+ } catch (DeadObjectException x) {
+ return ROLE_DETACHED;
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+ }
+ }
+
+ /** TODO: doc */
+ @Nullable
+ public LowpanCredential getLowpanCredential() {
+ try {
+ return mBinder.getLowpanCredential();
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+ }
+ }
+
+ public @NonNull String[] getSupportedNetworkTypes() throws LowpanException {
+ try {
+ return mBinder.getSupportedNetworkTypes();
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+
+ } catch (ServiceSpecificException x) {
+ throw LowpanException.rethrowFromServiceSpecificException(x);
+ }
+ }
+
+ public @NonNull LowpanChannelInfo[] getSupportedChannels() throws LowpanException {
+ try {
+ return mBinder.getSupportedChannels();
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+
+ } catch (ServiceSpecificException x) {
+ throw LowpanException.rethrowFromServiceSpecificException(x);
+ }
+ }
+
+ // Listener Support
+
+ /**
+ * Registers a subclass of {@link LowpanInterface.Callback} to receive events.
+ *
+ * @param cb Subclass of {@link LowpanInterface.Callback} which will receive events.
+ * @param handler If not <code>null</code>, events will be dispatched via the given handler
+ * object. If <code>null</code>, the thread upon which events will be dispatched is
+ * unspecified.
+ * @see #registerCallback(Callback)
+ * @see #unregisterCallback(Callback)
+ */
+ public void registerCallback(@NonNull Callback cb, @Nullable Handler handler) {
+ ILowpanInterfaceListener.Stub listenerBinder =
+ new ILowpanInterfaceListener.Stub() {
+ private Handler mHandler;
+
+ {
+ if (handler != null) {
+ mHandler = handler;
+ } else if (mLooper != null) {
+ mHandler = new Handler(mLooper);
+ } else {
+ mHandler = new Handler();
+ }
+ }
+
+ @Override
+ public void onEnabledChanged(boolean value) {
+ mHandler.post(() -> cb.onEnabledChanged(value));
+ }
+
+ @Override
+ public void onConnectedChanged(boolean value) {
+ mHandler.post(() -> cb.onConnectedChanged(value));
+ }
+
+ @Override
+ public void onUpChanged(boolean value) {
+ mHandler.post(() -> cb.onUpChanged(value));
+ }
+
+ @Override
+ public void onRoleChanged(String value) {
+ mHandler.post(() -> cb.onRoleChanged(value));
+ }
+
+ @Override
+ public void onStateChanged(String value) {
+ mHandler.post(() -> cb.onStateChanged(value));
+ }
+
+ @Override
+ public void onLowpanIdentityChanged(LowpanIdentity value) {
+ mHandler.post(() -> cb.onLowpanIdentityChanged(value));
+ }
+
+ @Override
+ public void onLinkNetworkAdded(IpPrefix value) {
+ mHandler.post(() -> cb.onLinkNetworkAdded(value));
+ }
+
+ @Override
+ public void onLinkNetworkRemoved(IpPrefix value) {
+ mHandler.post(() -> cb.onLinkNetworkRemoved(value));
+ }
+
+ @Override
+ public void onLinkAddressAdded(String value) {
+ LinkAddress la;
+ try {
+ la = new LinkAddress(value);
+ } catch (IllegalArgumentException x) {
+ Log.e(
+ TAG,
+ "onLinkAddressAdded: Bad LinkAddress \"" + value + "\", " + x);
+ return;
+ }
+ mHandler.post(() -> cb.onLinkAddressAdded(la));
+ }
+
+ @Override
+ public void onLinkAddressRemoved(String value) {
+ LinkAddress la;
+ try {
+ la = new LinkAddress(value);
+ } catch (IllegalArgumentException x) {
+ Log.e(
+ TAG,
+ "onLinkAddressRemoved: Bad LinkAddress \""
+ + value
+ + "\", "
+ + x);
+ return;
+ }
+ mHandler.post(() -> cb.onLinkAddressRemoved(la));
+ }
+
+ @Override
+ public void onReceiveFromCommissioner(byte[] packet) {
+ // This is only used by the LowpanCommissioningSession.
+ }
+ };
+ try {
+ mBinder.addListener(listenerBinder);
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+ }
+
+ synchronized (mListenerMap) {
+ mListenerMap.put(System.identityHashCode(cb), listenerBinder);
+ }
+ }
+
+ /**
+ * Registers a subclass of {@link LowpanInterface.Callback} to receive events.
+ *
+ * <p>The thread upon which events will be dispatched is unspecified.
+ *
+ * @param cb Subclass of {@link LowpanInterface.Callback} which will receive events.
+ * @see #registerCallback(Callback, Handler)
+ * @see #unregisterCallback(Callback)
+ */
+ public void registerCallback(Callback cb) {
+ registerCallback(cb, null);
+ }
+
+ /**
+ * Unregisters a previously registered callback class.
+ *
+ * @param cb Subclass of {@link LowpanInterface.Callback} which was previously registered to
+ * receive events.
+ * @see #registerCallback(Callback, Handler)
+ * @see #registerCallback(Callback)
+ */
+ public void unregisterCallback(Callback cb) {
+ int hashCode = System.identityHashCode(cb);
+ synchronized (mListenerMap) {
+ ILowpanInterfaceListener listenerBinder = mListenerMap.get(hashCode);
+
+ if (listenerBinder != null) {
+ mListenerMap.remove(hashCode);
+
+ try {
+ mBinder.removeListener(listenerBinder);
+ } catch (DeadObjectException x) {
+ // We ignore a dead object exception because that
+ // pretty clearly means our callback isn't registered.
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+ }
+ }
+ }
+ }
+
+ // Active and Passive Scanning
+
+ /**
+ * Creates a new {@link android.net.lowpan.LowpanScanner} object for this interface.
+ *
+ * <p>This method allocates a new unique object for each call.
+ *
+ * @see android.net.lowpan.LowpanScanner
+ */
+ public @NonNull LowpanScanner createScanner() {
+ return new LowpanScanner(mBinder);
+ }
+
+ // Route Management
+
+ /**
+ * Makes a copy of the internal list of LinkAddresses.
+ *
+ * @hide
+ */
+ public LinkAddress[] getLinkAddresses() throws LowpanException {
+ try {
+ String[] linkAddressStrings = mBinder.getLinkAddresses();
+ LinkAddress[] ret = new LinkAddress[linkAddressStrings.length];
+ int i = 0;
+ for (String str : linkAddressStrings) {
+ ret[i++] = new LinkAddress(str);
+ }
+ return ret;
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+
+ } catch (ServiceSpecificException x) {
+ throw LowpanException.rethrowFromServiceSpecificException(x);
+ }
+ }
+
+ /**
+ * Makes a copy of the internal list of networks reachable on via this link.
+ *
+ * @hide
+ */
+ public IpPrefix[] getLinkNetworks() throws LowpanException {
+ try {
+ return mBinder.getLinkNetworks();
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+
+ } catch (ServiceSpecificException x) {
+ throw LowpanException.rethrowFromServiceSpecificException(x);
+ }
+ }
+
+ /**
+ * Advertise the given IP prefix as an on-mesh prefix.
+ *
+ * @hide
+ */
+ public void addOnMeshPrefix(IpPrefix prefix, int flags) throws LowpanException {
+ try {
+ mBinder.addOnMeshPrefix(prefix, flags);
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+
+ } catch (ServiceSpecificException x) {
+ throw LowpanException.rethrowFromServiceSpecificException(x);
+ }
+ }
+
+ /**
+ * Remove an IP prefix previously advertised by this device from the list of advertised on-mesh
+ * prefixes.
+ *
+ * @hide
+ */
+ public void removeOnMeshPrefix(IpPrefix prefix) {
+ try {
+ mBinder.removeOnMeshPrefix(prefix);
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+
+ } catch (ServiceSpecificException x) {
+ // Catch and ignore all service exceptions
+ Log.e(TAG, x.toString());
+ }
+ }
+
+ /**
+ * Advertise this device to other devices on the mesh network as having a specific route to the
+ * given network. This device will then receive forwarded traffic for that network.
+ *
+ * @hide
+ */
+ public void addExternalRoute(IpPrefix prefix, int flags) throws LowpanException {
+ try {
+ mBinder.addExternalRoute(prefix, flags);
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+
+ } catch (ServiceSpecificException x) {
+ throw LowpanException.rethrowFromServiceSpecificException(x);
+ }
+ }
+
+ /**
+ * Revoke a previously advertised specific route to the given network.
+ *
+ * @hide
+ */
+ public void removeExternalRoute(IpPrefix prefix) {
+ try {
+ mBinder.removeExternalRoute(prefix);
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+
+ } catch (ServiceSpecificException x) {
+ // Catch and ignore all service exceptions
+ Log.e(TAG, x.toString());
+ }
+ }
+}
diff --git a/android/net/lowpan/LowpanManager.java b/android/net/lowpan/LowpanManager.java
new file mode 100644
index 0000000..76876ce
--- /dev/null
+++ b/android/net/lowpan/LowpanManager.java
@@ -0,0 +1,327 @@
+/*
+ * 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 android.net.lowpan;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * Manager object for looking up LoWPAN interfaces.
+ *
+ * @hide
+ */
+// @SystemApi
+public class LowpanManager {
+ private static final String TAG = LowpanManager.class.getSimpleName();
+
+ /** @hide */
+ // @SystemApi
+ public abstract static class Callback {
+ public void onInterfaceAdded(LowpanInterface lowpanInterface) {}
+
+ public void onInterfaceRemoved(LowpanInterface lowpanInterface) {}
+ }
+
+ private final Map<Integer, ILowpanManagerListener> mListenerMap = new HashMap<>();
+ private final Map<String, LowpanInterface> mInterfaceCache = new HashMap<>();
+
+ /* This is a WeakHashMap because we don't want to hold onto
+ * a strong reference to ILowpanInterface, so that it can be
+ * garbage collected if it isn't being used anymore. Since
+ * the value class holds onto this specific ILowpanInterface,
+ * we also need to have a weak reference to the value.
+ * This design pattern allows us to skip removal of items
+ * from this Map without leaking memory.
+ */
+ private final Map<IBinder, WeakReference<LowpanInterface>> mBinderCache =
+ new WeakHashMap<>();
+
+ private final ILowpanManager mService;
+ private final Context mContext;
+ private final Looper mLooper;
+
+ // Static Methods
+
+ public static LowpanManager from(Context context) {
+ return (LowpanManager) context.getSystemService(Context.LOWPAN_SERVICE);
+ }
+
+ /** @hide */
+ public static LowpanManager getManager() {
+ IBinder binder = ServiceManager.getService(Context.LOWPAN_SERVICE);
+
+ if (binder != null) {
+ ILowpanManager service = ILowpanManager.Stub.asInterface(binder);
+ return new LowpanManager(service);
+ }
+
+ return null;
+ }
+
+ // Constructors
+
+ LowpanManager(ILowpanManager service) {
+ mService = service;
+ mContext = null;
+ mLooper = null;
+ }
+
+ /**
+ * Create a new LowpanManager instance. Applications will almost always want to use {@link
+ * android.content.Context#getSystemService Context.getSystemService()} to retrieve the standard
+ * {@link android.content.Context#LOWPAN_SERVICE Context.LOWPAN_SERVICE}.
+ *
+ * @param context the application context
+ * @param service the Binder interface
+ * @param looper the default Looper to run callbacks on
+ * @hide - hide this because it takes in a parameter of type ILowpanManager, which is a system
+ * private class.
+ */
+ public LowpanManager(Context context, ILowpanManager service, Looper looper) {
+ mContext = context;
+ mService = service;
+ mLooper = looper;
+ }
+
+ /** @hide */
+ @Nullable
+ public LowpanInterface getInterfaceNoCreate(@NonNull ILowpanInterface ifaceService) {
+ LowpanInterface iface = null;
+
+ synchronized (mBinderCache) {
+ if (mBinderCache.containsKey(ifaceService.asBinder())) {
+ iface = mBinderCache.get(ifaceService.asBinder()).get();
+ }
+ }
+
+ return iface;
+ }
+
+ /** @hide */
+ @Nullable
+ public LowpanInterface getInterface(@NonNull ILowpanInterface ifaceService) {
+ LowpanInterface iface = null;
+
+ try {
+ synchronized (mBinderCache) {
+ if (mBinderCache.containsKey(ifaceService.asBinder())) {
+ iface = mBinderCache.get(ifaceService.asBinder()).get();
+ }
+
+ if (iface == null) {
+ String ifaceName = ifaceService.getName();
+
+ iface = new LowpanInterface(mContext, ifaceService, mLooper);
+
+ synchronized (mInterfaceCache) {
+ mInterfaceCache.put(iface.getName(), iface);
+ }
+
+ mBinderCache.put(ifaceService.asBinder(), new WeakReference(iface));
+
+ /* Make sure we remove the object from the
+ * interface cache if the associated service
+ * dies.
+ */
+ ifaceService
+ .asBinder()
+ .linkToDeath(
+ new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ synchronized (mInterfaceCache) {
+ LowpanInterface iface =
+ mInterfaceCache.get(ifaceName);
+
+ if ((iface != null)
+ && (iface.getService() == ifaceService)) {
+ mInterfaceCache.remove(ifaceName);
+ }
+ }
+ }
+ },
+ 0);
+ }
+ }
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+ }
+
+ return iface;
+ }
+
+ /**
+ * Returns a reference to the requested LowpanInterface object. If the given interface doesn't
+ * exist, or it is not a LoWPAN interface, returns null.
+ */
+ @Nullable
+ public LowpanInterface getInterface(@NonNull String name) {
+ LowpanInterface iface = null;
+
+ try {
+ /* This synchronized block covers both branches of the enclosed
+ * if() statement in order to avoid a race condition. Two threads
+ * calling getInterface() with the same name would race to create
+ * the associated LowpanInterface object, creating two of them.
+ * Having the whole block be synchronized avoids that race.
+ */
+ synchronized (mInterfaceCache) {
+ if (mInterfaceCache.containsKey(name)) {
+ iface = mInterfaceCache.get(name);
+
+ } else {
+ ILowpanInterface ifaceService = mService.getInterface(name);
+
+ if (ifaceService != null) {
+ iface = getInterface(ifaceService);
+ }
+ }
+ }
+ } catch (RemoteException x) {
+ throw x.rethrowFromSystemServer();
+ }
+
+ return iface;
+ }
+
+ /**
+ * Returns a reference to the first registered LowpanInterface object. If there are no LoWPAN
+ * interfaces registered, returns null.
+ */
+ @Nullable
+ public LowpanInterface getInterface() {
+ String[] ifaceList = getInterfaceList();
+ if (ifaceList.length > 0) {
+ return getInterface(ifaceList[0]);
+ }
+ return null;
+ }
+
+ /**
+ * Returns a string array containing the names of LoWPAN interfaces. This list may contain fewer
+ * interfaces if the calling process does not have permissions to see individual interfaces.
+ */
+ @NonNull
+ public String[] getInterfaceList() {
+ try {
+ return mService.getInterfaceList();
+ } catch (RemoteException x) {
+ throw x.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers a callback object to receive notifications when LoWPAN interfaces are added or
+ * removed.
+ *
+ * @hide
+ */
+ public void registerCallback(@NonNull Callback cb, @Nullable Handler handler)
+ throws LowpanException {
+ ILowpanManagerListener.Stub listenerBinder =
+ new ILowpanManagerListener.Stub() {
+ private Handler mHandler;
+
+ {
+ if (handler != null) {
+ mHandler = handler;
+ } else if (mLooper != null) {
+ mHandler = new Handler(mLooper);
+ } else {
+ mHandler = new Handler();
+ }
+ }
+
+ @Override
+ public void onInterfaceAdded(ILowpanInterface ifaceService) {
+ Runnable runnable =
+ () -> {
+ LowpanInterface iface = getInterface(ifaceService);
+
+ if (iface != null) {
+ cb.onInterfaceAdded(iface);
+ }
+ };
+
+ mHandler.post(runnable);
+ }
+
+ @Override
+ public void onInterfaceRemoved(ILowpanInterface ifaceService) {
+ Runnable runnable =
+ () -> {
+ LowpanInterface iface = getInterfaceNoCreate(ifaceService);
+
+ if (iface != null) {
+ cb.onInterfaceRemoved(iface);
+ }
+ };
+
+ mHandler.post(runnable);
+ }
+ };
+ try {
+ mService.addListener(listenerBinder);
+ } catch (RemoteException x) {
+ throw x.rethrowFromSystemServer();
+ }
+
+ synchronized (mListenerMap) {
+ mListenerMap.put(Integer.valueOf(System.identityHashCode(cb)), listenerBinder);
+ }
+ }
+
+ /** @hide */
+ public void registerCallback(@NonNull Callback cb) throws LowpanException {
+ registerCallback(cb, null);
+ }
+
+ /**
+ * Unregisters a previously registered {@link LowpanManager.Callback} object.
+ *
+ * @hide
+ */
+ public void unregisterCallback(@NonNull Callback cb) {
+ Integer hashCode = Integer.valueOf(System.identityHashCode(cb));
+ ILowpanManagerListener listenerBinder = null;
+
+ synchronized (mListenerMap) {
+ listenerBinder = mListenerMap.get(hashCode);
+ mListenerMap.remove(hashCode);
+ }
+
+ if (listenerBinder != null) {
+ try {
+ mService.removeListener(listenerBinder);
+ } catch (RemoteException x) {
+ throw x.rethrowFromSystemServer();
+ }
+ } else {
+ throw new RuntimeException("Attempt to unregister an unknown callback");
+ }
+ }
+}
diff --git a/android/net/lowpan/LowpanProperties.java b/android/net/lowpan/LowpanProperties.java
new file mode 100644
index 0000000..cc45ff8
--- /dev/null
+++ b/android/net/lowpan/LowpanProperties.java
@@ -0,0 +1,56 @@
+/*
+ * 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 android.net.lowpan;
+
+/** {@hide} */
+public final class LowpanProperties {
+
+ public static final LowpanProperty<int[]> KEY_CHANNEL_MASK =
+ new LowpanStandardProperty("android.net.lowpan.property.CHANNEL_MASK", int[].class);
+
+ public static final LowpanProperty<Integer> KEY_MAX_TX_POWER =
+ new LowpanStandardProperty("android.net.lowpan.property.MAX_TX_POWER", Integer.class);
+
+ /** @hide */
+ private LowpanProperties() {}
+
+ /** @hide */
+ static final class LowpanStandardProperty<T> extends LowpanProperty<T> {
+ private final String mName;
+ private final Class<T> mType;
+
+ LowpanStandardProperty(String name, Class<T> type) {
+ mName = name;
+ mType = type;
+ }
+
+ @Override
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ public Class<T> getType() {
+ return mType;
+ }
+
+ @Override
+ public String toString() {
+ return getName();
+ }
+ }
+}
diff --git a/android/net/lowpan/LowpanProperty.java b/android/net/lowpan/LowpanProperty.java
new file mode 100644
index 0000000..7f26986
--- /dev/null
+++ b/android/net/lowpan/LowpanProperty.java
@@ -0,0 +1,34 @@
+/*
+ * 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 android.net.lowpan;
+
+import java.util.Map;
+
+/** {@hide} */
+public abstract class LowpanProperty<T> {
+ public abstract String getName();
+
+ public abstract Class<T> getType();
+
+ public void putInMap(Map map, T value) {
+ map.put(getName(), value);
+ }
+
+ public T getFromMap(Map map) {
+ return (T) map.get(getName());
+ }
+}
diff --git a/android/net/lowpan/LowpanProvision.java b/android/net/lowpan/LowpanProvision.java
new file mode 100644
index 0000000..68c8709
--- /dev/null
+++ b/android/net/lowpan/LowpanProvision.java
@@ -0,0 +1,149 @@
+/*
+ * 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 android.net.lowpan;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import java.util.Objects;
+
+/**
+ * Describes the information needed to describe a network
+ *
+ * @hide
+ */
+// @SystemApi
+public class LowpanProvision implements Parcelable {
+
+ // Builder
+
+ /** @hide */
+ // @SystemApi
+ public static class Builder {
+ private final LowpanProvision provision = new LowpanProvision();
+
+ public Builder setLowpanIdentity(@NonNull LowpanIdentity identity) {
+ provision.mIdentity = identity;
+ return this;
+ }
+
+ public Builder setLowpanCredential(@NonNull LowpanCredential credential) {
+ provision.mCredential = credential;
+ return this;
+ }
+
+ public LowpanProvision build() {
+ return provision;
+ }
+ }
+
+ private LowpanProvision() {}
+
+ // Instance Variables
+
+ private LowpanIdentity mIdentity = new LowpanIdentity();
+ private LowpanCredential mCredential = null;
+
+ // Public Getters and Setters
+
+ @NonNull
+ public LowpanIdentity getLowpanIdentity() {
+ return mIdentity;
+ }
+
+ @Nullable
+ public LowpanCredential getLowpanCredential() {
+ return mCredential;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append("LowpanProvision { identity => ").append(mIdentity.toString());
+
+ if (mCredential != null) {
+ sb.append(", credential => ").append(mCredential.toString());
+ }
+
+ sb.append("}");
+
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mIdentity, mCredential);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof LowpanProvision)) {
+ return false;
+ }
+ LowpanProvision rhs = (LowpanProvision) obj;
+
+ if (!mIdentity.equals(rhs.mIdentity)) {
+ return false;
+ }
+
+ if (!Objects.equals(mCredential, rhs.mCredential)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /** Implement the Parcelable interface. */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ mIdentity.writeToParcel(dest, flags);
+ if (mCredential == null) {
+ dest.writeBoolean(false);
+ } else {
+ dest.writeBoolean(true);
+ mCredential.writeToParcel(dest, flags);
+ }
+ }
+
+ /** Implement the Parcelable interface. */
+ public static final @android.annotation.NonNull Creator<LowpanProvision> CREATOR =
+ new Creator<LowpanProvision>() {
+ public LowpanProvision createFromParcel(Parcel in) {
+ Builder builder = new Builder();
+
+ builder.setLowpanIdentity(LowpanIdentity.CREATOR.createFromParcel(in));
+
+ if (in.readBoolean()) {
+ builder.setLowpanCredential(LowpanCredential.CREATOR.createFromParcel(in));
+ }
+
+ return builder.build();
+ }
+
+ public LowpanProvision[] newArray(int size) {
+ return new LowpanProvision[size];
+ }
+ };
+};
diff --git a/android/net/lowpan/LowpanRuntimeException.java b/android/net/lowpan/LowpanRuntimeException.java
new file mode 100644
index 0000000..71a5a13
--- /dev/null
+++ b/android/net/lowpan/LowpanRuntimeException.java
@@ -0,0 +1,42 @@
+/*
+ * 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 android.net.lowpan;
+
+import android.util.AndroidRuntimeException;
+
+/**
+ * Generic runtime exception for LoWPAN operations.
+ *
+ * @hide
+ */
+// @SystemApi
+public class LowpanRuntimeException extends AndroidRuntimeException {
+
+ public LowpanRuntimeException() {}
+
+ public LowpanRuntimeException(String message) {
+ super(message);
+ }
+
+ public LowpanRuntimeException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public LowpanRuntimeException(Exception cause) {
+ super(cause);
+ }
+}
diff --git a/android/net/lowpan/LowpanScanner.java b/android/net/lowpan/LowpanScanner.java
new file mode 100644
index 0000000..59156c4
--- /dev/null
+++ b/android/net/lowpan/LowpanScanner.java
@@ -0,0 +1,326 @@
+/*
+ * 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 android.net.lowpan;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * LoWPAN Scanner
+ *
+ * <p>This class allows performing network (active) scans and energy (passive) scans.
+ *
+ * @see LowpanInterface
+ * @hide
+ */
+// @SystemApi
+public class LowpanScanner {
+ private static final String TAG = LowpanScanner.class.getSimpleName();
+
+ // Public Classes
+
+ /**
+ * Callback base class for LowpanScanner
+ *
+ * @hide
+ */
+ // @SystemApi
+ public abstract static class Callback {
+ public void onNetScanBeacon(LowpanBeaconInfo beacon) {}
+
+ public void onEnergyScanResult(LowpanEnergyScanResult result) {}
+
+ public void onScanFinished() {}
+ }
+
+ // Instance Variables
+
+ private ILowpanInterface mBinder;
+ private Callback mCallback = null;
+ private Handler mHandler = null;
+ private ArrayList<Integer> mChannelMask = null;
+ private int mTxPower = Integer.MAX_VALUE;
+
+ // Constructors/Accessors and Exception Glue
+
+ LowpanScanner(@NonNull ILowpanInterface binder) {
+ mBinder = binder;
+ }
+
+ /** Sets an instance of {@link LowpanScanner.Callback} to receive events. */
+ public synchronized void setCallback(@Nullable Callback cb, @Nullable Handler handler) {
+ mCallback = cb;
+ mHandler = handler;
+ }
+
+ /** Sets an instance of {@link LowpanScanner.Callback} to receive events. */
+ public void setCallback(@Nullable Callback cb) {
+ setCallback(cb, null);
+ }
+
+ /**
+ * Sets the channel mask to use when scanning.
+ *
+ * @param mask The channel mask to use when scanning. If <code>null</code>, any previously set
+ * channel mask will be cleared and all channels not masked by the current regulatory zone
+ * will be scanned.
+ */
+ public void setChannelMask(@Nullable Collection<Integer> mask) {
+ if (mask == null) {
+ mChannelMask = null;
+ } else {
+ if (mChannelMask == null) {
+ mChannelMask = new ArrayList<>();
+ } else {
+ mChannelMask.clear();
+ }
+ mChannelMask.addAll(mask);
+ }
+ }
+
+ /**
+ * Gets the current channel mask.
+ *
+ * @return the current channel mask, or <code>null</code> if no channel mask is currently set.
+ */
+ public @Nullable Collection<Integer> getChannelMask() {
+ return (Collection<Integer>) mChannelMask.clone();
+ }
+
+ /**
+ * Adds a channel to the channel mask used for scanning.
+ *
+ * <p>If a channel mask was previously <code>null</code>, a new one is created containing only
+ * this channel. May be called multiple times to add additional channels ot the channel mask.
+ *
+ * @see #setChannelMask
+ * @see #getChannelMask
+ * @see #getTxPower
+ */
+ public void addChannel(int channel) {
+ if (mChannelMask == null) {
+ mChannelMask = new ArrayList<>();
+ }
+ mChannelMask.add(Integer.valueOf(channel));
+ }
+
+ /**
+ * Sets the maximum transmit power to be used for active scanning.
+ *
+ * <p>The actual transmit power used is the lesser of this value and the currently configured
+ * maximum transmit power for the interface.
+ *
+ * @see #getTxPower
+ */
+ public void setTxPower(int txPower) {
+ mTxPower = txPower;
+ }
+
+ /**
+ * Gets the maximum transmit power used for active scanning.
+ *
+ * @see #setTxPower
+ */
+ public int getTxPower() {
+ return mTxPower;
+ }
+
+ private Map<String, Object> createScanOptionMap() {
+ Map<String, Object> map = new HashMap();
+
+ if (mChannelMask != null) {
+ LowpanProperties.KEY_CHANNEL_MASK.putInMap(
+ map, mChannelMask.stream().mapToInt(i -> i).toArray());
+ }
+
+ if (mTxPower != Integer.MAX_VALUE) {
+ LowpanProperties.KEY_MAX_TX_POWER.putInMap(map, Integer.valueOf(mTxPower));
+ }
+
+ return map;
+ }
+
+ /**
+ * Start a network scan.
+ *
+ * <p>This method will return once the scan has started.
+ *
+ * @see #stopNetScan
+ */
+ public void startNetScan() throws LowpanException {
+ Map<String, Object> map = createScanOptionMap();
+
+ ILowpanNetScanCallback binderListener =
+ new ILowpanNetScanCallback.Stub() {
+ public void onNetScanBeacon(LowpanBeaconInfo beaconInfo) {
+ Callback callback;
+ Handler handler;
+
+ synchronized (LowpanScanner.this) {
+ callback = mCallback;
+ handler = mHandler;
+ }
+
+ if (callback == null) {
+ return;
+ }
+
+ Runnable runnable = () -> callback.onNetScanBeacon(beaconInfo);
+
+ if (handler != null) {
+ handler.post(runnable);
+ } else {
+ runnable.run();
+ }
+ }
+
+ public void onNetScanFinished() {
+ Callback callback;
+ Handler handler;
+
+ synchronized (LowpanScanner.this) {
+ callback = mCallback;
+ handler = mHandler;
+ }
+
+ if (callback == null) {
+ return;
+ }
+
+ Runnable runnable = () -> callback.onScanFinished();
+
+ if (handler != null) {
+ handler.post(runnable);
+ } else {
+ runnable.run();
+ }
+ }
+ };
+
+ try {
+ mBinder.startNetScan(map, binderListener);
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+
+ } catch (ServiceSpecificException x) {
+ throw LowpanException.rethrowFromServiceSpecificException(x);
+ }
+ }
+
+ /**
+ * Stop a network scan currently in progress.
+ *
+ * @see #startNetScan
+ */
+ public void stopNetScan() {
+ try {
+ mBinder.stopNetScan();
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Start an energy scan.
+ *
+ * <p>This method will return once the scan has started.
+ *
+ * @see #stopEnergyScan
+ */
+ public void startEnergyScan() throws LowpanException {
+ Map<String, Object> map = createScanOptionMap();
+
+ ILowpanEnergyScanCallback binderListener =
+ new ILowpanEnergyScanCallback.Stub() {
+ public void onEnergyScanResult(int channel, int rssi) {
+ Callback callback = mCallback;
+ Handler handler = mHandler;
+
+ if (callback == null) {
+ return;
+ }
+
+ Runnable runnable =
+ () -> {
+ if (callback != null) {
+ LowpanEnergyScanResult result =
+ new LowpanEnergyScanResult();
+ result.setChannel(channel);
+ result.setMaxRssi(rssi);
+ callback.onEnergyScanResult(result);
+ }
+ };
+
+ if (handler != null) {
+ handler.post(runnable);
+ } else {
+ runnable.run();
+ }
+ }
+
+ public void onEnergyScanFinished() {
+ Callback callback = mCallback;
+ Handler handler = mHandler;
+
+ if (callback == null) {
+ return;
+ }
+
+ Runnable runnable = () -> callback.onScanFinished();
+
+ if (handler != null) {
+ handler.post(runnable);
+ } else {
+ runnable.run();
+ }
+ }
+ };
+
+ try {
+ mBinder.startEnergyScan(map, binderListener);
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+
+ } catch (ServiceSpecificException x) {
+ throw LowpanException.rethrowFromServiceSpecificException(x);
+ }
+ }
+
+ /**
+ * Stop an energy scan currently in progress.
+ *
+ * @see #startEnergyScan
+ */
+ public void stopEnergyScan() {
+ try {
+ mBinder.stopEnergyScan();
+
+ } catch (RemoteException x) {
+ throw x.rethrowAsRuntimeException();
+ }
+ }
+}
diff --git a/android/net/lowpan/NetworkAlreadyExistsException.java b/android/net/lowpan/NetworkAlreadyExistsException.java
new file mode 100644
index 0000000..90ef498
--- /dev/null
+++ b/android/net/lowpan/NetworkAlreadyExistsException.java
@@ -0,0 +1,41 @@
+/*
+ * 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 android.net.lowpan;
+
+/**
+ * Exception indicating the form operation found a network nearby with the same identity.
+ *
+ * @see LowpanInterface
+ * @hide
+ */
+// @SystemApi
+public class NetworkAlreadyExistsException extends LowpanException {
+
+ public NetworkAlreadyExistsException() {}
+
+ public NetworkAlreadyExistsException(String message) {
+ super(message, null);
+ }
+
+ public NetworkAlreadyExistsException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public NetworkAlreadyExistsException(Exception cause) {
+ super(cause);
+ }
+}
diff --git a/android/net/lowpan/OperationCanceledException.java b/android/net/lowpan/OperationCanceledException.java
new file mode 100644
index 0000000..fcafe3a
--- /dev/null
+++ b/android/net/lowpan/OperationCanceledException.java
@@ -0,0 +1,41 @@
+/*
+ * 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 android.net.lowpan;
+
+/**
+ * Exception indicating this operation was canceled by the driver before it could finish.
+ *
+ * @see LowpanInterface
+ * @hide
+ */
+// @SystemApi
+public class OperationCanceledException extends LowpanException {
+
+ public OperationCanceledException() {}
+
+ public OperationCanceledException(String message) {
+ super(message);
+ }
+
+ public OperationCanceledException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ protected OperationCanceledException(Exception cause) {
+ super(cause);
+ }
+}
diff --git a/android/net/lowpan/WrongStateException.java b/android/net/lowpan/WrongStateException.java
new file mode 100644
index 0000000..3565419
--- /dev/null
+++ b/android/net/lowpan/WrongStateException.java
@@ -0,0 +1,41 @@
+/*
+ * 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 android.net.lowpan;
+
+/**
+ * Exception indicating the interface is the wrong state for an operation.
+ *
+ * @see LowpanInterface
+ * @hide
+ */
+// @SystemApi
+public class WrongStateException extends LowpanException {
+
+ public WrongStateException() {}
+
+ public WrongStateException(String message) {
+ super(message);
+ }
+
+ public WrongStateException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ protected WrongStateException(Exception cause) {
+ super(cause);
+ }
+}
diff --git a/android/net/metrics/ApfProgramEvent.java b/android/net/metrics/ApfProgramEvent.java
new file mode 100644
index 0000000..e9c209c
--- /dev/null
+++ b/android/net/metrics/ApfProgramEvent.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2016 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.metrics;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.SparseArray;
+
+import com.android.internal.util.MessageUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.List;
+
+/**
+ * An event logged when there is a change or event that requires updating the
+ * the APF program in place with a new APF program.
+ * {@hide}
+ */
+@TestApi
+@SystemApi
+public final class ApfProgramEvent implements IpConnectivityLog.Event {
+
+ // Bitflag constants describing what an Apf program filters.
+ // Bits are indexeds from LSB to MSB, starting at index 0.
+ /** @hide */
+ public static final int FLAG_MULTICAST_FILTER_ON = 0;
+ /** @hide */
+ public static final int FLAG_HAS_IPV4_ADDRESS = 1;
+
+ /** {@hide} */
+ @IntDef(flag = true, value = {FLAG_MULTICAST_FILTER_ON, FLAG_HAS_IPV4_ADDRESS})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Flags {}
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public final long lifetime; // Maximum computed lifetime of the program in seconds
+ /** @hide */
+ @UnsupportedAppUsage
+ public final long actualLifetime; // Effective program lifetime in seconds
+ /** @hide */
+ @UnsupportedAppUsage
+ public final int filteredRas; // Number of RAs filtered by the APF program
+ /** @hide */
+ @UnsupportedAppUsage
+ public final int currentRas; // Total number of current RAs at generation time
+ /** @hide */
+ @UnsupportedAppUsage
+ public final int programLength; // Length of the APF program in bytes
+ /** @hide */
+ @UnsupportedAppUsage
+ public final int flags; // Bitfield compound of FLAG_* constants
+
+ private ApfProgramEvent(long lifetime, long actualLifetime, int filteredRas, int currentRas,
+ int programLength, int flags) {
+ this.lifetime = lifetime;
+ this.actualLifetime = actualLifetime;
+ this.filteredRas = filteredRas;
+ this.currentRas = currentRas;
+ this.programLength = programLength;
+ this.flags = flags;
+ }
+
+ private ApfProgramEvent(Parcel in) {
+ this.lifetime = in.readLong();
+ this.actualLifetime = in.readLong();
+ this.filteredRas = in.readInt();
+ this.currentRas = in.readInt();
+ this.programLength = in.readInt();
+ this.flags = in.readInt();
+ }
+
+ /**
+ * Utility to create an instance of {@link ApfProgramEvent}.
+ */
+ public static final class Builder {
+ private long mLifetime;
+ private long mActualLifetime;
+ private int mFilteredRas;
+ private int mCurrentRas;
+ private int mProgramLength;
+ private int mFlags;
+
+ /**
+ * Set the maximum computed lifetime of the program in seconds.
+ */
+ @NonNull
+ public Builder setLifetime(long lifetime) {
+ mLifetime = lifetime;
+ return this;
+ }
+
+ /**
+ * Set the effective program lifetime in seconds.
+ */
+ @NonNull
+ public Builder setActualLifetime(long lifetime) {
+ mActualLifetime = lifetime;
+ return this;
+ }
+
+ /**
+ * Set the number of RAs filtered by the APF program.
+ */
+ @NonNull
+ public Builder setFilteredRas(int filteredRas) {
+ mFilteredRas = filteredRas;
+ return this;
+ }
+
+ /**
+ * Set the total number of current RAs at generation time.
+ */
+ @NonNull
+ public Builder setCurrentRas(int currentRas) {
+ mCurrentRas = currentRas;
+ return this;
+ }
+
+ /**
+ * Set the length of the APF program in bytes.
+ */
+ @NonNull
+ public Builder setProgramLength(int programLength) {
+ mProgramLength = programLength;
+ return this;
+ }
+
+ /**
+ * Set the flags describing what an Apf program filters.
+ */
+ @NonNull
+ public Builder setFlags(boolean hasIPv4, boolean multicastFilterOn) {
+ mFlags = flagsFor(hasIPv4, multicastFilterOn);
+ return this;
+ }
+
+ /**
+ * Build a new {@link ApfProgramEvent}.
+ */
+ @NonNull
+ public ApfProgramEvent build() {
+ return new ApfProgramEvent(mLifetime, mActualLifetime, mFilteredRas, mCurrentRas,
+ mProgramLength, mFlags);
+ }
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(lifetime);
+ out.writeLong(actualLifetime);
+ out.writeInt(filteredRas);
+ out.writeInt(currentRas);
+ out.writeInt(programLength);
+ out.writeInt(this.flags);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ String lifetimeString = (lifetime < Long.MAX_VALUE) ? lifetime + "s" : "forever";
+ return String.format("ApfProgramEvent(%d/%d RAs %dB %ds/%s %s)", filteredRas, currentRas,
+ programLength, actualLifetime, lifetimeString, namesOf(flags));
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(obj.getClass().equals(ApfProgramEvent.class))) return false;
+ final ApfProgramEvent other = (ApfProgramEvent) obj;
+ return lifetime == other.lifetime
+ && actualLifetime == other.actualLifetime
+ && filteredRas == other.filteredRas
+ && currentRas == other.currentRas
+ && programLength == other.programLength
+ && flags == other.flags;
+ }
+
+ /** @hide */
+ public static final @android.annotation.NonNull Parcelable.Creator<ApfProgramEvent> CREATOR
+ = new Parcelable.Creator<ApfProgramEvent>() {
+ public ApfProgramEvent createFromParcel(Parcel in) {
+ return new ApfProgramEvent(in);
+ }
+
+ public ApfProgramEvent[] newArray(int size) {
+ return new ApfProgramEvent[size];
+ }
+ };
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public static @Flags int flagsFor(boolean hasIPv4, boolean multicastFilterOn) {
+ int bitfield = 0;
+ if (hasIPv4) {
+ bitfield |= (1 << FLAG_HAS_IPV4_ADDRESS);
+ }
+ if (multicastFilterOn) {
+ bitfield |= (1 << FLAG_MULTICAST_FILTER_ON);
+ }
+ return bitfield;
+ }
+
+ private static String namesOf(@Flags int bitfield) {
+ List<String> names = new ArrayList<>(Integer.bitCount(bitfield));
+ BitSet set = BitSet.valueOf(new long[]{bitfield & Integer.MAX_VALUE});
+ // Only iterate over flag bits which are set.
+ for (int bit = set.nextSetBit(0); bit >= 0; bit = set.nextSetBit(bit+1)) {
+ names.add(Decoder.constants.get(bit));
+ }
+ return TextUtils.join("|", names);
+ }
+
+ final static class Decoder {
+ static final SparseArray<String> constants =
+ MessageUtils.findMessageNames(
+ new Class[]{ApfProgramEvent.class}, new String[]{"FLAG_"});
+ }
+}
diff --git a/android/net/metrics/ApfStats.java b/android/net/metrics/ApfStats.java
new file mode 100644
index 0000000..b963777
--- /dev/null
+++ b/android/net/metrics/ApfStats.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2016 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.metrics;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * An event logged for an interface with APF capabilities when its IpClient state machine exits.
+ * {@hide}
+ */
+@SystemApi
+@TestApi
+public final class ApfStats implements IpConnectivityLog.Event {
+
+ /**
+ * time interval in milliseconds these stastistics covers.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public final long durationMs;
+ /**
+ * number of received RAs.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public final int receivedRas;
+ /**
+ * number of received RAs matching a known RA.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public final int matchingRas;
+ /**
+ * number of received RAs ignored due to the MAX_RAS limit.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public final int droppedRas;
+ /**
+ * number of received RAs with a minimum lifetime of 0.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public final int zeroLifetimeRas;
+ /**
+ * number of received RAs that could not be parsed.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public final int parseErrors;
+ /**
+ * number of APF program updates from receiving RAs.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public final int programUpdates;
+ /**
+ * total number of APF program updates.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public final int programUpdatesAll;
+ /**
+ * number of APF program updates from allowing multicast traffic.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public final int programUpdatesAllowingMulticast;
+ /**
+ * maximum APF program size advertised by hardware.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public final int maxProgramSize;
+
+ private ApfStats(Parcel in) {
+ this.durationMs = in.readLong();
+ this.receivedRas = in.readInt();
+ this.matchingRas = in.readInt();
+ this.droppedRas = in.readInt();
+ this.zeroLifetimeRas = in.readInt();
+ this.parseErrors = in.readInt();
+ this.programUpdates = in.readInt();
+ this.programUpdatesAll = in.readInt();
+ this.programUpdatesAllowingMulticast = in.readInt();
+ this.maxProgramSize = in.readInt();
+ }
+
+ private ApfStats(long durationMs, int receivedRas, int matchingRas, int droppedRas,
+ int zeroLifetimeRas, int parseErrors, int programUpdates, int programUpdatesAll,
+ int programUpdatesAllowingMulticast, int maxProgramSize) {
+ this.durationMs = durationMs;
+ this.receivedRas = receivedRas;
+ this.matchingRas = matchingRas;
+ this.droppedRas = droppedRas;
+ this.zeroLifetimeRas = zeroLifetimeRas;
+ this.parseErrors = parseErrors;
+ this.programUpdates = programUpdates;
+ this.programUpdatesAll = programUpdatesAll;
+ this.programUpdatesAllowingMulticast = programUpdatesAllowingMulticast;
+ this.maxProgramSize = maxProgramSize;
+ }
+
+ /**
+ * Utility to create an instance of {@link ApfStats}.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final class Builder {
+ private long mDurationMs;
+ private int mReceivedRas;
+ private int mMatchingRas;
+ private int mDroppedRas;
+ private int mZeroLifetimeRas;
+ private int mParseErrors;
+ private int mProgramUpdates;
+ private int mProgramUpdatesAll;
+ private int mProgramUpdatesAllowingMulticast;
+ private int mMaxProgramSize;
+
+ /**
+ * Set the time interval in milliseconds these statistics covers.
+ */
+ @NonNull
+ public Builder setDurationMs(long durationMs) {
+ mDurationMs = durationMs;
+ return this;
+ }
+
+ /**
+ * Set the number of received RAs.
+ */
+ @NonNull
+ public Builder setReceivedRas(int receivedRas) {
+ mReceivedRas = receivedRas;
+ return this;
+ }
+
+ /**
+ * Set the number of received RAs matching a known RA.
+ */
+ @NonNull
+ public Builder setMatchingRas(int matchingRas) {
+ mMatchingRas = matchingRas;
+ return this;
+ }
+
+ /**
+ * Set the number of received RAs ignored due to the MAX_RAS limit.
+ */
+ @NonNull
+ public Builder setDroppedRas(int droppedRas) {
+ mDroppedRas = droppedRas;
+ return this;
+ }
+
+ /**
+ * Set the number of received RAs with a minimum lifetime of 0.
+ */
+ @NonNull
+ public Builder setZeroLifetimeRas(int zeroLifetimeRas) {
+ mZeroLifetimeRas = zeroLifetimeRas;
+ return this;
+ }
+
+ /**
+ * Set the number of received RAs that could not be parsed.
+ */
+ @NonNull
+ public Builder setParseErrors(int parseErrors) {
+ mParseErrors = parseErrors;
+ return this;
+ }
+
+ /**
+ * Set the number of APF program updates from receiving RAs.
+ */
+ @NonNull
+ public Builder setProgramUpdates(int programUpdates) {
+ mProgramUpdates = programUpdates;
+ return this;
+ }
+
+ /**
+ * Set the total number of APF program updates.
+ */
+ @NonNull
+ public Builder setProgramUpdatesAll(int programUpdatesAll) {
+ mProgramUpdatesAll = programUpdatesAll;
+ return this;
+ }
+
+ /**
+ * Set the number of APF program updates from allowing multicast traffic.
+ */
+ @NonNull
+ public Builder setProgramUpdatesAllowingMulticast(int programUpdatesAllowingMulticast) {
+ mProgramUpdatesAllowingMulticast = programUpdatesAllowingMulticast;
+ return this;
+ }
+
+ /**
+ * Set the maximum APF program size advertised by hardware.
+ */
+ @NonNull
+ public Builder setMaxProgramSize(int maxProgramSize) {
+ mMaxProgramSize = maxProgramSize;
+ return this;
+ }
+
+ /**
+ * Create a new {@link ApfStats}.
+ */
+ @NonNull
+ public ApfStats build() {
+ return new ApfStats(mDurationMs, mReceivedRas, mMatchingRas, mDroppedRas,
+ mZeroLifetimeRas, mParseErrors, mProgramUpdates, mProgramUpdatesAll,
+ mProgramUpdatesAllowingMulticast, mMaxProgramSize);
+ }
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(durationMs);
+ out.writeInt(receivedRas);
+ out.writeInt(matchingRas);
+ out.writeInt(droppedRas);
+ out.writeInt(zeroLifetimeRas);
+ out.writeInt(parseErrors);
+ out.writeInt(programUpdates);
+ out.writeInt(programUpdatesAll);
+ out.writeInt(programUpdatesAllowingMulticast);
+ out.writeInt(maxProgramSize);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder("ApfStats(")
+ .append(String.format("%dms ", durationMs))
+ .append(String.format("%dB RA: {", maxProgramSize))
+ .append(String.format("%d received, ", receivedRas))
+ .append(String.format("%d matching, ", matchingRas))
+ .append(String.format("%d dropped, ", droppedRas))
+ .append(String.format("%d zero lifetime, ", zeroLifetimeRas))
+ .append(String.format("%d parse errors}, ", parseErrors))
+ .append(String.format("updates: {all: %d, RAs: %d, allow multicast: %d})",
+ programUpdatesAll, programUpdates, programUpdatesAllowingMulticast))
+ .toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(obj.getClass().equals(ApfStats.class))) return false;
+ final ApfStats other = (ApfStats) obj;
+ return durationMs == other.durationMs
+ && receivedRas == other.receivedRas
+ && matchingRas == other.matchingRas
+ && droppedRas == other.droppedRas
+ && zeroLifetimeRas == other.zeroLifetimeRas
+ && parseErrors == other.parseErrors
+ && programUpdates == other.programUpdates
+ && programUpdatesAll == other.programUpdatesAll
+ && programUpdatesAllowingMulticast == other.programUpdatesAllowingMulticast
+ && maxProgramSize == other.maxProgramSize;
+ }
+
+ /** @hide */
+ public static final @android.annotation.NonNull Parcelable.Creator<ApfStats> CREATOR = new Parcelable.Creator<ApfStats>() {
+ public ApfStats createFromParcel(Parcel in) {
+ return new ApfStats(in);
+ }
+
+ public ApfStats[] newArray(int size) {
+ return new ApfStats[size];
+ }
+ };
+}
diff --git a/android/net/metrics/ConnectStats.java b/android/net/metrics/ConnectStats.java
new file mode 100644
index 0000000..b320b75
--- /dev/null
+++ b/android/net/metrics/ConnectStats.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2016 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.metrics;
+
+import android.net.NetworkCapabilities;
+import android.system.OsConstants;
+import android.util.IntArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.util.BitUtils;
+import com.android.internal.util.TokenBucket;
+
+/**
+ * A class that aggregates connect() statistics.
+ * {@hide}
+ */
+public class ConnectStats {
+ private final static int EALREADY = OsConstants.EALREADY;
+ private final static int EINPROGRESS = OsConstants.EINPROGRESS;
+
+ /** Network id of the network associated with the event, or 0 if unspecified. */
+ public final int netId;
+ /** Transports of the network associated with the event, as defined in NetworkCapabilities. */
+ public final long transports;
+ /** How many events resulted in a given errno. */
+ public final SparseIntArray errnos = new SparseIntArray();
+ /** Latencies of successful blocking connects. TODO: add non-blocking connects latencies. */
+ public final IntArray latencies = new IntArray();
+ /** TokenBucket for rate limiting latency recording. */
+ public final TokenBucket mLatencyTb;
+ /** Maximum number of latency values recorded. */
+ public final int mMaxLatencyRecords;
+ /** Total count of events */
+ public int eventCount = 0;
+ /** Total count of successful connects. */
+ public int connectCount = 0;
+ /** Total count of successful connects done in blocking mode. */
+ public int connectBlockingCount = 0;
+ /** Total count of successful connects with IPv6 socket address. */
+ public int ipv6ConnectCount = 0;
+
+ public ConnectStats(int netId, long transports, TokenBucket tb, int maxLatencyRecords) {
+ this.netId = netId;
+ this.transports = transports;
+ mLatencyTb = tb;
+ mMaxLatencyRecords = maxLatencyRecords;
+ }
+
+ boolean addEvent(int errno, int latencyMs, String ipAddr) {
+ eventCount++;
+ if (isSuccess(errno)) {
+ countConnect(errno, ipAddr);
+ countLatency(errno, latencyMs);
+ return true;
+ } else {
+ countError(errno);
+ return false;
+ }
+ }
+
+ private void countConnect(int errno, String ipAddr) {
+ connectCount++;
+ if (!isNonBlocking(errno)) {
+ connectBlockingCount++;
+ }
+ if (isIPv6(ipAddr)) {
+ ipv6ConnectCount++;
+ }
+ }
+
+ private void countLatency(int errno, int ms) {
+ if (isNonBlocking(errno)) {
+ // Ignore connect() on non-blocking sockets
+ return;
+ }
+ if (!mLatencyTb.get()) {
+ // Rate limited
+ return;
+ }
+ if (latencies.size() >= mMaxLatencyRecords) {
+ // Hard limit the total number of latency measurements.
+ return;
+ }
+ latencies.add(ms);
+ }
+
+ private void countError(int errno) {
+ final int newcount = errnos.get(errno, 0) + 1;
+ errnos.put(errno, newcount);
+ }
+
+ private static boolean isSuccess(int errno) {
+ return (errno == 0) || isNonBlocking(errno);
+ }
+
+ static boolean isNonBlocking(int errno) {
+ // On non-blocking TCP sockets, connect() immediately returns EINPROGRESS.
+ // On non-blocking TCP sockets that are connecting, connect() immediately returns EALREADY.
+ return (errno == EINPROGRESS) || (errno == EALREADY);
+ }
+
+ private static boolean isIPv6(String ipAddr) {
+ return ipAddr.contains(":");
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder =
+ new StringBuilder("ConnectStats(").append("netId=").append(netId).append(", ");
+ for (int t : BitUtils.unpackBits(transports)) {
+ builder.append(NetworkCapabilities.transportNameOf(t)).append(", ");
+ }
+ builder.append(String.format("%d events, ", eventCount));
+ builder.append(String.format("%d success, ", connectCount));
+ builder.append(String.format("%d blocking, ", connectBlockingCount));
+ builder.append(String.format("%d IPv6 dst", ipv6ConnectCount));
+ for (int i = 0; i < errnos.size(); i++) {
+ String errno = OsConstants.errnoName(errnos.keyAt(i));
+ int count = errnos.valueAt(i);
+ builder.append(String.format(", %s: %d", errno, count));
+ }
+ return builder.append(")").toString();
+ }
+}
diff --git a/android/net/metrics/DefaultNetworkEvent.java b/android/net/metrics/DefaultNetworkEvent.java
new file mode 100644
index 0000000..6f383b4
--- /dev/null
+++ b/android/net/metrics/DefaultNetworkEvent.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2016 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.metrics;
+
+import static android.net.ConnectivityManager.NETID_UNSET;
+
+import android.net.NetworkCapabilities;
+
+import com.android.internal.util.BitUtils;
+
+import java.util.StringJoiner;
+
+/**
+ * An event recorded by ConnectivityService when there is a change in the default network.
+ * {@hide}
+ */
+public class DefaultNetworkEvent {
+
+ // The creation time in milliseconds of this DefaultNetworkEvent.
+ public final long creationTimeMs;
+ // The network ID of the network or NETID_UNSET if none.
+ public int netId = NETID_UNSET;
+ // The list of transport types, as defined in NetworkCapabilities.java.
+ public int transports;
+ // The list of transport types of the last previous default network.
+ public int previousTransports;
+ // Whether the network has IPv4/IPv6 connectivity.
+ public boolean ipv4;
+ public boolean ipv6;
+ // The initial network score when this network became the default network.
+ public int initialScore;
+ // The initial network score when this network stopped being the default network.
+ public int finalScore;
+ // The total duration in milliseconds this network was the default network.
+ public long durationMs;
+ // The total duration in milliseconds this network was the default network and was validated.
+ public long validatedMs;
+
+ public DefaultNetworkEvent(long timeMs) {
+ creationTimeMs = timeMs;
+ }
+
+ /** Update the durationMs of this DefaultNetworkEvent for the given current time. */
+ public void updateDuration(long timeMs) {
+ durationMs = timeMs - creationTimeMs;
+ }
+
+ @Override
+ public String toString() {
+ StringJoiner j = new StringJoiner(", ", "DefaultNetworkEvent(", ")");
+ j.add("netId=" + netId);
+ for (int t : BitUtils.unpackBits(transports)) {
+ j.add(NetworkCapabilities.transportNameOf(t));
+ }
+ j.add("ip=" + ipSupport());
+ if (initialScore > 0) {
+ j.add("initial_score=" + initialScore);
+ }
+ if (finalScore > 0) {
+ j.add("final_score=" + finalScore);
+ }
+ j.add(String.format("duration=%.0fs", durationMs / 1000.0));
+ j.add(String.format("validation=%04.1f%%", (validatedMs * 100.0) / durationMs));
+ return j.toString();
+ }
+
+ private String ipSupport() {
+ if (ipv4 && ipv6) {
+ return "IPv4v6";
+ }
+ if (ipv6) {
+ return "IPv6";
+ }
+ if (ipv4) {
+ return "IPv4";
+ }
+ return "NONE";
+ }
+}
diff --git a/android/net/metrics/DhcpClientEvent.java b/android/net/metrics/DhcpClientEvent.java
new file mode 100644
index 0000000..2fed736
--- /dev/null
+++ b/android/net/metrics/DhcpClientEvent.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2016 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.metrics;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * An event recorded when a DhcpClient state machine transitions to a new state.
+ * {@hide}
+ */
+@SystemApi
+@TestApi
+public final class DhcpClientEvent implements IpConnectivityLog.Event {
+
+ // Names for recording DhcpClient pseudo-state transitions.
+
+ /** @hide */
+ public final String msg;
+ /** @hide */
+ public final int durationMs;
+
+ @UnsupportedAppUsage
+ private DhcpClientEvent(String msg, int durationMs) {
+ this.msg = msg;
+ this.durationMs = durationMs;
+ }
+
+ private DhcpClientEvent(Parcel in) {
+ this.msg = in.readString();
+ this.durationMs = in.readInt();
+ }
+
+ /**
+ * Utility to create an instance of {@link ApfProgramEvent}.
+ */
+ public static final class Builder {
+ private String mMsg;
+ private int mDurationMs;
+
+ /**
+ * Set the message of the event.
+ */
+ @NonNull
+ public Builder setMsg(String msg) {
+ mMsg = msg;
+ return this;
+ }
+
+ /**
+ * Set the duration of the event in milliseconds.
+ */
+ @NonNull
+ public Builder setDurationMs(int durationMs) {
+ mDurationMs = durationMs;
+ return this;
+ }
+
+ /**
+ * Create a new {@link DhcpClientEvent}.
+ */
+ @NonNull
+ public DhcpClientEvent build() {
+ return new DhcpClientEvent(mMsg, mDurationMs);
+ }
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(msg);
+ out.writeInt(durationMs);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("DhcpClientEvent(%s, %dms)", msg, durationMs);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(obj.getClass().equals(DhcpClientEvent.class))) return false;
+ final DhcpClientEvent other = (DhcpClientEvent) obj;
+ return TextUtils.equals(msg, other.msg)
+ && durationMs == other.durationMs;
+ }
+
+ /** @hide */
+ public static final @android.annotation.NonNull Parcelable.Creator<DhcpClientEvent> CREATOR
+ = new Parcelable.Creator<DhcpClientEvent>() {
+ public DhcpClientEvent createFromParcel(Parcel in) {
+ return new DhcpClientEvent(in);
+ }
+
+ public DhcpClientEvent[] newArray(int size) {
+ return new DhcpClientEvent[size];
+ }
+ };
+}
diff --git a/android/net/metrics/DhcpErrorEvent.java b/android/net/metrics/DhcpErrorEvent.java
new file mode 100644
index 0000000..8760004
--- /dev/null
+++ b/android/net/metrics/DhcpErrorEvent.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2016 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.metrics;
+
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseArray;
+
+import com.android.internal.util.MessageUtils;
+
+/**
+ * Event class used to record error events when parsing DHCP response packets.
+ * {@hide}
+ */
+@SystemApi
+@TestApi
+public final class DhcpErrorEvent implements IpConnectivityLog.Event {
+ public static final int L2_ERROR = 1;
+ public static final int L3_ERROR = 2;
+ public static final int L4_ERROR = 3;
+ public static final int DHCP_ERROR = 4;
+ public static final int MISC_ERROR = 5;
+
+ // error code byte format (MSB to LSB):
+ // byte 0: error type
+ // byte 1: error subtype
+ // byte 2: unused
+ // byte 3: optional code
+ /** @hide */
+ public final int errorCode;
+
+ private static final int L2_ERROR_TYPE = L2_ERROR << 8;
+ private static final int L3_ERROR_TYPE = L3_ERROR << 8;
+ private static final int L4_ERROR_TYPE = L4_ERROR << 8;
+ private static final int DHCP_ERROR_TYPE = DHCP_ERROR << 8;
+ private static final int MISC_ERROR_TYPE = MISC_ERROR << 8;
+
+ public static final int L2_TOO_SHORT = (L2_ERROR_TYPE | 0x1) << 16;
+ public static final int L2_WRONG_ETH_TYPE = (L2_ERROR_TYPE | 0x2) << 16;
+
+ public static final int L3_TOO_SHORT = (L3_ERROR_TYPE | 0x1) << 16;
+ public static final int L3_NOT_IPV4 = (L3_ERROR_TYPE | 0x2) << 16;
+ public static final int L3_INVALID_IP = (L3_ERROR_TYPE | 0x3) << 16;
+
+ public static final int L4_NOT_UDP = (L4_ERROR_TYPE | 0x1) << 16;
+ public static final int L4_WRONG_PORT = (L4_ERROR_TYPE | 0x2) << 16;
+
+ public static final int BOOTP_TOO_SHORT = (DHCP_ERROR_TYPE | 0x1) << 16;
+ public static final int DHCP_BAD_MAGIC_COOKIE = (DHCP_ERROR_TYPE | 0x2) << 16;
+ public static final int DHCP_INVALID_OPTION_LENGTH = (DHCP_ERROR_TYPE | 0x3) << 16;
+ public static final int DHCP_NO_MSG_TYPE = (DHCP_ERROR_TYPE | 0x4) << 16;
+ public static final int DHCP_UNKNOWN_MSG_TYPE = (DHCP_ERROR_TYPE | 0x5) << 16;
+ public static final int DHCP_NO_COOKIE = (DHCP_ERROR_TYPE | 0x6) << 16;
+
+ public static final int BUFFER_UNDERFLOW = (MISC_ERROR_TYPE | 0x1) << 16;
+ public static final int RECEIVE_ERROR = (MISC_ERROR_TYPE | 0x2) << 16;
+ public static final int PARSING_ERROR = (MISC_ERROR_TYPE | 0x3) << 16;
+
+ public DhcpErrorEvent(int errorCode) {
+ this.errorCode = errorCode;
+ }
+
+ private DhcpErrorEvent(Parcel in) {
+ this.errorCode = in.readInt();
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(errorCode);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ public static final @android.annotation.NonNull Parcelable.Creator<DhcpErrorEvent> CREATOR
+ = new Parcelable.Creator<DhcpErrorEvent>() {
+ public DhcpErrorEvent createFromParcel(Parcel in) {
+ return new DhcpErrorEvent(in);
+ }
+
+ public DhcpErrorEvent[] newArray(int size) {
+ return new DhcpErrorEvent[size];
+ }
+ };
+
+ public static int errorCodeWithOption(int errorCode, int option) {
+ return (0xFFFF0000 & errorCode) | (0xFF & option);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("DhcpErrorEvent(%s)", Decoder.constants.get(errorCode));
+ }
+
+ final static class Decoder {
+ static final SparseArray<String> constants = MessageUtils.findMessageNames(
+ new Class[]{DhcpErrorEvent.class},
+ new String[]{"L2_", "L3_", "L4_", "BOOTP_", "DHCP_", "BUFFER_", "RECEIVE_",
+ "PARSING_"});
+ }
+}
diff --git a/android/net/metrics/DnsEvent.java b/android/net/metrics/DnsEvent.java
new file mode 100644
index 0000000..5aa705b
--- /dev/null
+++ b/android/net/metrics/DnsEvent.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2016 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.metrics;
+
+import android.net.NetworkCapabilities;
+
+import com.android.internal.util.BitUtils;
+
+import java.util.Arrays;
+
+/**
+ * A batch of DNS events recorded by NetdEventListenerService for a specific network.
+ * {@hide}
+ */
+final public class DnsEvent {
+
+ private static final int SIZE_LIMIT = 20000;
+
+ // Network id of the network associated with the event, or 0 if unspecified.
+ public final int netId;
+ // Transports of the network associated with the event, as defined in NetworkCapabilities.
+ // It is the caller responsability to ensure the value of transports does not change between
+ // calls to addResult.
+ public final long transports;
+ // The number of DNS queries recorded. Queries are stored in the structure-of-array style where
+ // the eventTypes, returnCodes, and latenciesMs arrays have the same length and the i-th event
+ // is spread across the three array at position i.
+ public int eventCount;
+ // The number of successful DNS queries recorded.
+ public int successCount;
+ // The types of DNS queries as defined in INetdEventListener.
+ public byte[] eventTypes;
+ // Current getaddrinfo codes go from 1 to EAI_MAX = 15. gethostbyname returns errno, but there
+ // are fewer than 255 errno values. So we store the result code in a byte as well.
+ public byte[] returnCodes;
+ // Latencies in milliseconds of queries, stored as ints.
+ public int[] latenciesMs;
+
+ public DnsEvent(int netId, long transports, int initialCapacity) {
+ this.netId = netId;
+ this.transports = transports;
+ eventTypes = new byte[initialCapacity];
+ returnCodes = new byte[initialCapacity];
+ latenciesMs = new int[initialCapacity];
+ }
+
+ boolean addResult(byte eventType, byte returnCode, int latencyMs) {
+ boolean isSuccess = (returnCode == 0);
+ if (eventCount >= SIZE_LIMIT) {
+ // TODO: implement better rate limiting that does not biases metrics.
+ return isSuccess;
+ }
+ if (eventCount == eventTypes.length) {
+ resize((int) (1.4 * eventCount));
+ }
+ eventTypes[eventCount] = eventType;
+ returnCodes[eventCount] = returnCode;
+ latenciesMs[eventCount] = latencyMs;
+ eventCount++;
+ if (isSuccess) {
+ successCount++;
+ }
+ return isSuccess;
+ }
+
+ public void resize(int newLength) {
+ eventTypes = Arrays.copyOf(eventTypes, newLength);
+ returnCodes = Arrays.copyOf(returnCodes, newLength);
+ latenciesMs = Arrays.copyOf(latenciesMs, newLength);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder =
+ new StringBuilder("DnsEvent(").append("netId=").append(netId).append(", ");
+ for (int t : BitUtils.unpackBits(transports)) {
+ builder.append(NetworkCapabilities.transportNameOf(t)).append(", ");
+ }
+ builder.append(String.format("%d events, ", eventCount));
+ builder.append(String.format("%d success)", successCount));
+ return builder.toString();
+ }
+}
diff --git a/android/net/metrics/IpConnectivityLog.java b/android/net/metrics/IpConnectivityLog.java
new file mode 100644
index 0000000..680c015
--- /dev/null
+++ b/android/net/metrics/IpConnectivityLog.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2016 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.metrics;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.net.ConnectivityMetricsEvent;
+import android.net.IIpConnectivityMetrics;
+import android.net.Network;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.BitUtils;
+
+/**
+ * Class for logging IpConnectvity events with IpConnectivityMetrics
+ * {@hide}
+ */
+@SystemApi
+@TestApi
+public class IpConnectivityLog {
+ private static final String TAG = IpConnectivityLog.class.getSimpleName();
+ private static final boolean DBG = false;
+
+ /** @hide */
+ public static final String SERVICE_NAME = "connmetrics";
+ @NonNull
+ private IIpConnectivityMetrics mService;
+
+ /**
+ * An event to be logged.
+ */
+ public interface Event extends Parcelable {}
+
+ /** @hide */
+ @SystemApi
+ @TestApi
+ public IpConnectivityLog() {
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public IpConnectivityLog(@NonNull IIpConnectivityMetrics service) {
+ mService = service;
+ }
+
+ private boolean checkLoggerService() {
+ if (mService != null) {
+ return true;
+ }
+ final IIpConnectivityMetrics service =
+ IIpConnectivityMetrics.Stub.asInterface(ServiceManager.getService(SERVICE_NAME));
+ if (service == null) {
+ return false;
+ }
+ // Two threads racing here will write the same pointer because getService
+ // is idempotent once MetricsLoggerService is initialized.
+ mService = service;
+ return true;
+ }
+
+ /**
+ * Log a ConnectivityMetricsEvent.
+ * @param ev the event to log. If the event timestamp is 0,
+ * the timestamp is set to the current time in milliseconds.
+ * @return true if the event was successfully logged.
+ * @hide
+ */
+ public boolean log(@NonNull ConnectivityMetricsEvent ev) {
+ if (!checkLoggerService()) {
+ if (DBG) {
+ Log.d(TAG, SERVICE_NAME + " service was not ready");
+ }
+ return false;
+ }
+ if (ev.timestamp == 0) {
+ ev.timestamp = System.currentTimeMillis();
+ }
+ try {
+ int left = mService.logEvent(ev);
+ return left >= 0;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error logging event", e);
+ return false;
+ }
+ }
+
+ /**
+ * Log an IpConnectivity event.
+ * @param timestamp is the epoch timestamp of the event in ms.
+ * If the timestamp is 0, the timestamp is set to the current time in milliseconds.
+ * @param data is a Parcelable instance representing the event.
+ * @return true if the event was successfully logged.
+ */
+ public boolean log(long timestamp, @NonNull Event data) {
+ ConnectivityMetricsEvent ev = makeEv(data);
+ ev.timestamp = timestamp;
+ return log(ev);
+ }
+
+ /**
+ * Log an IpConnectivity event.
+ * @param ifname the network interface associated with the event.
+ * @param data is a Parcelable instance representing the event.
+ * @return true if the event was successfully logged.
+ */
+ public boolean log(@NonNull String ifname, @NonNull Event data) {
+ ConnectivityMetricsEvent ev = makeEv(data);
+ ev.ifname = ifname;
+ return log(ev);
+ }
+
+ /**
+ * Log an IpConnectivity event.
+ * @param network the network associated with the event.
+ * @param transports the current transports of the network associated with the event, as defined
+ * in NetworkCapabilities.
+ * @param data is a Parcelable instance representing the event.
+ * @return true if the event was successfully logged.
+ */
+ public boolean log(@NonNull Network network, @NonNull int[] transports, @NonNull Event data) {
+ return log(network.netId, transports, data);
+ }
+
+ /**
+ * Log an IpConnectivity event.
+ * @param netid the id of the network associated with the event.
+ * @param transports the current transports of the network associated with the event, as defined
+ * in NetworkCapabilities.
+ * @param data is a Parcelable instance representing the event.
+ * @return true if the event was successfully logged.
+ */
+ public boolean log(int netid, @NonNull int[] transports, @NonNull Event data) {
+ ConnectivityMetricsEvent ev = makeEv(data);
+ ev.netId = netid;
+ ev.transports = BitUtils.packBits(transports);
+ return log(ev);
+ }
+
+ /**
+ * Log an IpConnectivity event.
+ * @param data is a Parcelable instance representing the event.
+ * @return true if the event was successfully logged.
+ */
+ public boolean log(@NonNull Event data) {
+ return log(makeEv(data));
+ }
+
+ private static ConnectivityMetricsEvent makeEv(Event data) {
+ ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent();
+ ev.data = data;
+ return ev;
+ }
+}
diff --git a/android/net/metrics/IpManagerEvent.java b/android/net/metrics/IpManagerEvent.java
new file mode 100644
index 0000000..ba05c59
--- /dev/null
+++ b/android/net/metrics/IpManagerEvent.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2016 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.metrics;
+
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseArray;
+
+import com.android.internal.util.MessageUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An event recorded by IpClient when IP provisioning completes for a network or
+ * when a network disconnects.
+ * {@hide}
+ */
+@SystemApi
+@TestApi
+public final class IpManagerEvent implements IpConnectivityLog.Event {
+
+ public static final int PROVISIONING_OK = 1;
+ public static final int PROVISIONING_FAIL = 2;
+ public static final int COMPLETE_LIFECYCLE = 3;
+ public static final int ERROR_STARTING_IPV4 = 4;
+ public static final int ERROR_STARTING_IPV6 = 5;
+ public static final int ERROR_STARTING_IPREACHABILITYMONITOR = 6;
+ public static final int ERROR_INVALID_PROVISIONING = 7;
+ public static final int ERROR_INTERFACE_NOT_FOUND = 8;
+
+ /** @hide */
+ @IntDef(value = {
+ PROVISIONING_OK, PROVISIONING_FAIL, COMPLETE_LIFECYCLE,
+ ERROR_STARTING_IPV4, ERROR_STARTING_IPV6, ERROR_STARTING_IPREACHABILITYMONITOR,
+ ERROR_INVALID_PROVISIONING, ERROR_INTERFACE_NOT_FOUND,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EventType {}
+
+ /** @hide */
+ public final @EventType int eventType;
+ /** @hide */
+ public final long durationMs;
+
+ public IpManagerEvent(@EventType int eventType, long duration) {
+ this.eventType = eventType;
+ this.durationMs = duration;
+ }
+
+ private IpManagerEvent(Parcel in) {
+ this.eventType = in.readInt();
+ this.durationMs = in.readLong();
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(eventType);
+ out.writeLong(durationMs);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ public static final @android.annotation.NonNull Parcelable.Creator<IpManagerEvent> CREATOR
+ = new Parcelable.Creator<IpManagerEvent>() {
+ public IpManagerEvent createFromParcel(Parcel in) {
+ return new IpManagerEvent(in);
+ }
+
+ public IpManagerEvent[] newArray(int size) {
+ return new IpManagerEvent[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return String.format("IpManagerEvent(%s, %dms)",
+ Decoder.constants.get(eventType), durationMs);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(obj.getClass().equals(IpManagerEvent.class))) return false;
+ final IpManagerEvent other = (IpManagerEvent) obj;
+ return eventType == other.eventType
+ && durationMs == other.durationMs;
+ }
+
+ final static class Decoder {
+ static final SparseArray<String> constants = MessageUtils.findMessageNames(
+ new Class[]{IpManagerEvent.class},
+ new String[]{"PROVISIONING_", "COMPLETE_", "ERROR_"});
+ }
+}
diff --git a/android/net/metrics/IpReachabilityEvent.java b/android/net/metrics/IpReachabilityEvent.java
new file mode 100644
index 0000000..d4ba294
--- /dev/null
+++ b/android/net/metrics/IpReachabilityEvent.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2016 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.metrics;
+
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseArray;
+
+import com.android.internal.util.MessageUtils;
+
+/**
+ * An event recorded when IpReachabilityMonitor sends a neighbor probe or receives
+ * a neighbor probe result.
+ * {@hide}
+ */
+@SystemApi
+@TestApi
+public final class IpReachabilityEvent implements IpConnectivityLog.Event {
+
+ // Event types.
+ /** A probe forced by IpReachabilityMonitor. */
+ public static final int PROBE = 1 << 8;
+ /** Neighbor unreachable after a forced probe. */
+ public static final int NUD_FAILED = 2 << 8;
+ /** Neighbor unreachable after a forced probe, IP provisioning is also lost. */
+ public static final int PROVISIONING_LOST = 3 << 8;
+ /** Neighbor unreachable notification from kernel. */
+ public static final int NUD_FAILED_ORGANIC = 4 << 8;
+ /** Neighbor unreachable notification from kernel, IP provisioning is also lost. */
+ public static final int PROVISIONING_LOST_ORGANIC = 5 << 8;
+
+ // eventType byte format (MSB to LSB):
+ // byte 0: unused
+ // byte 1: unused
+ // byte 2: type of event: PROBE, NUD_FAILED, PROVISIONING_LOST
+ // byte 3: when byte 2 == PROBE, errno code from RTNetlink or IpReachabilityMonitor.
+ /** @hide */
+ public final int eventType;
+
+ public IpReachabilityEvent(int eventType) {
+ this.eventType = eventType;
+ }
+
+ private IpReachabilityEvent(Parcel in) {
+ this.eventType = in.readInt();
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(eventType);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ public static final @android.annotation.NonNull Parcelable.Creator<IpReachabilityEvent> CREATOR
+ = new Parcelable.Creator<IpReachabilityEvent>() {
+ public IpReachabilityEvent createFromParcel(Parcel in) {
+ return new IpReachabilityEvent(in);
+ }
+
+ public IpReachabilityEvent[] newArray(int size) {
+ return new IpReachabilityEvent[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ int hi = eventType & 0xff00;
+ int lo = eventType & 0x00ff;
+ String eventName = Decoder.constants.get(hi);
+ return String.format("IpReachabilityEvent(%s:%02x)", eventName, lo);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(obj.getClass().equals(IpReachabilityEvent.class))) return false;
+ final IpReachabilityEvent other = (IpReachabilityEvent) obj;
+ return eventType == other.eventType;
+ }
+
+ final static class Decoder {
+ static final SparseArray<String> constants =
+ MessageUtils.findMessageNames(new Class[]{IpReachabilityEvent.class},
+ new String[]{"PROBE", "PROVISIONING_", "NUD_"});
+ }
+}
diff --git a/android/net/metrics/NetworkEvent.java b/android/net/metrics/NetworkEvent.java
new file mode 100644
index 0000000..0c57ec6
--- /dev/null
+++ b/android/net/metrics/NetworkEvent.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2016 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.metrics;
+
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseArray;
+
+import com.android.internal.util.MessageUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * {@hide}
+ */
+@SystemApi
+@TestApi
+public final class NetworkEvent implements IpConnectivityLog.Event {
+
+ public static final int NETWORK_CONNECTED = 1;
+ public static final int NETWORK_VALIDATED = 2;
+ public static final int NETWORK_VALIDATION_FAILED = 3;
+ public static final int NETWORK_CAPTIVE_PORTAL_FOUND = 4;
+ public static final int NETWORK_LINGER = 5;
+ public static final int NETWORK_UNLINGER = 6;
+ public static final int NETWORK_DISCONNECTED = 7;
+
+ public static final int NETWORK_FIRST_VALIDATION_SUCCESS = 8;
+ public static final int NETWORK_REVALIDATION_SUCCESS = 9;
+ public static final int NETWORK_FIRST_VALIDATION_PORTAL_FOUND = 10;
+ public static final int NETWORK_REVALIDATION_PORTAL_FOUND = 11;
+
+ public static final int NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND = 12;
+
+ public static final int NETWORK_PARTIAL_CONNECTIVITY = 13;
+
+ /** @hide */
+ @IntDef(value = {
+ NETWORK_CONNECTED,
+ NETWORK_VALIDATED,
+ NETWORK_VALIDATION_FAILED,
+ NETWORK_CAPTIVE_PORTAL_FOUND,
+ NETWORK_LINGER,
+ NETWORK_UNLINGER,
+ NETWORK_DISCONNECTED,
+ NETWORK_FIRST_VALIDATION_SUCCESS,
+ NETWORK_REVALIDATION_SUCCESS,
+ NETWORK_FIRST_VALIDATION_PORTAL_FOUND,
+ NETWORK_REVALIDATION_PORTAL_FOUND,
+ NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND,
+ NETWORK_PARTIAL_CONNECTIVITY,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EventType {}
+
+ /** @hide */
+ public final @EventType int eventType;
+ /** @hide */
+ public final long durationMs;
+
+ public NetworkEvent(@EventType int eventType, long durationMs) {
+ this.eventType = eventType;
+ this.durationMs = durationMs;
+ }
+
+ public NetworkEvent(@EventType int eventType) {
+ this(eventType, 0);
+ }
+
+ private NetworkEvent(Parcel in) {
+ eventType = in.readInt();
+ durationMs = in.readLong();
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(eventType);
+ out.writeLong(durationMs);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ public static final @android.annotation.NonNull Parcelable.Creator<NetworkEvent> CREATOR
+ = new Parcelable.Creator<NetworkEvent>() {
+ public NetworkEvent createFromParcel(Parcel in) {
+ return new NetworkEvent(in);
+ }
+
+ public NetworkEvent[] newArray(int size) {
+ return new NetworkEvent[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return String.format("NetworkEvent(%s, %dms)",
+ Decoder.constants.get(eventType), durationMs);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(obj.getClass().equals(NetworkEvent.class))) return false;
+ final NetworkEvent other = (NetworkEvent) obj;
+ return eventType == other.eventType
+ && durationMs == other.durationMs;
+ }
+
+ final static class Decoder {
+ static final SparseArray<String> constants = MessageUtils.findMessageNames(
+ new Class[]{NetworkEvent.class}, new String[]{"NETWORK_"});
+ }
+}
diff --git a/android/net/metrics/NetworkMetrics.java b/android/net/metrics/NetworkMetrics.java
new file mode 100644
index 0000000..66d92c4
--- /dev/null
+++ b/android/net/metrics/NetworkMetrics.java
@@ -0,0 +1,194 @@
+/*
+ * 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 android.net.metrics;
+
+import android.net.NetworkCapabilities;
+
+import com.android.internal.util.BitUtils;
+import com.android.internal.util.TokenBucket;
+
+import java.util.StringJoiner;
+
+/**
+ * A class accumulating network metrics received from Netd regarding dns queries and
+ * connect() calls on a given network.
+ *
+ * This class also accumulates running sums of dns and connect latency stats and
+ * error counts for bug report logging.
+ *
+ * @hide
+ */
+public class NetworkMetrics {
+
+ private static final int INITIAL_DNS_BATCH_SIZE = 100;
+ private static final int CONNECT_LATENCY_MAXIMUM_RECORDS = 20000;
+
+ // The network id of the Android Network.
+ public final int netId;
+ // The transport types bitmap of the Android Network, as defined in NetworkCapabilities.java.
+ public final long transports;
+ // Accumulated metrics for connect events.
+ public final ConnectStats connectMetrics;
+ // Accumulated metrics for dns events.
+ public final DnsEvent dnsMetrics;
+ // Running sums of latencies and error counts for connect and dns events.
+ public final Summary summary;
+ // Running sums of the most recent latencies and error counts for connect and dns events.
+ // Starts null until some events are accumulated.
+ // Allows to collect periodic snapshot of the running summaries for a given network.
+ public Summary pendingSummary;
+
+ public NetworkMetrics(int netId, long transports, TokenBucket tb) {
+ this.netId = netId;
+ this.transports = transports;
+ this.connectMetrics =
+ new ConnectStats(netId, transports, tb, CONNECT_LATENCY_MAXIMUM_RECORDS);
+ this.dnsMetrics = new DnsEvent(netId, transports, INITIAL_DNS_BATCH_SIZE);
+ this.summary = new Summary(netId, transports);
+ }
+
+ /**
+ * Get currently pending Summary statistics, if any, for this NetworkMetrics, merge them
+ * into the long running Summary statistics of this NetworkMetrics, and also clear them.
+ */
+ public Summary getPendingStats() {
+ Summary s = pendingSummary;
+ pendingSummary = null;
+ if (s != null) {
+ summary.merge(s);
+ }
+ return s;
+ }
+
+ /** Accumulate a dns query result reported by netd. */
+ public void addDnsResult(int eventType, int returnCode, int latencyMs) {
+ if (pendingSummary == null) {
+ pendingSummary = new Summary(netId, transports);
+ }
+ boolean isSuccess = dnsMetrics.addResult((byte) eventType, (byte) returnCode, latencyMs);
+ pendingSummary.dnsLatencies.count(latencyMs);
+ pendingSummary.dnsErrorRate.count(isSuccess ? 0 : 1);
+ }
+
+ /** Accumulate a connect query result reported by netd. */
+ public void addConnectResult(int error, int latencyMs, String ipAddr) {
+ if (pendingSummary == null) {
+ pendingSummary = new Summary(netId, transports);
+ }
+ boolean isSuccess = connectMetrics.addEvent(error, latencyMs, ipAddr);
+ pendingSummary.connectErrorRate.count(isSuccess ? 0 : 1);
+ if (ConnectStats.isNonBlocking(error)) {
+ pendingSummary.connectLatencies.count(latencyMs);
+ }
+ }
+
+ /** Accumulate a single netd sock_diag poll result reported by netd. */
+ public void addTcpStatsResult(int sent, int lost, int rttUs, int sentAckDiffMs) {
+ if (pendingSummary == null) {
+ pendingSummary = new Summary(netId, transports);
+ }
+ pendingSummary.tcpLossRate.count(lost, sent);
+ pendingSummary.roundTripTimeUs.count(rttUs);
+ pendingSummary.sentAckTimeDiffenceMs.count(sentAckDiffMs);
+ }
+
+ /** Represents running sums for dns and connect average error counts and average latencies. */
+ public static class Summary {
+
+ public final int netId;
+ public final long transports;
+ // DNS latencies measured in milliseconds.
+ public final Metrics dnsLatencies = new Metrics();
+ // DNS error rate measured in percentage points.
+ public final Metrics dnsErrorRate = new Metrics();
+ // Blocking connect latencies measured in milliseconds.
+ public final Metrics connectLatencies = new Metrics();
+ // Blocking and non blocking connect error rate measured in percentage points.
+ public final Metrics connectErrorRate = new Metrics();
+ // TCP socket packet loss stats collected from Netlink sock_diag.
+ public final Metrics tcpLossRate = new Metrics();
+ // TCP averaged microsecond round-trip-time stats collected from Netlink sock_diag.
+ public final Metrics roundTripTimeUs = new Metrics();
+ // TCP stats collected from Netlink sock_diag that averages millisecond per-socket
+ // differences between last packet sent timestamp and last ack received timestamp.
+ public final Metrics sentAckTimeDiffenceMs = new Metrics();
+
+ public Summary(int netId, long transports) {
+ this.netId = netId;
+ this.transports = transports;
+ }
+
+ void merge(Summary that) {
+ dnsLatencies.merge(that.dnsLatencies);
+ dnsErrorRate.merge(that.dnsErrorRate);
+ connectLatencies.merge(that.connectLatencies);
+ connectErrorRate.merge(that.connectErrorRate);
+ tcpLossRate.merge(that.tcpLossRate);
+ }
+
+ @Override
+ public String toString() {
+ StringJoiner j = new StringJoiner(", ", "{", "}");
+ j.add("netId=" + netId);
+ for (int t : BitUtils.unpackBits(transports)) {
+ j.add(NetworkCapabilities.transportNameOf(t));
+ }
+ j.add(String.format("dns avg=%dms max=%dms err=%.1f%% tot=%d",
+ (int) dnsLatencies.average(), (int) dnsLatencies.max,
+ 100 * dnsErrorRate.average(), dnsErrorRate.count));
+ j.add(String.format("connect avg=%dms max=%dms err=%.1f%% tot=%d",
+ (int) connectLatencies.average(), (int) connectLatencies.max,
+ 100 * connectErrorRate.average(), connectErrorRate.count));
+ j.add(String.format("tcp avg_loss=%.1f%% total_sent=%d total_lost=%d",
+ 100 * tcpLossRate.average(), tcpLossRate.count, (int) tcpLossRate.sum));
+ j.add(String.format("tcp rtt=%dms", (int) (roundTripTimeUs.average() / 1000)));
+ j.add(String.format("tcp sent-ack_diff=%dms", (int) sentAckTimeDiffenceMs.average()));
+ return j.toString();
+ }
+ }
+
+ /** Tracks a running sum and returns the average of a metric. */
+ static class Metrics {
+ public double sum;
+ public double max = Double.MIN_VALUE;
+ public int count;
+
+ void merge(Metrics that) {
+ this.count += that.count;
+ this.sum += that.sum;
+ this.max = Math.max(this.max, that.max);
+ }
+
+ void count(double value) {
+ count(value, 1);
+ }
+
+ void count(double value, int subcount) {
+ count += subcount;
+ sum += value;
+ max = Math.max(max, value);
+ }
+
+ double average() {
+ double a = sum / (double) count;
+ if (Double.isNaN(a)) {
+ a = 0;
+ }
+ return a;
+ }
+ }
+}
diff --git a/android/net/metrics/RaEvent.java b/android/net/metrics/RaEvent.java
new file mode 100644
index 0000000..3fd87c2
--- /dev/null
+++ b/android/net/metrics/RaEvent.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2016 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.metrics;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * An event logged when the APF packet socket receives an RA packet.
+ * {@hide}
+ */
+@SystemApi
+@TestApi
+public final class RaEvent implements IpConnectivityLog.Event {
+
+ private static final long NO_LIFETIME = -1L;
+
+ // Lifetime in seconds of options found in a single RA packet.
+ // When an option is not set, the value of the associated field is -1;
+ /** @hide */
+ public final long routerLifetime;
+ /** @hide */
+ public final long prefixValidLifetime;
+ /** @hide */
+ public final long prefixPreferredLifetime;
+ /** @hide */
+ public final long routeInfoLifetime;
+ /** @hide */
+ public final long rdnssLifetime;
+ /** @hide */
+ public final long dnsslLifetime;
+
+ /** @hide */
+ public RaEvent(long routerLifetime, long prefixValidLifetime, long prefixPreferredLifetime,
+ long routeInfoLifetime, long rdnssLifetime, long dnsslLifetime) {
+ this.routerLifetime = routerLifetime;
+ this.prefixValidLifetime = prefixValidLifetime;
+ this.prefixPreferredLifetime = prefixPreferredLifetime;
+ this.routeInfoLifetime = routeInfoLifetime;
+ this.rdnssLifetime = rdnssLifetime;
+ this.dnsslLifetime = dnsslLifetime;
+ }
+
+ /** @hide */
+ private RaEvent(Parcel in) {
+ routerLifetime = in.readLong();
+ prefixValidLifetime = in.readLong();
+ prefixPreferredLifetime = in.readLong();
+ routeInfoLifetime = in.readLong();
+ rdnssLifetime = in.readLong();
+ dnsslLifetime = in.readLong();
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(routerLifetime);
+ out.writeLong(prefixValidLifetime);
+ out.writeLong(prefixPreferredLifetime);
+ out.writeLong(routeInfoLifetime);
+ out.writeLong(rdnssLifetime);
+ out.writeLong(dnsslLifetime);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder("RaEvent(lifetimes: ")
+ .append(String.format("router=%ds, ", routerLifetime))
+ .append(String.format("prefix_valid=%ds, ", prefixValidLifetime))
+ .append(String.format("prefix_preferred=%ds, ", prefixPreferredLifetime))
+ .append(String.format("route_info=%ds, ", routeInfoLifetime))
+ .append(String.format("rdnss=%ds, ", rdnssLifetime))
+ .append(String.format("dnssl=%ds)", dnsslLifetime))
+ .toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(obj.getClass().equals(RaEvent.class))) return false;
+ final RaEvent other = (RaEvent) obj;
+ return routerLifetime == other.routerLifetime
+ && prefixValidLifetime == other.prefixValidLifetime
+ && prefixPreferredLifetime == other.prefixPreferredLifetime
+ && routeInfoLifetime == other.routeInfoLifetime
+ && rdnssLifetime == other.rdnssLifetime
+ && dnsslLifetime == other.dnsslLifetime;
+ }
+
+ /** @hide */
+ public static final @android.annotation.NonNull Parcelable.Creator<RaEvent> CREATOR = new Parcelable.Creator<RaEvent>() {
+ public RaEvent createFromParcel(Parcel in) {
+ return new RaEvent(in);
+ }
+
+ public RaEvent[] newArray(int size) {
+ return new RaEvent[size];
+ }
+ };
+
+ public static final class Builder {
+
+ long routerLifetime = NO_LIFETIME;
+ long prefixValidLifetime = NO_LIFETIME;
+ long prefixPreferredLifetime = NO_LIFETIME;
+ long routeInfoLifetime = NO_LIFETIME;
+ long rdnssLifetime = NO_LIFETIME;
+ long dnsslLifetime = NO_LIFETIME;
+
+ public Builder() {
+ }
+
+ public @NonNull RaEvent build() {
+ return new RaEvent(routerLifetime, prefixValidLifetime, prefixPreferredLifetime,
+ routeInfoLifetime, rdnssLifetime, dnsslLifetime);
+ }
+
+ public @NonNull Builder updateRouterLifetime(long lifetime) {
+ routerLifetime = updateLifetime(routerLifetime, lifetime);
+ return this;
+ }
+
+ public @NonNull Builder updatePrefixValidLifetime(long lifetime) {
+ prefixValidLifetime = updateLifetime(prefixValidLifetime, lifetime);
+ return this;
+ }
+
+ public @NonNull Builder updatePrefixPreferredLifetime(long lifetime) {
+ prefixPreferredLifetime = updateLifetime(prefixPreferredLifetime, lifetime);
+ return this;
+ }
+
+ public @NonNull Builder updateRouteInfoLifetime(long lifetime) {
+ routeInfoLifetime = updateLifetime(routeInfoLifetime, lifetime);
+ return this;
+ }
+
+ public @NonNull Builder updateRdnssLifetime(long lifetime) {
+ rdnssLifetime = updateLifetime(rdnssLifetime, lifetime);
+ return this;
+ }
+
+ public @NonNull Builder updateDnsslLifetime(long lifetime) {
+ dnsslLifetime = updateLifetime(dnsslLifetime, lifetime);
+ return this;
+ }
+
+ private long updateLifetime(long currentLifetime, long newLifetime) {
+ if (currentLifetime == RaEvent.NO_LIFETIME) {
+ return newLifetime;
+ }
+ return Math.min(currentLifetime, newLifetime);
+ }
+ }
+}
diff --git a/android/net/metrics/ValidationProbeEvent.java b/android/net/metrics/ValidationProbeEvent.java
new file mode 100644
index 0000000..1aaa50d
--- /dev/null
+++ b/android/net/metrics/ValidationProbeEvent.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2016 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.metrics;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseArray;
+
+import com.android.internal.util.MessageUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An event recorded by NetworkMonitor when sending a probe for finding captive portals.
+ * {@hide}
+ */
+@SystemApi
+@TestApi
+public final class ValidationProbeEvent implements IpConnectivityLog.Event {
+
+ public static final int PROBE_DNS = 0;
+ public static final int PROBE_HTTP = 1;
+ public static final int PROBE_HTTPS = 2;
+ public static final int PROBE_PAC = 3;
+ public static final int PROBE_FALLBACK = 4;
+ public static final int PROBE_PRIVDNS = 5;
+
+ public static final int DNS_FAILURE = 0;
+ public static final int DNS_SUCCESS = 1;
+
+ private static final int FIRST_VALIDATION = 1 << 8;
+ private static final int REVALIDATION = 2 << 8;
+
+ /** @hide */
+ @IntDef(value = {DNS_FAILURE, DNS_SUCCESS})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ReturnCode {}
+
+ /** @hide */
+ public final long durationMs;
+ // probeType byte format (MSB to LSB):
+ // byte 0: unused
+ // byte 1: unused
+ // byte 2: 0 = UNKNOWN, 1 = FIRST_VALIDATION, 2 = REVALIDATION
+ // byte 3: PROBE_* constant
+ /** @hide */
+ public final int probeType;
+ /** @hide */
+ public final @ReturnCode int returnCode;
+
+ private ValidationProbeEvent(long durationMs, int probeType, int returnCode) {
+ this.durationMs = durationMs;
+ this.probeType = probeType;
+ this.returnCode = returnCode;
+ }
+
+ private ValidationProbeEvent(Parcel in) {
+ durationMs = in.readLong();
+ probeType = in.readInt();
+ returnCode = in.readInt();
+ }
+
+ /**
+ * Utility to create an instance of {@link ValidationProbeEvent}.
+ */
+ public static final class Builder {
+ private long mDurationMs;
+ private int mProbeType;
+ private int mReturnCode;
+
+ /**
+ * Set the duration of the probe in milliseconds.
+ */
+ @NonNull
+ public Builder setDurationMs(long durationMs) {
+ mDurationMs = durationMs;
+ return this;
+ }
+
+ /**
+ * Set the probe type based on whether it was the first validation.
+ */
+ @NonNull
+ public Builder setProbeType(int probeType, boolean firstValidation) {
+ mProbeType = makeProbeType(probeType, firstValidation);
+ return this;
+ }
+
+ /**
+ * Set the return code of the probe.
+ */
+ @NonNull
+ public Builder setReturnCode(int returnCode) {
+ mReturnCode = returnCode;
+ return this;
+ }
+
+ /**
+ * Create a new {@link ValidationProbeEvent}.
+ */
+ @NonNull
+ public ValidationProbeEvent build() {
+ return new ValidationProbeEvent(mDurationMs, mProbeType, mReturnCode);
+ }
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(durationMs);
+ out.writeInt(probeType);
+ out.writeInt(returnCode);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ public static final @android.annotation.NonNull Parcelable.Creator<ValidationProbeEvent> CREATOR
+ = new Parcelable.Creator<ValidationProbeEvent>() {
+ public ValidationProbeEvent createFromParcel(Parcel in) {
+ return new ValidationProbeEvent(in);
+ }
+
+ public ValidationProbeEvent[] newArray(int size) {
+ return new ValidationProbeEvent[size];
+ }
+ };
+
+ private static int makeProbeType(int probeType, boolean firstValidation) {
+ return (probeType & 0xff) | (firstValidation ? FIRST_VALIDATION : REVALIDATION);
+ }
+
+ /**
+ * Get the name of a probe specified by its probe type.
+ */
+ public static @NonNull String getProbeName(int probeType) {
+ return Decoder.constants.get(probeType & 0xff, "PROBE_???");
+ }
+
+ private static @NonNull String getValidationStage(int probeType) {
+ return Decoder.constants.get(probeType & 0xff00, "UNKNOWN");
+ }
+
+ @Override
+ public String toString() {
+ return String.format("ValidationProbeEvent(%s:%d %s, %dms)",
+ getProbeName(probeType), returnCode, getValidationStage(probeType), durationMs);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(obj.getClass().equals(ValidationProbeEvent.class))) return false;
+ final ValidationProbeEvent other = (ValidationProbeEvent) obj;
+ return durationMs == other.durationMs
+ && probeType == other.probeType
+ && returnCode == other.returnCode;
+ }
+
+ final static class Decoder {
+ static final SparseArray<String> constants = MessageUtils.findMessageNames(
+ new Class[]{ValidationProbeEvent.class},
+ new String[]{"PROBE_", "FIRST_", "REVALIDATION"});
+ }
+}
diff --git a/android/net/metrics/WakeupEvent.java b/android/net/metrics/WakeupEvent.java
new file mode 100644
index 0000000..af9a73c
--- /dev/null
+++ b/android/net/metrics/WakeupEvent.java
@@ -0,0 +1,61 @@
+/*
+ * 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 android.net.metrics;
+
+import android.net.MacAddress;
+
+import java.util.StringJoiner;
+
+/**
+ * An event logged when NFLOG notifies userspace of a wakeup packet for
+ * watched interfaces.
+ * {@hide}
+ */
+public class WakeupEvent {
+ public String iface;
+ public int uid;
+ public int ethertype;
+ public MacAddress dstHwAddr;
+ public String srcIp;
+ public String dstIp;
+ public int ipNextHeader;
+ public int srcPort;
+ public int dstPort;
+ public long timestampMs;
+
+ @Override
+ public String toString() {
+ StringJoiner j = new StringJoiner(", ", "WakeupEvent(", ")");
+ j.add(String.format("%tT.%tL", timestampMs, timestampMs));
+ j.add(iface);
+ j.add("uid: " + Integer.toString(uid));
+ j.add("eth=0x" + Integer.toHexString(ethertype));
+ j.add("dstHw=" + dstHwAddr);
+ if (ipNextHeader > 0) {
+ j.add("ipNxtHdr=" + ipNextHeader);
+ j.add("srcIp=" + srcIp);
+ j.add("dstIp=" + dstIp);
+ if (srcPort > -1) {
+ j.add("srcPort=" + srcPort);
+ }
+ if (dstPort > -1) {
+ j.add("dstPort=" + dstPort);
+ }
+ }
+ return j.toString();
+ }
+}
diff --git a/android/net/metrics/WakeupStats.java b/android/net/metrics/WakeupStats.java
new file mode 100644
index 0000000..bb36536
--- /dev/null
+++ b/android/net/metrics/WakeupStats.java
@@ -0,0 +1,134 @@
+/*
+ * 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 android.net.metrics;
+
+import android.net.MacAddress;
+import android.os.Process;
+import android.os.SystemClock;
+import android.util.SparseIntArray;
+
+import java.util.StringJoiner;
+
+/**
+ * An event logged per interface and that aggregates WakeupEvents for that interface.
+ * {@hide}
+ */
+public class WakeupStats {
+
+ private static final int NO_UID = -1;
+
+ public final long creationTimeMs = SystemClock.elapsedRealtime();
+ public final String iface;
+
+ public long totalWakeups = 0;
+ public long rootWakeups = 0;
+ public long systemWakeups = 0;
+ public long nonApplicationWakeups = 0;
+ public long applicationWakeups = 0;
+ public long noUidWakeups = 0;
+ public long durationSec = 0;
+
+ public long l2UnicastCount = 0;
+ public long l2MulticastCount = 0;
+ public long l2BroadcastCount = 0;
+
+ public final SparseIntArray ethertypes = new SparseIntArray();
+ public final SparseIntArray ipNextHeaders = new SparseIntArray();
+
+ public WakeupStats(String iface) {
+ this.iface = iface;
+ }
+
+ /** Update durationSec with current time. */
+ public void updateDuration() {
+ durationSec = (SystemClock.elapsedRealtime() - creationTimeMs) / 1000;
+ }
+
+ /** Update wakeup counters for the given WakeupEvent. */
+ public void countEvent(WakeupEvent ev) {
+ totalWakeups++;
+ switch (ev.uid) {
+ case Process.ROOT_UID:
+ rootWakeups++;
+ break;
+ case Process.SYSTEM_UID:
+ systemWakeups++;
+ break;
+ case NO_UID:
+ noUidWakeups++;
+ break;
+ default:
+ if (ev.uid >= Process.FIRST_APPLICATION_UID) {
+ applicationWakeups++;
+ } else {
+ nonApplicationWakeups++;
+ }
+ break;
+ }
+
+ switch (ev.dstHwAddr.getAddressType()) {
+ case MacAddress.TYPE_UNICAST:
+ l2UnicastCount++;
+ break;
+ case MacAddress.TYPE_MULTICAST:
+ l2MulticastCount++;
+ break;
+ case MacAddress.TYPE_BROADCAST:
+ l2BroadcastCount++;
+ break;
+ default:
+ break;
+ }
+
+ increment(ethertypes, ev.ethertype);
+ if (ev.ipNextHeader >= 0) {
+ increment(ipNextHeaders, ev.ipNextHeader);
+ }
+ }
+
+ @Override
+ public String toString() {
+ updateDuration();
+ StringJoiner j = new StringJoiner(", ", "WakeupStats(", ")");
+ j.add(iface);
+ j.add("" + durationSec + "s");
+ j.add("total: " + totalWakeups);
+ j.add("root: " + rootWakeups);
+ j.add("system: " + systemWakeups);
+ j.add("apps: " + applicationWakeups);
+ j.add("non-apps: " + nonApplicationWakeups);
+ j.add("no uid: " + noUidWakeups);
+ j.add(String.format("l2 unicast/multicast/broadcast: %d/%d/%d",
+ l2UnicastCount, l2MulticastCount, l2BroadcastCount));
+ for (int i = 0; i < ethertypes.size(); i++) {
+ int eth = ethertypes.keyAt(i);
+ int count = ethertypes.valueAt(i);
+ j.add(String.format("ethertype 0x%x: %d", eth, count));
+ }
+ for (int i = 0; i < ipNextHeaders.size(); i++) {
+ int proto = ipNextHeaders.keyAt(i);
+ int count = ipNextHeaders.valueAt(i);
+ j.add(String.format("ipNxtHdr %d: %d", proto, count));
+ }
+ return j.toString();
+ }
+
+ private static void increment(SparseIntArray counters, int key) {
+ int newcount = counters.get(key, 0) + 1;
+ counters.put(key, newcount);
+ }
+}
diff --git a/android/net/netlink/ConntrackMessage.java b/android/net/netlink/ConntrackMessage.java
new file mode 100644
index 0000000..6978739
--- /dev/null
+++ b/android/net/netlink/ConntrackMessage.java
@@ -0,0 +1,109 @@
+/*
+ * 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 android.net.netlink;
+
+import static android.net.netlink.StructNlMsgHdr.NLM_F_ACK;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REPLACE;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+
+import static java.nio.ByteOrder.BIG_ENDIAN;
+
+import android.system.OsConstants;
+
+import java.net.Inet4Address;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+
+/**
+ * A NetlinkMessage subclass for netlink conntrack messages.
+ *
+ * see also: <linux_src>/include/uapi/linux/netfilter/nfnetlink_conntrack.h
+ *
+ * @hide
+ */
+public class ConntrackMessage extends NetlinkMessage {
+ public static final int STRUCT_SIZE = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE;
+
+ public static final short NFNL_SUBSYS_CTNETLINK = 1;
+ public static final short IPCTNL_MSG_CT_NEW = 0;
+
+ // enum ctattr_type
+ public static final short CTA_TUPLE_ORIG = 1;
+ public static final short CTA_TUPLE_REPLY = 2;
+ public static final short CTA_TIMEOUT = 7;
+
+ // enum ctattr_tuple
+ public static final short CTA_TUPLE_IP = 1;
+ public static final short CTA_TUPLE_PROTO = 2;
+
+ // enum ctattr_ip
+ public static final short CTA_IP_V4_SRC = 1;
+ public static final short CTA_IP_V4_DST = 2;
+
+ // enum ctattr_l4proto
+ public static final short CTA_PROTO_NUM = 1;
+ public static final short CTA_PROTO_SRC_PORT = 2;
+ public static final short CTA_PROTO_DST_PORT = 3;
+
+ public static byte[] newIPv4TimeoutUpdateRequest(
+ int proto, Inet4Address src, int sport, Inet4Address dst, int dport, int timeoutSec) {
+ // *** STYLE WARNING ***
+ //
+ // Code below this point uses extra block indentation to highlight the
+ // packing of nested tuple netlink attribute types.
+ final StructNlAttr ctaTupleOrig = new StructNlAttr(CTA_TUPLE_ORIG,
+ new StructNlAttr(CTA_TUPLE_IP,
+ new StructNlAttr(CTA_IP_V4_SRC, src),
+ new StructNlAttr(CTA_IP_V4_DST, dst)),
+ new StructNlAttr(CTA_TUPLE_PROTO,
+ new StructNlAttr(CTA_PROTO_NUM, (byte) proto),
+ new StructNlAttr(CTA_PROTO_SRC_PORT, (short) sport, BIG_ENDIAN),
+ new StructNlAttr(CTA_PROTO_DST_PORT, (short) dport, BIG_ENDIAN)));
+
+ final StructNlAttr ctaTimeout = new StructNlAttr(CTA_TIMEOUT, timeoutSec, BIG_ENDIAN);
+
+ final int payloadLength = ctaTupleOrig.getAlignedLength() + ctaTimeout.getAlignedLength();
+ final byte[] bytes = new byte[STRUCT_SIZE + payloadLength];
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+ byteBuffer.order(ByteOrder.nativeOrder());
+
+ final ConntrackMessage ctmsg = new ConntrackMessage();
+ ctmsg.mHeader.nlmsg_len = bytes.length;
+ ctmsg.mHeader.nlmsg_type = (NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_NEW;
+ ctmsg.mHeader.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE;
+ ctmsg.mHeader.nlmsg_seq = 1;
+ ctmsg.pack(byteBuffer);
+
+ ctaTupleOrig.pack(byteBuffer);
+ ctaTimeout.pack(byteBuffer);
+
+ return bytes;
+ }
+
+ protected StructNfGenMsg mNfGenMsg;
+
+ private ConntrackMessage() {
+ super(new StructNlMsgHdr());
+ mNfGenMsg = new StructNfGenMsg((byte) OsConstants.AF_INET);
+ }
+
+ public void pack(ByteBuffer byteBuffer) {
+ mHeader.pack(byteBuffer);
+ mNfGenMsg.pack(byteBuffer);
+ }
+}
diff --git a/android/net/netlink/InetDiagMessage.java b/android/net/netlink/InetDiagMessage.java
new file mode 100644
index 0000000..af9e601
--- /dev/null
+++ b/android/net/netlink/InetDiagMessage.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netlink;
+
+import static android.os.Process.INVALID_UID;
+import static android.net.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY;
+import static android.net.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.NETLINK_INET_DIAG;
+
+import android.os.Build;
+import android.os.Process;
+import android.system.ErrnoException;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
+import java.net.DatagramSocket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetSocketAddress;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * A NetlinkMessage subclass for netlink inet_diag messages.
+ *
+ * see also: <linux_src>/include/uapi/linux/inet_diag.h
+ *
+ * @hide
+ */
+public class InetDiagMessage extends NetlinkMessage {
+ public static final String TAG = "InetDiagMessage";
+ private static final int TIMEOUT_MS = 500;
+
+ public static byte[] InetDiagReqV2(int protocol, InetSocketAddress local,
+ InetSocketAddress remote, int family, short flags) {
+ final byte[] bytes = new byte[StructNlMsgHdr.STRUCT_SIZE + StructInetDiagReqV2.STRUCT_SIZE];
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+ byteBuffer.order(ByteOrder.nativeOrder());
+
+ final StructNlMsgHdr nlMsgHdr = new StructNlMsgHdr();
+ nlMsgHdr.nlmsg_len = bytes.length;
+ nlMsgHdr.nlmsg_type = SOCK_DIAG_BY_FAMILY;
+ nlMsgHdr.nlmsg_flags = flags;
+ nlMsgHdr.pack(byteBuffer);
+
+ final StructInetDiagReqV2 inetDiagReqV2 = new StructInetDiagReqV2(protocol, local, remote,
+ family);
+ inetDiagReqV2.pack(byteBuffer);
+ return bytes;
+ }
+
+ public StructInetDiagMsg mStructInetDiagMsg;
+
+ private InetDiagMessage(StructNlMsgHdr header) {
+ super(header);
+ mStructInetDiagMsg = new StructInetDiagMsg();
+ }
+
+ public static InetDiagMessage parse(StructNlMsgHdr header, ByteBuffer byteBuffer) {
+ final InetDiagMessage msg = new InetDiagMessage(header);
+ msg.mStructInetDiagMsg = StructInetDiagMsg.parse(byteBuffer);
+ return msg;
+ }
+
+ private static int lookupUidByFamily(int protocol, InetSocketAddress local,
+ InetSocketAddress remote, int family, short flags,
+ FileDescriptor fd)
+ throws ErrnoException, InterruptedIOException {
+ byte[] msg = InetDiagReqV2(protocol, local, remote, family, flags);
+ NetlinkSocket.sendMessage(fd, msg, 0, msg.length, TIMEOUT_MS);
+ ByteBuffer response = NetlinkSocket.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TIMEOUT_MS);
+
+ final NetlinkMessage nlMsg = NetlinkMessage.parse(response);
+ final StructNlMsgHdr hdr = nlMsg.getHeader();
+ if (hdr.nlmsg_type == NetlinkConstants.NLMSG_DONE) {
+ return INVALID_UID;
+ }
+ if (nlMsg instanceof InetDiagMessage) {
+ return ((InetDiagMessage) nlMsg).mStructInetDiagMsg.idiag_uid;
+ }
+ return INVALID_UID;
+ }
+
+ private static final int FAMILY[] = {AF_INET6, AF_INET};
+
+ private static int lookupUid(int protocol, InetSocketAddress local,
+ InetSocketAddress remote, FileDescriptor fd)
+ throws ErrnoException, InterruptedIOException {
+ int uid;
+
+ for (int family : FAMILY) {
+ /**
+ * For exact match lookup, swap local and remote for UDP lookups due to kernel
+ * bug which will not be fixed. See aosp/755889 and
+ * https://www.mail-archive.com/[email protected]/msg248638.html
+ */
+ if (protocol == IPPROTO_UDP) {
+ uid = lookupUidByFamily(protocol, remote, local, family, NLM_F_REQUEST, fd);
+ } else {
+ uid = lookupUidByFamily(protocol, local, remote, family, NLM_F_REQUEST, fd);
+ }
+ if (uid != INVALID_UID) {
+ return uid;
+ }
+ }
+
+ /**
+ * For UDP it's possible for a socket to send packets to arbitrary destinations, even if the
+ * socket is not connected (and even if the socket is connected to a different destination).
+ * If we want this API to work for such packets, then on miss we need to do a second lookup
+ * with only the local address and port filled in.
+ * Always use flags == NLM_F_REQUEST | NLM_F_DUMP for wildcard.
+ */
+ if (protocol == IPPROTO_UDP) {
+ try {
+ InetSocketAddress wildcard = new InetSocketAddress(
+ Inet6Address.getByName("::"), 0);
+ uid = lookupUidByFamily(protocol, local, wildcard, AF_INET6,
+ (short) (NLM_F_REQUEST | NLM_F_DUMP), fd);
+ if (uid != INVALID_UID) {
+ return uid;
+ }
+ wildcard = new InetSocketAddress(Inet4Address.getByName("0.0.0.0"), 0);
+ uid = lookupUidByFamily(protocol, local, wildcard, AF_INET,
+ (short) (NLM_F_REQUEST | NLM_F_DUMP), fd);
+ if (uid != INVALID_UID) {
+ return uid;
+ }
+ } catch (UnknownHostException e) {
+ Log.e(TAG, e.toString());
+ }
+ }
+ return INVALID_UID;
+ }
+
+ /**
+ * Use an inet_diag socket to look up the UID associated with the input local and remote
+ * address/port and protocol of a connection.
+ */
+ public static int getConnectionOwnerUid(int protocol, InetSocketAddress local,
+ InetSocketAddress remote) {
+ try {
+ final FileDescriptor fd = NetlinkSocket.forProto(NETLINK_INET_DIAG);
+ NetlinkSocket.connectToKernel(fd);
+
+ return lookupUid(protocol, local, remote, fd);
+
+ } catch (ErrnoException | SocketException | IllegalArgumentException
+ | InterruptedIOException e) {
+ Log.e(TAG, e.toString());
+ }
+ return INVALID_UID;
+ }
+
+ @Override
+ public String toString() {
+ return "InetDiagMessage{ "
+ + "nlmsghdr{" + (mHeader == null ? "" : mHeader.toString()) + "}, "
+ + "inet_diag_msg{"
+ + (mStructInetDiagMsg == null ? "" : mStructInetDiagMsg.toString()) + "} "
+ + "}";
+ }
+}
diff --git a/android/net/netlink/NetlinkConstants.java b/android/net/netlink/NetlinkConstants.java
new file mode 100644
index 0000000..fc1551c
--- /dev/null
+++ b/android/net/netlink/NetlinkConstants.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2015 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.netlink;
+
+import android.system.OsConstants;
+import com.android.internal.util.HexDump;
+
+import java.nio.ByteBuffer;
+
+
+/**
+ * Various constants and static helper methods for netlink communications.
+ *
+ * Values taken from:
+ *
+ * <linux_src>/include/uapi/linux/netlink.h
+ * <linux_src>/include/uapi/linux/rtnetlink.h
+ *
+ * @hide
+ */
+public class NetlinkConstants {
+ private NetlinkConstants() {}
+
+ public static final int NLA_ALIGNTO = 4;
+
+ public static final int alignedLengthOf(short length) {
+ final int intLength = (int) length & 0xffff;
+ return alignedLengthOf(intLength);
+ }
+
+ public static final int alignedLengthOf(int length) {
+ if (length <= 0) { return 0; }
+ return (((length + NLA_ALIGNTO - 1) / NLA_ALIGNTO) * NLA_ALIGNTO);
+ }
+
+ public static String stringForAddressFamily(int family) {
+ if (family == OsConstants.AF_INET) { return "AF_INET"; }
+ if (family == OsConstants.AF_INET6) { return "AF_INET6"; }
+ if (family == OsConstants.AF_NETLINK) { return "AF_NETLINK"; }
+ return String.valueOf(family);
+ }
+
+ public static String stringForProtocol(int protocol) {
+ if (protocol == OsConstants.IPPROTO_TCP) { return "IPPROTO_TCP"; }
+ if (protocol == OsConstants.IPPROTO_UDP) { return "IPPROTO_UDP"; }
+ return String.valueOf(protocol);
+ }
+
+ public static String hexify(byte[] bytes) {
+ if (bytes == null) { return "(null)"; }
+ return HexDump.toHexString(bytes);
+ }
+
+ public static String hexify(ByteBuffer buffer) {
+ if (buffer == null) { return "(null)"; }
+ return HexDump.toHexString(
+ buffer.array(), buffer.position(), buffer.remaining());
+ }
+
+ // Known values for struct nlmsghdr nlm_type.
+ public static final short NLMSG_NOOP = 1; // Nothing
+ public static final short NLMSG_ERROR = 2; // Error
+ public static final short NLMSG_DONE = 3; // End of a dump
+ public static final short NLMSG_OVERRUN = 4; // Data lost
+ public static final short NLMSG_MAX_RESERVED = 15; // Max reserved value
+
+ public static final short RTM_NEWLINK = 16;
+ public static final short RTM_DELLINK = 17;
+ public static final short RTM_GETLINK = 18;
+ public static final short RTM_SETLINK = 19;
+ public static final short RTM_NEWADDR = 20;
+ public static final short RTM_DELADDR = 21;
+ public static final short RTM_GETADDR = 22;
+ public static final short RTM_NEWROUTE = 24;
+ public static final short RTM_DELROUTE = 25;
+ public static final short RTM_GETROUTE = 26;
+ public static final short RTM_NEWNEIGH = 28;
+ public static final short RTM_DELNEIGH = 29;
+ public static final short RTM_GETNEIGH = 30;
+ public static final short RTM_NEWRULE = 32;
+ public static final short RTM_DELRULE = 33;
+ public static final short RTM_GETRULE = 34;
+ public static final short RTM_NEWNDUSEROPT = 68;
+
+ /* see <linux_src>/include/uapi/linux/sock_diag.h */
+ public static final short SOCK_DIAG_BY_FAMILY = 20;
+
+ public static String stringForNlMsgType(short nlm_type) {
+ switch (nlm_type) {
+ case NLMSG_NOOP: return "NLMSG_NOOP";
+ case NLMSG_ERROR: return "NLMSG_ERROR";
+ case NLMSG_DONE: return "NLMSG_DONE";
+ case NLMSG_OVERRUN: return "NLMSG_OVERRUN";
+ case RTM_NEWLINK: return "RTM_NEWLINK";
+ case RTM_DELLINK: return "RTM_DELLINK";
+ case RTM_GETLINK: return "RTM_GETLINK";
+ case RTM_SETLINK: return "RTM_SETLINK";
+ case RTM_NEWADDR: return "RTM_NEWADDR";
+ case RTM_DELADDR: return "RTM_DELADDR";
+ case RTM_GETADDR: return "RTM_GETADDR";
+ case RTM_NEWROUTE: return "RTM_NEWROUTE";
+ case RTM_DELROUTE: return "RTM_DELROUTE";
+ case RTM_GETROUTE: return "RTM_GETROUTE";
+ case RTM_NEWNEIGH: return "RTM_NEWNEIGH";
+ case RTM_DELNEIGH: return "RTM_DELNEIGH";
+ case RTM_GETNEIGH: return "RTM_GETNEIGH";
+ case RTM_NEWRULE: return "RTM_NEWRULE";
+ case RTM_DELRULE: return "RTM_DELRULE";
+ case RTM_GETRULE: return "RTM_GETRULE";
+ case RTM_NEWNDUSEROPT: return "RTM_NEWNDUSEROPT";
+ default:
+ return "unknown RTM type: " + String.valueOf(nlm_type);
+ }
+ }
+}
diff --git a/android/net/netlink/NetlinkErrorMessage.java b/android/net/netlink/NetlinkErrorMessage.java
new file mode 100644
index 0000000..e275574
--- /dev/null
+++ b/android/net/netlink/NetlinkErrorMessage.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 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.netlink;
+
+import android.net.netlink.StructNlMsgHdr;
+import android.net.netlink.NetlinkMessage;
+
+import java.nio.ByteBuffer;
+
+
+/**
+ * A NetlinkMessage subclass for netlink error messages.
+ *
+ * @hide
+ */
+public class NetlinkErrorMessage extends NetlinkMessage {
+
+ public static NetlinkErrorMessage parse(StructNlMsgHdr header, ByteBuffer byteBuffer) {
+ final NetlinkErrorMessage errorMsg = new NetlinkErrorMessage(header);
+
+ errorMsg.mNlMsgErr = StructNlMsgErr.parse(byteBuffer);
+ if (errorMsg.mNlMsgErr == null) {
+ return null;
+ }
+
+ return errorMsg;
+ }
+
+ private StructNlMsgErr mNlMsgErr;
+
+ NetlinkErrorMessage(StructNlMsgHdr header) {
+ super(header);
+ mNlMsgErr = null;
+ }
+
+ public StructNlMsgErr getNlMsgError() {
+ return mNlMsgErr;
+ }
+
+ @Override
+ public String toString() {
+ return "NetlinkErrorMessage{ "
+ + "nlmsghdr{" + (mHeader == null ? "" : mHeader.toString()) + "}, "
+ + "nlmsgerr{" + (mNlMsgErr == null ? "" : mNlMsgErr.toString()) + "} "
+ + "}";
+ }
+}
diff --git a/android/net/netlink/NetlinkMessage.java b/android/net/netlink/NetlinkMessage.java
new file mode 100644
index 0000000..a325db8
--- /dev/null
+++ b/android/net/netlink/NetlinkMessage.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2015 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.netlink;
+
+import android.net.netlink.NetlinkConstants;
+import android.net.netlink.NetlinkErrorMessage;
+import android.net.netlink.RtNetlinkNeighborMessage;
+import android.net.netlink.StructNlAttr;
+import android.net.netlink.StructNlMsgHdr;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+
+
+/**
+ * NetlinkMessage base class for other, more specific netlink message types.
+ *
+ * Classes that extend NetlinkMessage should:
+ * - implement a public static parse(StructNlMsgHdr, ByteBuffer) method
+ * - returning either null (parse errors) or a new object of the subclass
+ * type (cast-able to NetlinkMessage)
+ *
+ * NetlinkMessage.parse() should be updated to know which nlmsg_type values
+ * correspond with which message subclasses.
+ *
+ * @hide
+ */
+public class NetlinkMessage {
+ private final static String TAG = "NetlinkMessage";
+
+ public static NetlinkMessage parse(ByteBuffer byteBuffer) {
+ final int startPosition = (byteBuffer != null) ? byteBuffer.position() : -1;
+ final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(byteBuffer);
+ if (nlmsghdr == null) {
+ return null;
+ }
+
+ int payloadLength = NetlinkConstants.alignedLengthOf(nlmsghdr.nlmsg_len);
+ payloadLength -= StructNlMsgHdr.STRUCT_SIZE;
+ if (payloadLength < 0 || payloadLength > byteBuffer.remaining()) {
+ // Malformed message or runt buffer. Pretend the buffer was consumed.
+ byteBuffer.position(byteBuffer.limit());
+ return null;
+ }
+
+ switch (nlmsghdr.nlmsg_type) {
+ //case NetlinkConstants.NLMSG_NOOP:
+ case NetlinkConstants.NLMSG_ERROR:
+ return (NetlinkMessage) NetlinkErrorMessage.parse(nlmsghdr, byteBuffer);
+ case NetlinkConstants.NLMSG_DONE:
+ byteBuffer.position(byteBuffer.position() + payloadLength);
+ return new NetlinkMessage(nlmsghdr);
+ //case NetlinkConstants.NLMSG_OVERRUN:
+ case NetlinkConstants.RTM_NEWNEIGH:
+ case NetlinkConstants.RTM_DELNEIGH:
+ case NetlinkConstants.RTM_GETNEIGH:
+ return (NetlinkMessage) RtNetlinkNeighborMessage.parse(nlmsghdr, byteBuffer);
+ case NetlinkConstants.SOCK_DIAG_BY_FAMILY:
+ return (NetlinkMessage) InetDiagMessage.parse(nlmsghdr, byteBuffer);
+ default:
+ if (nlmsghdr.nlmsg_type <= NetlinkConstants.NLMSG_MAX_RESERVED) {
+ // Netlink control message. Just parse the header for now,
+ // pretending the whole message was consumed.
+ byteBuffer.position(byteBuffer.position() + payloadLength);
+ return new NetlinkMessage(nlmsghdr);
+ }
+ return null;
+ }
+ }
+
+ protected StructNlMsgHdr mHeader;
+
+ public NetlinkMessage(StructNlMsgHdr nlmsghdr) {
+ mHeader = nlmsghdr;
+ }
+
+ public StructNlMsgHdr getHeader() {
+ return mHeader;
+ }
+
+ @Override
+ public String toString() {
+ return "NetlinkMessage{" + (mHeader == null ? "" : mHeader.toString()) + "}";
+ }
+}
diff --git a/android/net/netlink/NetlinkSocket.java b/android/net/netlink/NetlinkSocket.java
new file mode 100644
index 0000000..7311fc5
--- /dev/null
+++ b/android/net/netlink/NetlinkSocket.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2015 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.netlink;
+
+import static android.net.util.SocketUtils.makeNetlinkSocketAddress;
+import static android.system.OsConstants.AF_NETLINK;
+import static android.system.OsConstants.EIO;
+import static android.system.OsConstants.EPROTO;
+import static android.system.OsConstants.ETIMEDOUT;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_RCVBUF;
+import static android.system.OsConstants.SO_RCVTIMEO;
+import static android.system.OsConstants.SO_SNDTIMEO;
+
+import android.net.util.SocketUtils;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructTimeval;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+
+/**
+ * NetlinkSocket
+ *
+ * A small static class to assist with AF_NETLINK socket operations.
+ *
+ * @hide
+ */
+public class NetlinkSocket {
+ private static final String TAG = "NetlinkSocket";
+
+ public static final int DEFAULT_RECV_BUFSIZE = 8 * 1024;
+ public static final int SOCKET_RECV_BUFSIZE = 64 * 1024;
+
+ public static void sendOneShotKernelMessage(int nlProto, byte[] msg) throws ErrnoException {
+ final String errPrefix = "Error in NetlinkSocket.sendOneShotKernelMessage";
+ final long IO_TIMEOUT = 300L;
+
+ final FileDescriptor fd = forProto(nlProto);
+
+ try {
+ connectToKernel(fd);
+ sendMessage(fd, msg, 0, msg.length, IO_TIMEOUT);
+ final ByteBuffer bytes = recvMessage(fd, DEFAULT_RECV_BUFSIZE, IO_TIMEOUT);
+ // recvMessage() guaranteed to not return null if it did not throw.
+ final NetlinkMessage response = NetlinkMessage.parse(bytes);
+ if (response != null && response instanceof NetlinkErrorMessage &&
+ (((NetlinkErrorMessage) response).getNlMsgError() != null)) {
+ final int errno = ((NetlinkErrorMessage) response).getNlMsgError().error;
+ if (errno != 0) {
+ // TODO: consider ignoring EINVAL (-22), which appears to be
+ // normal when probing a neighbor for which the kernel does
+ // not already have / no longer has a link layer address.
+ Log.e(TAG, errPrefix + ", errmsg=" + response.toString());
+ // Note: convert kernel errnos (negative) into userspace errnos (positive).
+ throw new ErrnoException(response.toString(), Math.abs(errno));
+ }
+ } else {
+ final String errmsg;
+ if (response == null) {
+ bytes.position(0);
+ errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes);
+ } else {
+ errmsg = response.toString();
+ }
+ Log.e(TAG, errPrefix + ", errmsg=" + errmsg);
+ throw new ErrnoException(errmsg, EPROTO);
+ }
+ } catch (InterruptedIOException e) {
+ Log.e(TAG, errPrefix, e);
+ throw new ErrnoException(errPrefix, ETIMEDOUT, e);
+ } catch (SocketException e) {
+ Log.e(TAG, errPrefix, e);
+ throw new ErrnoException(errPrefix, EIO, e);
+ } finally {
+ try {
+ SocketUtils.closeSocket(fd);
+ } catch (IOException e) {
+ // Nothing we can do here
+ }
+ }
+ }
+
+ public static FileDescriptor forProto(int nlProto) throws ErrnoException {
+ final FileDescriptor fd = Os.socket(AF_NETLINK, SOCK_DGRAM, nlProto);
+ Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, SOCKET_RECV_BUFSIZE);
+ return fd;
+ }
+
+ public static void connectToKernel(FileDescriptor fd) throws ErrnoException, SocketException {
+ Os.connect(fd, makeNetlinkSocketAddress(0, 0));
+ }
+
+ private static void checkTimeout(long timeoutMs) {
+ if (timeoutMs < 0) {
+ throw new IllegalArgumentException("Negative timeouts not permitted");
+ }
+ }
+
+ /**
+ * Wait up to |timeoutMs| (or until underlying socket error) for a
+ * netlink message of at most |bufsize| size.
+ *
+ * Multi-threaded calls with different timeouts will cause unexpected results.
+ */
+ public static ByteBuffer recvMessage(FileDescriptor fd, int bufsize, long timeoutMs)
+ throws ErrnoException, IllegalArgumentException, InterruptedIOException {
+ checkTimeout(timeoutMs);
+
+ Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(timeoutMs));
+
+ ByteBuffer byteBuffer = ByteBuffer.allocate(bufsize);
+ int length = Os.read(fd, byteBuffer);
+ if (length == bufsize) {
+ Log.w(TAG, "maximum read");
+ }
+ byteBuffer.position(0);
+ byteBuffer.limit(length);
+ byteBuffer.order(ByteOrder.nativeOrder());
+ return byteBuffer;
+ }
+
+ /**
+ * Send a message to a peer to which this socket has previously connected,
+ * waiting at most |timeoutMs| milliseconds for the send to complete.
+ *
+ * Multi-threaded calls with different timeouts will cause unexpected results.
+ */
+ public static int sendMessage(
+ FileDescriptor fd, byte[] bytes, int offset, int count, long timeoutMs)
+ throws ErrnoException, IllegalArgumentException, InterruptedIOException {
+ checkTimeout(timeoutMs);
+ Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(timeoutMs));
+ return Os.write(fd, bytes, offset, count);
+ }
+}
diff --git a/android/net/netlink/RtNetlinkNeighborMessage.java b/android/net/netlink/RtNetlinkNeighborMessage.java
new file mode 100644
index 0000000..e784fbb
--- /dev/null
+++ b/android/net/netlink/RtNetlinkNeighborMessage.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2015 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.netlink;
+
+import static android.net.netlink.StructNlMsgHdr.NLM_F_ACK;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REPLACE;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+
+import android.net.netlink.StructNdaCacheInfo;
+import android.net.netlink.StructNdMsg;
+import android.net.netlink.StructNlAttr;
+import android.net.netlink.StructNlMsgHdr;
+import android.net.netlink.NetlinkMessage;
+import android.system.OsConstants;
+import android.util.Log;
+
+import java.net.InetAddress;
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+
+/**
+ * A NetlinkMessage subclass for rtnetlink neighbor messages.
+ *
+ * see also: <linux_src>/include/uapi/linux/neighbour.h
+ *
+ * @hide
+ */
+public class RtNetlinkNeighborMessage extends NetlinkMessage {
+ public static final short NDA_UNSPEC = 0;
+ public static final short NDA_DST = 1;
+ public static final short NDA_LLADDR = 2;
+ public static final short NDA_CACHEINFO = 3;
+ public static final short NDA_PROBES = 4;
+ public static final short NDA_VLAN = 5;
+ public static final short NDA_PORT = 6;
+ public static final short NDA_VNI = 7;
+ public static final short NDA_IFINDEX = 8;
+ public static final short NDA_MASTER = 9;
+
+ private static StructNlAttr findNextAttrOfType(short attrType, ByteBuffer byteBuffer) {
+ while (byteBuffer != null && byteBuffer.remaining() > 0) {
+ final StructNlAttr nlAttr = StructNlAttr.peek(byteBuffer);
+ if (nlAttr == null) {
+ break;
+ }
+ if (nlAttr.nla_type == attrType) {
+ return StructNlAttr.parse(byteBuffer);
+ }
+ if (byteBuffer.remaining() < nlAttr.getAlignedLength()) {
+ break;
+ }
+ byteBuffer.position(byteBuffer.position() + nlAttr.getAlignedLength());
+ }
+ return null;
+ }
+
+ public static RtNetlinkNeighborMessage parse(StructNlMsgHdr header, ByteBuffer byteBuffer) {
+ final RtNetlinkNeighborMessage neighMsg = new RtNetlinkNeighborMessage(header);
+
+ neighMsg.mNdmsg = StructNdMsg.parse(byteBuffer);
+ if (neighMsg.mNdmsg == null) {
+ return null;
+ }
+
+ // Some of these are message-type dependent, and not always present.
+ final int baseOffset = byteBuffer.position();
+ StructNlAttr nlAttr = findNextAttrOfType(NDA_DST, byteBuffer);
+ if (nlAttr != null) {
+ neighMsg.mDestination = nlAttr.getValueAsInetAddress();
+ }
+
+ byteBuffer.position(baseOffset);
+ nlAttr = findNextAttrOfType(NDA_LLADDR, byteBuffer);
+ if (nlAttr != null) {
+ neighMsg.mLinkLayerAddr = nlAttr.nla_value;
+ }
+
+ byteBuffer.position(baseOffset);
+ nlAttr = findNextAttrOfType(NDA_PROBES, byteBuffer);
+ if (nlAttr != null) {
+ neighMsg.mNumProbes = nlAttr.getValueAsInt(0);
+ }
+
+ byteBuffer.position(baseOffset);
+ nlAttr = findNextAttrOfType(NDA_CACHEINFO, byteBuffer);
+ if (nlAttr != null) {
+ neighMsg.mCacheInfo = StructNdaCacheInfo.parse(nlAttr.getValueAsByteBuffer());
+ }
+
+ final int kMinConsumed = StructNlMsgHdr.STRUCT_SIZE + StructNdMsg.STRUCT_SIZE;
+ final int kAdditionalSpace = NetlinkConstants.alignedLengthOf(
+ neighMsg.mHeader.nlmsg_len - kMinConsumed);
+ if (byteBuffer.remaining() < kAdditionalSpace) {
+ byteBuffer.position(byteBuffer.limit());
+ } else {
+ byteBuffer.position(baseOffset + kAdditionalSpace);
+ }
+
+ return neighMsg;
+ }
+
+ /**
+ * A convenience method to create an RTM_GETNEIGH request message.
+ */
+ public static byte[] newGetNeighborsRequest(int seqNo) {
+ final int length = StructNlMsgHdr.STRUCT_SIZE + StructNdMsg.STRUCT_SIZE;
+ final byte[] bytes = new byte[length];
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+ byteBuffer.order(ByteOrder.nativeOrder());
+
+ final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr();
+ nlmsghdr.nlmsg_len = length;
+ nlmsghdr.nlmsg_type = NetlinkConstants.RTM_GETNEIGH;
+ nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ nlmsghdr.nlmsg_seq = seqNo;
+ nlmsghdr.pack(byteBuffer);
+
+ final StructNdMsg ndmsg = new StructNdMsg();
+ ndmsg.pack(byteBuffer);
+
+ return bytes;
+ }
+
+ /**
+ * A convenience method to create an RTM_NEWNEIGH message, to modify
+ * the kernel's state information for a specific neighbor.
+ */
+ public static byte[] newNewNeighborMessage(
+ int seqNo, InetAddress ip, short nudState, int ifIndex, byte[] llAddr) {
+ final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr();
+ nlmsghdr.nlmsg_type = NetlinkConstants.RTM_NEWNEIGH;
+ nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE;
+ nlmsghdr.nlmsg_seq = seqNo;
+
+ final RtNetlinkNeighborMessage msg = new RtNetlinkNeighborMessage(nlmsghdr);
+ msg.mNdmsg = new StructNdMsg();
+ msg.mNdmsg.ndm_family =
+ (byte) ((ip instanceof Inet6Address) ? OsConstants.AF_INET6 : OsConstants.AF_INET);
+ msg.mNdmsg.ndm_ifindex = ifIndex;
+ msg.mNdmsg.ndm_state = nudState;
+ msg.mDestination = ip;
+ msg.mLinkLayerAddr = llAddr; // might be null
+
+ final byte[] bytes = new byte[msg.getRequiredSpace()];
+ nlmsghdr.nlmsg_len = bytes.length;
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+ byteBuffer.order(ByteOrder.nativeOrder());
+ msg.pack(byteBuffer);
+ return bytes;
+ }
+
+ private StructNdMsg mNdmsg;
+ private InetAddress mDestination;
+ private byte[] mLinkLayerAddr;
+ private int mNumProbes;
+ private StructNdaCacheInfo mCacheInfo;
+
+ private RtNetlinkNeighborMessage(StructNlMsgHdr header) {
+ super(header);
+ mNdmsg = null;
+ mDestination = null;
+ mLinkLayerAddr = null;
+ mNumProbes = 0;
+ mCacheInfo = null;
+ }
+
+ public StructNdMsg getNdHeader() {
+ return mNdmsg;
+ }
+
+ public InetAddress getDestination() {
+ return mDestination;
+ }
+
+ public byte[] getLinkLayerAddress() {
+ return mLinkLayerAddr;
+ }
+
+ public int getProbes() {
+ return mNumProbes;
+ }
+
+ public StructNdaCacheInfo getCacheInfo() {
+ return mCacheInfo;
+ }
+
+ public int getRequiredSpace() {
+ int spaceRequired = StructNlMsgHdr.STRUCT_SIZE + StructNdMsg.STRUCT_SIZE;
+ if (mDestination != null) {
+ spaceRequired += NetlinkConstants.alignedLengthOf(
+ StructNlAttr.NLA_HEADERLEN + mDestination.getAddress().length);
+ }
+ if (mLinkLayerAddr != null) {
+ spaceRequired += NetlinkConstants.alignedLengthOf(
+ StructNlAttr.NLA_HEADERLEN + mLinkLayerAddr.length);
+ }
+ // Currently we don't write messages with NDA_PROBES nor NDA_CACHEINFO
+ // attributes appended. Fix later, if necessary.
+ return spaceRequired;
+ }
+
+ private static void packNlAttr(short nlType, byte[] nlValue, ByteBuffer byteBuffer) {
+ final StructNlAttr nlAttr = new StructNlAttr();
+ nlAttr.nla_type = nlType;
+ nlAttr.nla_value = nlValue;
+ nlAttr.nla_len = (short) (StructNlAttr.NLA_HEADERLEN + nlAttr.nla_value.length);
+ nlAttr.pack(byteBuffer);
+ }
+
+ public void pack(ByteBuffer byteBuffer) {
+ getHeader().pack(byteBuffer) ;
+ mNdmsg.pack(byteBuffer);
+
+ if (mDestination != null) {
+ packNlAttr(NDA_DST, mDestination.getAddress(), byteBuffer);
+ }
+ if (mLinkLayerAddr != null) {
+ packNlAttr(NDA_LLADDR, mLinkLayerAddr, byteBuffer);
+ }
+ }
+
+ @Override
+ public String toString() {
+ final String ipLiteral = (mDestination == null) ? "" : mDestination.getHostAddress();
+ return "RtNetlinkNeighborMessage{ "
+ + "nlmsghdr{" + (mHeader == null ? "" : mHeader.toString()) + "}, "
+ + "ndmsg{" + (mNdmsg == null ? "" : mNdmsg.toString()) + "}, "
+ + "destination{" + ipLiteral + "} "
+ + "linklayeraddr{" + NetlinkConstants.hexify(mLinkLayerAddr) + "} "
+ + "probes{" + mNumProbes + "} "
+ + "cacheinfo{" + (mCacheInfo == null ? "" : mCacheInfo.toString()) + "} "
+ + "}";
+ }
+}
diff --git a/android/net/netlink/StructInetDiagMsg.java b/android/net/netlink/StructInetDiagMsg.java
new file mode 100644
index 0000000..da824ad
--- /dev/null
+++ b/android/net/netlink/StructInetDiagMsg.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netlink;
+
+import static java.nio.ByteOrder.BIG_ENDIAN;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+
+import java.net.Inet4Address;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import android.util.Log;
+
+/**
+ * struct inet_diag_msg
+ *
+ * see <linux_src>/include/uapi/linux/inet_diag.h
+ *
+ * struct inet_diag_msg {
+ * __u8 idiag_family;
+ * __u8 idiag_state;
+ * __u8 idiag_timer;
+ * __u8 idiag_retrans;
+ * struct inet_diag_sockid id;
+ * __u32 idiag_expires;
+ * __u32 idiag_rqueue;
+ * __u32 idiag_wqueue;
+ * __u32 idiag_uid;
+ * __u32 idiag_inode;
+ * };
+ *
+ * @hide
+ */
+public class StructInetDiagMsg {
+ public static final int STRUCT_SIZE = 4 + StructInetDiagSockId.STRUCT_SIZE + 20;
+ private static final int IDIAG_UID_OFFSET = StructNlMsgHdr.STRUCT_SIZE + 4 +
+ StructInetDiagSockId.STRUCT_SIZE + 12;
+ public int idiag_uid;
+
+ public static StructInetDiagMsg parse(ByteBuffer byteBuffer) {
+ StructInetDiagMsg struct = new StructInetDiagMsg();
+ struct.idiag_uid = byteBuffer.getInt(IDIAG_UID_OFFSET);
+ return struct;
+ }
+
+ @Override
+ public String toString() {
+ return "StructInetDiagMsg{ "
+ + "idiag_uid{" + idiag_uid + "}, "
+ + "}";
+ }
+}
diff --git a/android/net/netlink/StructInetDiagReqV2.java b/android/net/netlink/StructInetDiagReqV2.java
new file mode 100644
index 0000000..49a9325
--- /dev/null
+++ b/android/net/netlink/StructInetDiagReqV2.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netlink;
+
+import static java.nio.ByteOrder.BIG_ENDIAN;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * struct inet_diag_req_v2
+ *
+ * see <linux_src>/include/uapi/linux/inet_diag.h
+ *
+ * struct inet_diag_req_v2 {
+ * __u8 sdiag_family;
+ * __u8 sdiag_protocol;
+ * __u8 idiag_ext;
+ * __u8 pad;
+ * __u32 idiag_states;
+ * struct inet_diag_sockid id;
+ * };
+ *
+ * @hide
+ */
+public class StructInetDiagReqV2 {
+ public static final int STRUCT_SIZE = 8 + StructInetDiagSockId.STRUCT_SIZE;
+
+ private final byte sdiag_family;
+ private final byte sdiag_protocol;
+ private final StructInetDiagSockId id;
+ private final int INET_DIAG_REQ_V2_ALL_STATES = (int) 0xffffffff;
+
+
+ public StructInetDiagReqV2(int protocol, InetSocketAddress local, InetSocketAddress remote,
+ int family) {
+ sdiag_family = (byte) family;
+ sdiag_protocol = (byte) protocol;
+ id = new StructInetDiagSockId(local, remote);
+ }
+
+ public void pack(ByteBuffer byteBuffer) {
+ // The ByteOrder must have already been set by the caller.
+ byteBuffer.put((byte) sdiag_family);
+ byteBuffer.put((byte) sdiag_protocol);
+ byteBuffer.put((byte) 0);
+ byteBuffer.put((byte) 0);
+ byteBuffer.putInt(INET_DIAG_REQ_V2_ALL_STATES);
+ id.pack(byteBuffer);
+ }
+
+ @Override
+ public String toString() {
+ final String familyStr = NetlinkConstants.stringForAddressFamily(sdiag_family);
+ final String protocolStr = NetlinkConstants.stringForAddressFamily(sdiag_protocol);
+
+ return "StructInetDiagReqV2{ "
+ + "sdiag_family{" + familyStr + "}, "
+ + "sdiag_protocol{" + protocolStr + "}, "
+ + "idiag_ext{" + 0 + ")}, "
+ + "pad{" + 0 + "}, "
+ + "idiag_states{" + Integer.toHexString(INET_DIAG_REQ_V2_ALL_STATES) + "}, "
+ + id.toString()
+ + "}";
+ }
+}
diff --git a/android/net/netlink/StructInetDiagSockId.java b/android/net/netlink/StructInetDiagSockId.java
new file mode 100644
index 0000000..2e9fa25
--- /dev/null
+++ b/android/net/netlink/StructInetDiagSockId.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netlink;
+
+import static java.nio.ByteOrder.BIG_ENDIAN;
+
+import java.net.Inet4Address;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * struct inet_diag_req_v2
+ *
+ * see <linux_src>/include/uapi/linux/inet_diag.h
+ *
+ * struct inet_diag_sockid {
+ * __be16 idiag_sport;
+ * __be16 idiag_dport;
+ * __be32 idiag_src[4];
+ * __be32 idiag_dst[4];
+ * __u32 idiag_if;
+ * __u32 idiag_cookie[2];
+ * #define INET_DIAG_NOCOOKIE (~0U)
+ * };
+ *
+ * @hide
+ */
+public class StructInetDiagSockId {
+ public static final int STRUCT_SIZE = 48;
+
+ private final InetSocketAddress mLocSocketAddress;
+ private final InetSocketAddress mRemSocketAddress;
+ private final byte[] INET_DIAG_NOCOOKIE = new byte[]{
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff};
+ private final byte[] IPV4_PADDING = new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+ public StructInetDiagSockId(InetSocketAddress loc, InetSocketAddress rem) {
+ mLocSocketAddress = loc;
+ mRemSocketAddress = rem;
+ }
+
+ public void pack(ByteBuffer byteBuffer) {
+ byteBuffer.order(BIG_ENDIAN);
+ byteBuffer.putShort((short) mLocSocketAddress.getPort());
+ byteBuffer.putShort((short) mRemSocketAddress.getPort());
+ byteBuffer.put(mLocSocketAddress.getAddress().getAddress());
+ if (mLocSocketAddress.getAddress() instanceof Inet4Address) {
+ byteBuffer.put(IPV4_PADDING);
+ }
+ byteBuffer.put(mRemSocketAddress.getAddress().getAddress());
+ if (mRemSocketAddress.getAddress() instanceof Inet4Address) {
+ byteBuffer.put(IPV4_PADDING);
+ }
+ byteBuffer.order(ByteOrder.nativeOrder());
+ byteBuffer.putInt(0);
+ byteBuffer.put(INET_DIAG_NOCOOKIE);
+ }
+
+ @Override
+ public String toString() {
+ return "StructInetDiagSockId{ "
+ + "idiag_sport{" + mLocSocketAddress.getPort() + "}, "
+ + "idiag_dport{" + mRemSocketAddress.getPort() + "}, "
+ + "idiag_src{" + mLocSocketAddress.getAddress().getHostAddress() + "}, "
+ + "idiag_dst{" + mRemSocketAddress.getAddress().getHostAddress() + "}, "
+ + "idiag_if{" + 0 + "} "
+ + "idiag_cookie{INET_DIAG_NOCOOKIE}"
+ + "}";
+ }
+}
diff --git a/android/net/netlink/StructNdMsg.java b/android/net/netlink/StructNdMsg.java
new file mode 100644
index 0000000..e34ec39
--- /dev/null
+++ b/android/net/netlink/StructNdMsg.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2015 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.netlink;
+
+import android.net.netlink.NetlinkConstants;
+import android.system.OsConstants;
+import java.nio.ByteBuffer;
+
+
+/**
+ * struct ndmsg
+ *
+ * see: <linux_src>/include/uapi/linux/neighbour.h
+ *
+ * @hide
+ */
+public class StructNdMsg {
+ // Already aligned.
+ public static final int STRUCT_SIZE = 12;
+
+ // Neighbor Cache Entry States
+ public static final short NUD_NONE = 0x00;
+ public static final short NUD_INCOMPLETE = 0x01;
+ public static final short NUD_REACHABLE = 0x02;
+ public static final short NUD_STALE = 0x04;
+ public static final short NUD_DELAY = 0x08;
+ public static final short NUD_PROBE = 0x10;
+ public static final short NUD_FAILED = 0x20;
+ public static final short NUD_NOARP = 0x40;
+ public static final short NUD_PERMANENT = 0x80;
+
+ public static String stringForNudState(short nudState) {
+ switch (nudState) {
+ case NUD_NONE: return "NUD_NONE";
+ case NUD_INCOMPLETE: return "NUD_INCOMPLETE";
+ case NUD_REACHABLE: return "NUD_REACHABLE";
+ case NUD_STALE: return "NUD_STALE";
+ case NUD_DELAY: return "NUD_DELAY";
+ case NUD_PROBE: return "NUD_PROBE";
+ case NUD_FAILED: return "NUD_FAILED";
+ case NUD_NOARP: return "NUD_NOARP";
+ case NUD_PERMANENT: return "NUD_PERMANENT";
+ default:
+ return "unknown NUD state: " + String.valueOf(nudState);
+ }
+ }
+
+ public static boolean isNudStateConnected(short nudState) {
+ return ((nudState & (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE)) != 0);
+ }
+
+ public static boolean isNudStateValid(short nudState) {
+ return (isNudStateConnected(nudState) ||
+ ((nudState & (NUD_PROBE|NUD_STALE|NUD_DELAY)) != 0));
+ }
+
+ // Neighbor Cache Entry Flags
+ public static byte NTF_USE = (byte) 0x01;
+ public static byte NTF_SELF = (byte) 0x02;
+ public static byte NTF_MASTER = (byte) 0x04;
+ public static byte NTF_PROXY = (byte) 0x08;
+ public static byte NTF_ROUTER = (byte) 0x80;
+
+ public static String stringForNudFlags(byte flags) {
+ final StringBuilder sb = new StringBuilder();
+ if ((flags & NTF_USE) != 0) {
+ sb.append("NTF_USE");
+ }
+ if ((flags & NTF_SELF) != 0) {
+ if (sb.length() > 0) { sb.append("|"); }
+ sb.append("NTF_SELF");
+ }
+ if ((flags & NTF_MASTER) != 0) {
+ if (sb.length() > 0) { sb.append("|"); }
+ sb.append("NTF_MASTER");
+ }
+ if ((flags & NTF_PROXY) != 0) {
+ if (sb.length() > 0) { sb.append("|");
+ }
+ sb.append("NTF_PROXY"); }
+ if ((flags & NTF_ROUTER) != 0) {
+ if (sb.length() > 0) { sb.append("|"); }
+ sb.append("NTF_ROUTER");
+ }
+ return sb.toString();
+ }
+
+ private static boolean hasAvailableSpace(ByteBuffer byteBuffer) {
+ return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE;
+ }
+
+ public static StructNdMsg parse(ByteBuffer byteBuffer) {
+ if (!hasAvailableSpace(byteBuffer)) { return null; }
+
+ // The ByteOrder must have already been set by the caller. In most
+ // cases ByteOrder.nativeOrder() is correct, with the possible
+ // exception of usage within unittests.
+ final StructNdMsg struct = new StructNdMsg();
+ struct.ndm_family = byteBuffer.get();
+ final byte pad1 = byteBuffer.get();
+ final short pad2 = byteBuffer.getShort();
+ struct.ndm_ifindex = byteBuffer.getInt();
+ struct.ndm_state = byteBuffer.getShort();
+ struct.ndm_flags = byteBuffer.get();
+ struct.ndm_type = byteBuffer.get();
+ return struct;
+ }
+
+ public byte ndm_family;
+ public int ndm_ifindex;
+ public short ndm_state;
+ public byte ndm_flags;
+ public byte ndm_type;
+
+ public StructNdMsg() {
+ ndm_family = (byte) OsConstants.AF_UNSPEC;
+ }
+
+ public void pack(ByteBuffer byteBuffer) {
+ // The ByteOrder must have already been set by the caller. In most
+ // cases ByteOrder.nativeOrder() is correct, with the exception
+ // of usage within unittests.
+ byteBuffer.put(ndm_family);
+ byteBuffer.put((byte) 0); // pad1
+ byteBuffer.putShort((short) 0); // pad2
+ byteBuffer.putInt(ndm_ifindex);
+ byteBuffer.putShort(ndm_state);
+ byteBuffer.put(ndm_flags);
+ byteBuffer.put(ndm_type);
+ }
+
+ public boolean nudConnected() {
+ return isNudStateConnected(ndm_state);
+ }
+
+ public boolean nudValid() {
+ return isNudStateValid(ndm_state);
+ }
+
+ @Override
+ public String toString() {
+ final String stateStr = "" + ndm_state + " (" + stringForNudState(ndm_state) + ")";
+ final String flagsStr = "" + ndm_flags + " (" + stringForNudFlags(ndm_flags) + ")";
+ return "StructNdMsg{ "
+ + "family{" + NetlinkConstants.stringForAddressFamily((int) ndm_family) + "}, "
+ + "ifindex{" + ndm_ifindex + "}, "
+ + "state{" + stateStr + "}, "
+ + "flags{" + flagsStr + "}, "
+ + "type{" + ndm_type + "} "
+ + "}";
+ }
+}
diff --git a/android/net/netlink/StructNdaCacheInfo.java b/android/net/netlink/StructNdaCacheInfo.java
new file mode 100644
index 0000000..16cf563
--- /dev/null
+++ b/android/net/netlink/StructNdaCacheInfo.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2015 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.netlink;
+
+import android.system.Os;
+import android.system.OsConstants;
+
+import java.nio.ByteBuffer;
+
+
+/**
+ * struct nda_cacheinfo
+ *
+ * see: <linux_src>/include/uapi/linux/neighbour.h
+ *
+ * @hide
+ */
+public class StructNdaCacheInfo {
+ // Already aligned.
+ public static final int STRUCT_SIZE = 16;
+
+ private static boolean hasAvailableSpace(ByteBuffer byteBuffer) {
+ return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE;
+ }
+
+ public static StructNdaCacheInfo parse(ByteBuffer byteBuffer) {
+ if (!hasAvailableSpace(byteBuffer)) { return null; }
+
+ // The ByteOrder must have already been set by the caller. In most
+ // cases ByteOrder.nativeOrder() is correct, with the possible
+ // exception of usage within unittests.
+ final StructNdaCacheInfo struct = new StructNdaCacheInfo();
+ struct.ndm_used = byteBuffer.getInt();
+ struct.ndm_confirmed = byteBuffer.getInt();
+ struct.ndm_updated = byteBuffer.getInt();
+ struct.ndm_refcnt = byteBuffer.getInt();
+ return struct;
+ }
+
+ // TODO: investigate whether this can change during device runtime and
+ // decide what (if anything) should be done about that.
+ private static final long CLOCK_TICKS_PER_SECOND = Os.sysconf(OsConstants._SC_CLK_TCK);
+
+ private static long ticksToMilliSeconds(int intClockTicks) {
+ final long longClockTicks = (long) intClockTicks & 0xffffffff;
+ return (longClockTicks * 1000) / CLOCK_TICKS_PER_SECOND;
+ }
+
+ /**
+ * Explanatory notes, for reference.
+ *
+ * Before being returned to user space, the neighbor entry times are
+ * converted to clock_t's like so:
+ *
+ * ndm_used = jiffies_to_clock_t(now - neigh->used);
+ * ndm_confirmed = jiffies_to_clock_t(now - neigh->confirmed);
+ * ndm_updated = jiffies_to_clock_t(now - neigh->updated);
+ *
+ * meaning that these values are expressed as "clock ticks ago". To
+ * convert these clock ticks to seconds divide by sysconf(_SC_CLK_TCK).
+ * When _SC_CLK_TCK is 100, for example, the ndm_* times are expressed
+ * in centiseconds.
+ *
+ * These values are unsigned, but fortunately being expressed as "some
+ * clock ticks ago", these values are typically very small (and
+ * 2^31 centiseconds = 248 days).
+ *
+ * By observation, it appears that:
+ * ndm_used: the last time ARP/ND took place for this neighbor
+ * ndm_confirmed: the last time ARP/ND succeeded for this neighbor OR
+ * higher layer confirmation (TCP or MSG_CONFIRM)
+ * was received
+ * ndm_updated: the time when the current NUD state was entered
+ */
+ public int ndm_used;
+ public int ndm_confirmed;
+ public int ndm_updated;
+ public int ndm_refcnt;
+
+ public StructNdaCacheInfo() {}
+
+ public long lastUsed() {
+ return ticksToMilliSeconds(ndm_used);
+ }
+
+ public long lastConfirmed() {
+ return ticksToMilliSeconds(ndm_confirmed);
+ }
+
+ public long lastUpdated() {
+ return ticksToMilliSeconds(ndm_updated);
+ }
+
+ @Override
+ public String toString() {
+ return "NdaCacheInfo{ "
+ + "ndm_used{" + lastUsed() + "}, "
+ + "ndm_confirmed{" + lastConfirmed() + "}, "
+ + "ndm_updated{" + lastUpdated() + "}, "
+ + "ndm_refcnt{" + ndm_refcnt + "} "
+ + "}";
+ }
+}
diff --git a/android/net/netlink/StructNfGenMsg.java b/android/net/netlink/StructNfGenMsg.java
new file mode 100644
index 0000000..8155977
--- /dev/null
+++ b/android/net/netlink/StructNfGenMsg.java
@@ -0,0 +1,49 @@
+/*
+ * 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 android.net.netlink;
+
+import java.nio.ByteBuffer;
+
+
+/**
+ * struct nfgenmsg
+ *
+ * see <linux_src>/include/uapi/linux/netfilter/nfnetlink.h
+ *
+ * @hide
+ */
+public class StructNfGenMsg {
+ public static final int STRUCT_SIZE = 2 + Short.BYTES;
+
+ public static final int NFNETLINK_V0 = 0;
+
+ final public byte nfgen_family;
+ final public byte version;
+ final public short res_id; // N.B.: this is big endian in the kernel
+
+ public StructNfGenMsg(byte family) {
+ nfgen_family = family;
+ version = (byte) NFNETLINK_V0;
+ res_id = (short) 0;
+ }
+
+ public void pack(ByteBuffer byteBuffer) {
+ byteBuffer.put(nfgen_family);
+ byteBuffer.put(version);
+ byteBuffer.putShort(res_id);
+ }
+}
diff --git a/android/net/netlink/StructNlAttr.java b/android/net/netlink/StructNlAttr.java
new file mode 100644
index 0000000..28a4e88
--- /dev/null
+++ b/android/net/netlink/StructNlAttr.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2015 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.netlink;
+
+import android.net.netlink.NetlinkConstants;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteOrder;
+import java.nio.ByteBuffer;
+
+
+/**
+ * struct nlattr
+ *
+ * see: <linux_src>/include/uapi/linux/netlink.h
+ *
+ * @hide
+ */
+public class StructNlAttr {
+ // Already aligned.
+ public static final int NLA_HEADERLEN = 4;
+ public static final int NLA_F_NESTED = (1 << 15);
+
+ public static short makeNestedType(short type) {
+ return (short) (type | NLA_F_NESTED);
+ }
+
+ // Return a (length, type) object only, without consuming any bytes in
+ // |byteBuffer| and without copying or interpreting any value bytes.
+ // This is used for scanning over a packed set of struct nlattr's,
+ // looking for instances of a particular type.
+ public static StructNlAttr peek(ByteBuffer byteBuffer) {
+ if (byteBuffer == null || byteBuffer.remaining() < NLA_HEADERLEN) {
+ return null;
+ }
+ final int baseOffset = byteBuffer.position();
+
+ // Assume the byte order of the buffer is the expected byte order of the value.
+ final StructNlAttr struct = new StructNlAttr(byteBuffer.order());
+ // The byte order of nla_len and nla_type is always native.
+ final ByteOrder originalOrder = byteBuffer.order();
+ byteBuffer.order(ByteOrder.nativeOrder());
+ try {
+ struct.nla_len = byteBuffer.getShort();
+ struct.nla_type = byteBuffer.getShort();
+ } finally {
+ byteBuffer.order(originalOrder);
+ }
+
+ byteBuffer.position(baseOffset);
+ if (struct.nla_len < NLA_HEADERLEN) {
+ // Malformed.
+ return null;
+ }
+ return struct;
+ }
+
+ public static StructNlAttr parse(ByteBuffer byteBuffer) {
+ final StructNlAttr struct = peek(byteBuffer);
+ if (struct == null || byteBuffer.remaining() < struct.getAlignedLength()) {
+ return null;
+ }
+
+ final int baseOffset = byteBuffer.position();
+ byteBuffer.position(baseOffset + NLA_HEADERLEN);
+
+ int valueLen = ((int) struct.nla_len) & 0xffff;
+ valueLen -= NLA_HEADERLEN;
+ if (valueLen > 0) {
+ struct.nla_value = new byte[valueLen];
+ byteBuffer.get(struct.nla_value, 0, valueLen);
+ byteBuffer.position(baseOffset + struct.getAlignedLength());
+ }
+ return struct;
+ }
+
+ public short nla_len = (short) NLA_HEADERLEN;
+ public short nla_type;
+ public byte[] nla_value;
+
+ // The byte order used to read/write the value member. Netlink length and
+ // type members are always read/written in native order.
+ private ByteOrder mByteOrder = ByteOrder.nativeOrder();
+
+ public StructNlAttr() {}
+
+ public StructNlAttr(ByteOrder byteOrder) {
+ mByteOrder = byteOrder;
+ }
+
+ public StructNlAttr(short type, byte value) {
+ nla_type = type;
+ setValue(new byte[1]);
+ nla_value[0] = value;
+ }
+
+ public StructNlAttr(short type, short value) {
+ this(type, value, ByteOrder.nativeOrder());
+ }
+
+ public StructNlAttr(short type, short value, ByteOrder order) {
+ this(order);
+ nla_type = type;
+ setValue(new byte[Short.BYTES]);
+ getValueAsByteBuffer().putShort(value);
+ }
+
+ public StructNlAttr(short type, int value) {
+ this(type, value, ByteOrder.nativeOrder());
+ }
+
+ public StructNlAttr(short type, int value, ByteOrder order) {
+ this(order);
+ nla_type = type;
+ setValue(new byte[Integer.BYTES]);
+ getValueAsByteBuffer().putInt(value);
+ }
+
+ public StructNlAttr(short type, InetAddress ip) {
+ nla_type = type;
+ setValue(ip.getAddress());
+ }
+
+ public StructNlAttr(short type, StructNlAttr... nested) {
+ this();
+ nla_type = makeNestedType(type);
+
+ int payloadLength = 0;
+ for (StructNlAttr nla : nested) payloadLength += nla.getAlignedLength();
+ setValue(new byte[payloadLength]);
+
+ final ByteBuffer buf = getValueAsByteBuffer();
+ for (StructNlAttr nla : nested) {
+ nla.pack(buf);
+ }
+ }
+
+ public int getAlignedLength() {
+ return NetlinkConstants.alignedLengthOf(nla_len);
+ }
+
+ public ByteBuffer getValueAsByteBuffer() {
+ if (nla_value == null) { return null; }
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(nla_value);
+ byteBuffer.order(mByteOrder);
+ return byteBuffer;
+ }
+
+ public int getValueAsInt(int defaultValue) {
+ final ByteBuffer byteBuffer = getValueAsByteBuffer();
+ if (byteBuffer == null || byteBuffer.remaining() != Integer.BYTES) {
+ return defaultValue;
+ }
+ return getValueAsByteBuffer().getInt();
+ }
+
+ public InetAddress getValueAsInetAddress() {
+ if (nla_value == null) { return null; }
+
+ try {
+ return InetAddress.getByAddress(nla_value);
+ } catch (UnknownHostException ignored) {
+ return null;
+ }
+ }
+
+ public void pack(ByteBuffer byteBuffer) {
+ final ByteOrder originalOrder = byteBuffer.order();
+ final int originalPosition = byteBuffer.position();
+
+ byteBuffer.order(ByteOrder.nativeOrder());
+ try {
+ byteBuffer.putShort(nla_len);
+ byteBuffer.putShort(nla_type);
+ if (nla_value != null) byteBuffer.put(nla_value);
+ } finally {
+ byteBuffer.order(originalOrder);
+ }
+ byteBuffer.position(originalPosition + getAlignedLength());
+ }
+
+ private void setValue(byte[] value) {
+ nla_value = value;
+ nla_len = (short) (NLA_HEADERLEN + ((nla_value != null) ? nla_value.length : 0));
+ }
+
+ @Override
+ public String toString() {
+ return "StructNlAttr{ "
+ + "nla_len{" + nla_len + "}, "
+ + "nla_type{" + nla_type + "}, "
+ + "nla_value{" + NetlinkConstants.hexify(nla_value) + "}, "
+ + "}";
+ }
+}
diff --git a/android/net/netlink/StructNlMsgErr.java b/android/net/netlink/StructNlMsgErr.java
new file mode 100644
index 0000000..6fcc6e6
--- /dev/null
+++ b/android/net/netlink/StructNlMsgErr.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2015 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.netlink;
+
+import android.net.netlink.NetlinkConstants;
+import android.net.netlink.StructNlMsgHdr;
+
+import java.nio.ByteBuffer;
+
+
+/**
+ * struct nlmsgerr
+ *
+ * see <linux_src>/include/uapi/linux/netlink.h
+ *
+ * @hide
+ */
+public class StructNlMsgErr {
+ public static final int STRUCT_SIZE = Integer.BYTES + StructNlMsgHdr.STRUCT_SIZE;
+
+ public static boolean hasAvailableSpace(ByteBuffer byteBuffer) {
+ return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE;
+ }
+
+ public static StructNlMsgErr parse(ByteBuffer byteBuffer) {
+ if (!hasAvailableSpace(byteBuffer)) { return null; }
+
+ // The ByteOrder must have already been set by the caller. In most
+ // cases ByteOrder.nativeOrder() is correct, with the exception
+ // of usage within unittests.
+ final StructNlMsgErr struct = new StructNlMsgErr();
+ struct.error = byteBuffer.getInt();
+ struct.msg = StructNlMsgHdr.parse(byteBuffer);
+ return struct;
+ }
+
+ public int error;
+ public StructNlMsgHdr msg;
+
+ public void pack(ByteBuffer byteBuffer) {
+ // The ByteOrder must have already been set by the caller. In most
+ // cases ByteOrder.nativeOrder() is correct, with the possible
+ // exception of usage within unittests.
+ byteBuffer.putInt(error);
+ if (msg != null) {
+ msg.pack(byteBuffer);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "StructNlMsgErr{ "
+ + "error{" + error + "}, "
+ + "msg{" + (msg == null ? "" : msg.toString()) + "} "
+ + "}";
+ }
+}
diff --git a/android/net/netlink/StructNlMsgHdr.java b/android/net/netlink/StructNlMsgHdr.java
new file mode 100644
index 0000000..98ab5e7
--- /dev/null
+++ b/android/net/netlink/StructNlMsgHdr.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2015 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.netlink;
+
+import android.net.netlink.NetlinkConstants;
+import java.nio.ByteBuffer;
+
+
+/**
+ * struct nlmsghdr
+ *
+ * see <linux_src>/include/uapi/linux/netlink.h
+ *
+ * @hide
+ */
+public class StructNlMsgHdr {
+ // Already aligned.
+ public static final int STRUCT_SIZE = 16;
+
+ public static final short NLM_F_REQUEST = 0x0001;
+ public static final short NLM_F_MULTI = 0x0002;
+ public static final short NLM_F_ACK = 0x0004;
+ public static final short NLM_F_ECHO = 0x0008;
+ // Flags for a GET request.
+ public static final short NLM_F_ROOT = 0x0100;
+ public static final short NLM_F_MATCH = 0x0200;
+ public static final short NLM_F_DUMP = NLM_F_ROOT|NLM_F_MATCH;
+ // Flags for a NEW request.
+ public static final short NLM_F_REPLACE = 0x100;
+ public static final short NLM_F_EXCL = 0x200;
+ public static final short NLM_F_CREATE = 0x400;
+ public static final short NLM_F_APPEND = 0x800;
+
+
+ public static String stringForNlMsgFlags(short flags) {
+ final StringBuilder sb = new StringBuilder();
+ if ((flags & NLM_F_REQUEST) != 0) {
+ sb.append("NLM_F_REQUEST");
+ }
+ if ((flags & NLM_F_MULTI) != 0) {
+ if (sb.length() > 0) { sb.append("|"); }
+ sb.append("NLM_F_MULTI");
+ }
+ if ((flags & NLM_F_ACK) != 0) {
+ if (sb.length() > 0) { sb.append("|"); }
+ sb.append("NLM_F_ACK");
+ }
+ if ((flags & NLM_F_ECHO) != 0) {
+ if (sb.length() > 0) { sb.append("|"); }
+ sb.append("NLM_F_ECHO");
+ }
+ if ((flags & NLM_F_ROOT) != 0) {
+ if (sb.length() > 0) { sb.append("|"); }
+ sb.append("NLM_F_ROOT");
+ }
+ if ((flags & NLM_F_MATCH) != 0) {
+ if (sb.length() > 0) { sb.append("|"); }
+ sb.append("NLM_F_MATCH");
+ }
+ return sb.toString();
+ }
+
+ public static boolean hasAvailableSpace(ByteBuffer byteBuffer) {
+ return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE;
+ }
+
+ public static StructNlMsgHdr parse(ByteBuffer byteBuffer) {
+ if (!hasAvailableSpace(byteBuffer)) { return null; }
+
+ // The ByteOrder must have already been set by the caller. In most
+ // cases ByteOrder.nativeOrder() is correct, with the exception
+ // of usage within unittests.
+ final StructNlMsgHdr struct = new StructNlMsgHdr();
+ struct.nlmsg_len = byteBuffer.getInt();
+ struct.nlmsg_type = byteBuffer.getShort();
+ struct.nlmsg_flags = byteBuffer.getShort();
+ struct.nlmsg_seq = byteBuffer.getInt();
+ struct.nlmsg_pid = byteBuffer.getInt();
+
+ if (struct.nlmsg_len < STRUCT_SIZE) {
+ // Malformed.
+ return null;
+ }
+ return struct;
+ }
+
+ public int nlmsg_len;
+ public short nlmsg_type;
+ public short nlmsg_flags;
+ public int nlmsg_seq;
+ public int nlmsg_pid;
+
+ public StructNlMsgHdr() {
+ nlmsg_len = 0;
+ nlmsg_type = 0;
+ nlmsg_flags = 0;
+ nlmsg_seq = 0;
+ nlmsg_pid = 0;
+ }
+
+ public void pack(ByteBuffer byteBuffer) {
+ // The ByteOrder must have already been set by the caller. In most
+ // cases ByteOrder.nativeOrder() is correct, with the possible
+ // exception of usage within unittests.
+ byteBuffer.putInt(nlmsg_len);
+ byteBuffer.putShort(nlmsg_type);
+ byteBuffer.putShort(nlmsg_flags);
+ byteBuffer.putInt(nlmsg_seq);
+ byteBuffer.putInt(nlmsg_pid);
+ }
+
+ @Override
+ public String toString() {
+ final String typeStr = "" + nlmsg_type
+ + "(" + NetlinkConstants.stringForNlMsgType(nlmsg_type) + ")";
+ final String flagsStr = "" + nlmsg_flags
+ + "(" + stringForNlMsgFlags(nlmsg_flags) + ")";
+ return "StructNlMsgHdr{ "
+ + "nlmsg_len{" + nlmsg_len + "}, "
+ + "nlmsg_type{" + typeStr + "}, "
+ + "nlmsg_flags{" + flagsStr + ")}, "
+ + "nlmsg_seq{" + nlmsg_seq + "}, "
+ + "nlmsg_pid{" + nlmsg_pid + "} "
+ + "}";
+ }
+}
diff --git a/android/net/nsd/DnsSdTxtRecord.java b/android/net/nsd/DnsSdTxtRecord.java
new file mode 100644
index 0000000..e4a91c5
--- /dev/null
+++ b/android/net/nsd/DnsSdTxtRecord.java
@@ -0,0 +1,325 @@
+/* -*- Mode: Java; tab-width: 4 -*-
+ *
+ * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
+ *
+ * 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.
+
+ To do:
+ - implement remove()
+ - fix set() to replace existing values
+ */
+
+package android.net.nsd;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+import java.util.Arrays;
+
+/**
+ * This class handles TXT record data for DNS based service discovery as specified at
+ * http://tools.ietf.org/html/draft-cheshire-dnsext-dns-sd-11
+ *
+ * DNS-SD specifies that a TXT record corresponding to an SRV record consist of
+ * a packed array of bytes, each preceded by a length byte. Each string
+ * is an attribute-value pair.
+ *
+ * The DnsSdTxtRecord object stores the entire TXT data as a single byte array, traversing it
+ * as need be to implement its various methods.
+ * @hide
+ *
+ */
+public class DnsSdTxtRecord implements Parcelable {
+ private static final byte mSeperator = '=';
+
+ private byte[] mData;
+
+ /** Constructs a new, empty TXT record. */
+ public DnsSdTxtRecord() {
+ mData = new byte[0];
+ }
+
+ /** Constructs a new TXT record from a byte array in the standard format. */
+ public DnsSdTxtRecord(byte[] data) {
+ mData = (byte[]) data.clone();
+ }
+
+ /** Copy constructor */
+ public DnsSdTxtRecord(DnsSdTxtRecord src) {
+ if (src != null && src.mData != null) {
+ mData = (byte[]) src.mData.clone();
+ }
+ }
+
+ /**
+ * Set a key/value pair. Setting an existing key will replace its value.
+ * @param key Must be ascii with no '='
+ * @param value matching value to key
+ */
+ public void set(String key, String value) {
+ byte[] keyBytes;
+ byte[] valBytes;
+ int valLen;
+
+ if (value != null) {
+ valBytes = value.getBytes();
+ valLen = valBytes.length;
+ } else {
+ valBytes = null;
+ valLen = 0;
+ }
+
+ try {
+ keyBytes = key.getBytes("US-ASCII");
+ }
+ catch (java.io.UnsupportedEncodingException e) {
+ throw new IllegalArgumentException("key should be US-ASCII");
+ }
+
+ for (int i = 0; i < keyBytes.length; i++) {
+ if (keyBytes[i] == '=') {
+ throw new IllegalArgumentException("= is not a valid character in key");
+ }
+ }
+
+ if (keyBytes.length + valLen >= 255) {
+ throw new IllegalArgumentException("Key and Value length cannot exceed 255 bytes");
+ }
+
+ int currentLoc = remove(key);
+ if (currentLoc == -1)
+ currentLoc = keyCount();
+
+ insert(keyBytes, valBytes, currentLoc);
+ }
+
+ /**
+ * Get a value for a key
+ *
+ * @param key
+ * @return The value associated with the key
+ */
+ public String get(String key) {
+ byte[] val = this.getValue(key);
+ return val != null ? new String(val) : null;
+ }
+
+ /** Remove a key/value pair. If found, returns the index or -1 if not found */
+ public int remove(String key) {
+ int avStart = 0;
+
+ for (int i=0; avStart < mData.length; i++) {
+ int avLen = mData[avStart];
+ if (key.length() <= avLen &&
+ (key.length() == avLen || mData[avStart + key.length() + 1] == mSeperator)) {
+ String s = new String(mData, avStart + 1, key.length());
+ if (0 == key.compareToIgnoreCase(s)) {
+ byte[] oldBytes = mData;
+ mData = new byte[oldBytes.length - avLen - 1];
+ System.arraycopy(oldBytes, 0, mData, 0, avStart);
+ System.arraycopy(oldBytes, avStart + avLen + 1, mData, avStart,
+ oldBytes.length - avStart - avLen - 1);
+ return i;
+ }
+ }
+ avStart += (0xFF & (avLen + 1));
+ }
+ return -1;
+ }
+
+ /** Return the count of keys */
+ public int keyCount() {
+ int count = 0, nextKey;
+ for (nextKey = 0; nextKey < mData.length; count++) {
+ nextKey += (0xFF & (mData[nextKey] + 1));
+ }
+ return count;
+ }
+
+ /** Return true if key is present, false if not. */
+ public boolean contains(String key) {
+ String s = null;
+ for (int i = 0; null != (s = this.getKey(i)); i++) {
+ if (0 == key.compareToIgnoreCase(s)) return true;
+ }
+ return false;
+ }
+
+ /* Gets the size in bytes */
+ public int size() {
+ return mData.length;
+ }
+
+ /* Gets the raw data in bytes */
+ public byte[] getRawData() {
+ return (byte[]) mData.clone();
+ }
+
+ private void insert(byte[] keyBytes, byte[] value, int index) {
+ byte[] oldBytes = mData;
+ int valLen = (value != null) ? value.length : 0;
+ int insertion = 0;
+ int newLen, avLen;
+
+ for (int i = 0; i < index && insertion < mData.length; i++) {
+ insertion += (0xFF & (mData[insertion] + 1));
+ }
+
+ avLen = keyBytes.length + valLen + (value != null ? 1 : 0);
+ newLen = avLen + oldBytes.length + 1;
+
+ mData = new byte[newLen];
+ System.arraycopy(oldBytes, 0, mData, 0, insertion);
+ int secondHalfLen = oldBytes.length - insertion;
+ System.arraycopy(oldBytes, insertion, mData, newLen - secondHalfLen, secondHalfLen);
+ mData[insertion] = (byte) avLen;
+ System.arraycopy(keyBytes, 0, mData, insertion + 1, keyBytes.length);
+ if (value != null) {
+ mData[insertion + 1 + keyBytes.length] = mSeperator;
+ System.arraycopy(value, 0, mData, insertion + keyBytes.length + 2, valLen);
+ }
+ }
+
+ /** Return a key in the TXT record by zero-based index. Returns null if index exceeds the total number of keys. */
+ private String getKey(int index) {
+ int avStart = 0;
+
+ for (int i=0; i < index && avStart < mData.length; i++) {
+ avStart += mData[avStart] + 1;
+ }
+
+ if (avStart < mData.length) {
+ int avLen = mData[avStart];
+ int aLen = 0;
+
+ for (aLen=0; aLen < avLen; aLen++) {
+ if (mData[avStart + aLen + 1] == mSeperator) break;
+ }
+ return new String(mData, avStart + 1, aLen);
+ }
+ return null;
+ }
+
+ /**
+ * Look up a key in the TXT record by zero-based index and return its value.
+ * Returns null if index exceeds the total number of keys.
+ * Returns null if the key is present with no value.
+ */
+ private byte[] getValue(int index) {
+ int avStart = 0;
+ byte[] value = null;
+
+ for (int i=0; i < index && avStart < mData.length; i++) {
+ avStart += mData[avStart] + 1;
+ }
+
+ if (avStart < mData.length) {
+ int avLen = mData[avStart];
+ int aLen = 0;
+
+ for (aLen=0; aLen < avLen; aLen++) {
+ if (mData[avStart + aLen + 1] == mSeperator) {
+ value = new byte[avLen - aLen - 1];
+ System.arraycopy(mData, avStart + aLen + 2, value, 0, avLen - aLen - 1);
+ break;
+ }
+ }
+ }
+ return value;
+ }
+
+ private String getValueAsString(int index) {
+ byte[] value = this.getValue(index);
+ return value != null ? new String(value) : null;
+ }
+
+ private byte[] getValue(String forKey) {
+ String s = null;
+ int i;
+
+ for (i = 0; null != (s = this.getKey(i)); i++) {
+ if (0 == forKey.compareToIgnoreCase(s)) {
+ return this.getValue(i);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return a string representation.
+ * Example : {key1=value1},{key2=value2}..
+ *
+ * For a key say like "key3" with null value
+ * {key1=value1},{key2=value2}{key3}
+ */
+ public String toString() {
+ String a, result = null;
+
+ for (int i = 0; null != (a = this.getKey(i)); i++) {
+ String av = "{" + a;
+ String val = this.getValueAsString(i);
+ if (val != null)
+ av += "=" + val + "}";
+ else
+ av += "}";
+ if (result == null)
+ result = av;
+ else
+ result = result + ", " + av;
+ }
+ return result != null ? result : "";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof DnsSdTxtRecord)) {
+ return false;
+ }
+
+ DnsSdTxtRecord record = (DnsSdTxtRecord)o;
+ return Arrays.equals(record.mData, mData);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(mData);
+ }
+
+ /** Implement the Parcelable interface */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeByteArray(mData);
+ }
+
+ /** Implement the Parcelable interface */
+ public static final @android.annotation.NonNull Creator<DnsSdTxtRecord> CREATOR =
+ new Creator<DnsSdTxtRecord>() {
+ public DnsSdTxtRecord createFromParcel(Parcel in) {
+ DnsSdTxtRecord info = new DnsSdTxtRecord();
+ in.readByteArray(info.mData);
+ return info;
+ }
+
+ public DnsSdTxtRecord[] newArray(int size) {
+ return new DnsSdTxtRecord[size];
+ }
+ };
+}
diff --git a/android/net/nsd/NsdManager.java b/android/net/nsd/NsdManager.java
new file mode 100644
index 0000000..535bf67
--- /dev/null
+++ b/android/net/nsd/NsdManager.java
@@ -0,0 +1,656 @@
+/*
+ * Copyright (C) 2012 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.nsd;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.internal.util.Preconditions.checkStringNotEmpty;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * The Network Service Discovery Manager class provides the API to discover services
+ * on a network. As an example, if device A and device B are connected over a Wi-Fi
+ * network, a game registered on device A can be discovered by a game on device
+ * B. Another example use case is an application discovering printers on the network.
+ *
+ * <p> The API currently supports DNS based service discovery and discovery is currently
+ * limited to a local network over Multicast DNS. DNS service discovery is described at
+ * http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt
+ *
+ * <p> The API is asynchronous and responses to requests from an application are on listener
+ * callbacks on a seperate internal thread.
+ *
+ * <p> There are three main operations the API supports - registration, discovery and resolution.
+ * <pre>
+ * Application start
+ * |
+ * |
+ * | onServiceRegistered()
+ * Register any local services /
+ * to be advertised with \
+ * registerService() onRegistrationFailed()
+ * |
+ * |
+ * discoverServices()
+ * |
+ * Maintain a list to track
+ * discovered services
+ * |
+ * |--------->
+ * | |
+ * | onServiceFound()
+ * | |
+ * | add service to list
+ * | |
+ * |<----------
+ * |
+ * |--------->
+ * | |
+ * | onServiceLost()
+ * | |
+ * | remove service from list
+ * | |
+ * |<----------
+ * |
+ * |
+ * | Connect to a service
+ * | from list ?
+ * |
+ * resolveService()
+ * |
+ * onServiceResolved()
+ * |
+ * Establish connection to service
+ * with the host and port information
+ *
+ * </pre>
+ * An application that needs to advertise itself over a network for other applications to
+ * discover it can do so with a call to {@link #registerService}. If Example is a http based
+ * application that can provide HTML data to peer services, it can register a name "Example"
+ * with service type "_http._tcp". A successful registration is notified with a callback to
+ * {@link RegistrationListener#onServiceRegistered} and a failure to register is notified
+ * over {@link RegistrationListener#onRegistrationFailed}
+ *
+ * <p> A peer application looking for http services can initiate a discovery for "_http._tcp"
+ * with a call to {@link #discoverServices}. A service found is notified with a callback
+ * to {@link DiscoveryListener#onServiceFound} and a service lost is notified on
+ * {@link DiscoveryListener#onServiceLost}.
+ *
+ * <p> Once the peer application discovers the "Example" http service, and either needs to read the
+ * attributes of the service or wants to receive data from the "Example" application, it can
+ * initiate a resolve with {@link #resolveService} to resolve the attributes, host, and port
+ * details. A successful resolve is notified on {@link ResolveListener#onServiceResolved} and a
+ * failure is notified on {@link ResolveListener#onResolveFailed}.
+ *
+ * Applications can reserve for a service type at
+ * http://www.iana.org/form/ports-service. Existing services can be found at
+ * http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml
+ *
+ * {@see NsdServiceInfo}
+ */
+@SystemService(Context.NSD_SERVICE)
+public final class NsdManager {
+ private static final String TAG = NsdManager.class.getSimpleName();
+ private static final boolean DBG = false;
+
+ /**
+ * Broadcast intent action to indicate whether network service discovery is
+ * enabled or disabled. An extra {@link #EXTRA_NSD_STATE} provides the state
+ * information as int.
+ *
+ * @see #EXTRA_NSD_STATE
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_NSD_STATE_CHANGED = "android.net.nsd.STATE_CHANGED";
+
+ /**
+ * The lookup key for an int that indicates whether network service discovery is enabled
+ * or disabled. Retrieve it with {@link android.content.Intent#getIntExtra(String,int)}.
+ *
+ * @see #NSD_STATE_DISABLED
+ * @see #NSD_STATE_ENABLED
+ */
+ public static final String EXTRA_NSD_STATE = "nsd_state";
+
+ /**
+ * Network service discovery is disabled
+ *
+ * @see #ACTION_NSD_STATE_CHANGED
+ */
+ public static final int NSD_STATE_DISABLED = 1;
+
+ /**
+ * Network service discovery is enabled
+ *
+ * @see #ACTION_NSD_STATE_CHANGED
+ */
+ public static final int NSD_STATE_ENABLED = 2;
+
+ private static final int BASE = Protocol.BASE_NSD_MANAGER;
+
+ /** @hide */
+ public static final int DISCOVER_SERVICES = BASE + 1;
+ /** @hide */
+ public static final int DISCOVER_SERVICES_STARTED = BASE + 2;
+ /** @hide */
+ public static final int DISCOVER_SERVICES_FAILED = BASE + 3;
+ /** @hide */
+ public static final int SERVICE_FOUND = BASE + 4;
+ /** @hide */
+ public static final int SERVICE_LOST = BASE + 5;
+
+ /** @hide */
+ public static final int STOP_DISCOVERY = BASE + 6;
+ /** @hide */
+ public static final int STOP_DISCOVERY_FAILED = BASE + 7;
+ /** @hide */
+ public static final int STOP_DISCOVERY_SUCCEEDED = BASE + 8;
+
+ /** @hide */
+ public static final int REGISTER_SERVICE = BASE + 9;
+ /** @hide */
+ public static final int REGISTER_SERVICE_FAILED = BASE + 10;
+ /** @hide */
+ public static final int REGISTER_SERVICE_SUCCEEDED = BASE + 11;
+
+ /** @hide */
+ public static final int UNREGISTER_SERVICE = BASE + 12;
+ /** @hide */
+ public static final int UNREGISTER_SERVICE_FAILED = BASE + 13;
+ /** @hide */
+ public static final int UNREGISTER_SERVICE_SUCCEEDED = BASE + 14;
+
+ /** @hide */
+ public static final int RESOLVE_SERVICE = BASE + 18;
+ /** @hide */
+ public static final int RESOLVE_SERVICE_FAILED = BASE + 19;
+ /** @hide */
+ public static final int RESOLVE_SERVICE_SUCCEEDED = BASE + 20;
+
+ /** @hide */
+ public static final int ENABLE = BASE + 24;
+ /** @hide */
+ public static final int DISABLE = BASE + 25;
+
+ /** @hide */
+ public static final int NATIVE_DAEMON_EVENT = BASE + 26;
+
+ /** Dns based service discovery protocol */
+ public static final int PROTOCOL_DNS_SD = 0x0001;
+
+ private static final SparseArray<String> EVENT_NAMES = new SparseArray<>();
+ static {
+ EVENT_NAMES.put(DISCOVER_SERVICES, "DISCOVER_SERVICES");
+ EVENT_NAMES.put(DISCOVER_SERVICES_STARTED, "DISCOVER_SERVICES_STARTED");
+ EVENT_NAMES.put(DISCOVER_SERVICES_FAILED, "DISCOVER_SERVICES_FAILED");
+ EVENT_NAMES.put(SERVICE_FOUND, "SERVICE_FOUND");
+ EVENT_NAMES.put(SERVICE_LOST, "SERVICE_LOST");
+ EVENT_NAMES.put(STOP_DISCOVERY, "STOP_DISCOVERY");
+ EVENT_NAMES.put(STOP_DISCOVERY_FAILED, "STOP_DISCOVERY_FAILED");
+ EVENT_NAMES.put(STOP_DISCOVERY_SUCCEEDED, "STOP_DISCOVERY_SUCCEEDED");
+ EVENT_NAMES.put(REGISTER_SERVICE, "REGISTER_SERVICE");
+ EVENT_NAMES.put(REGISTER_SERVICE_FAILED, "REGISTER_SERVICE_FAILED");
+ EVENT_NAMES.put(REGISTER_SERVICE_SUCCEEDED, "REGISTER_SERVICE_SUCCEEDED");
+ EVENT_NAMES.put(UNREGISTER_SERVICE, "UNREGISTER_SERVICE");
+ EVENT_NAMES.put(UNREGISTER_SERVICE_FAILED, "UNREGISTER_SERVICE_FAILED");
+ EVENT_NAMES.put(UNREGISTER_SERVICE_SUCCEEDED, "UNREGISTER_SERVICE_SUCCEEDED");
+ EVENT_NAMES.put(RESOLVE_SERVICE, "RESOLVE_SERVICE");
+ EVENT_NAMES.put(RESOLVE_SERVICE_FAILED, "RESOLVE_SERVICE_FAILED");
+ EVENT_NAMES.put(RESOLVE_SERVICE_SUCCEEDED, "RESOLVE_SERVICE_SUCCEEDED");
+ EVENT_NAMES.put(ENABLE, "ENABLE");
+ EVENT_NAMES.put(DISABLE, "DISABLE");
+ EVENT_NAMES.put(NATIVE_DAEMON_EVENT, "NATIVE_DAEMON_EVENT");
+ }
+
+ /** @hide */
+ public static String nameOf(int event) {
+ String name = EVENT_NAMES.get(event);
+ if (name == null) {
+ return Integer.toString(event);
+ }
+ return name;
+ }
+
+ private static final int FIRST_LISTENER_KEY = 1;
+
+ private final INsdManager mService;
+ private final Context mContext;
+
+ private int mListenerKey = FIRST_LISTENER_KEY;
+ private final SparseArray mListenerMap = new SparseArray();
+ private final SparseArray<NsdServiceInfo> mServiceMap = new SparseArray<>();
+ private final Object mMapLock = new Object();
+
+ private final AsyncChannel mAsyncChannel = new AsyncChannel();
+ private ServiceHandler mHandler;
+ private final CountDownLatch mConnected = new CountDownLatch(1);
+
+ /**
+ * Create a new Nsd instance. Applications use
+ * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
+ * {@link android.content.Context#NSD_SERVICE Context.NSD_SERVICE}.
+ * @param service the Binder interface
+ * @hide - hide this because it takes in a parameter of type INsdManager, which
+ * is a system private class.
+ */
+ public NsdManager(Context context, INsdManager service) {
+ mService = service;
+ mContext = context;
+ init();
+ }
+
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public void disconnect() {
+ mAsyncChannel.disconnect();
+ mHandler.getLooper().quitSafely();
+ }
+
+ /**
+ * Failures are passed with {@link RegistrationListener#onRegistrationFailed},
+ * {@link RegistrationListener#onUnregistrationFailed},
+ * {@link DiscoveryListener#onStartDiscoveryFailed},
+ * {@link DiscoveryListener#onStopDiscoveryFailed} or {@link ResolveListener#onResolveFailed}.
+ *
+ * Indicates that the operation failed due to an internal error.
+ */
+ public static final int FAILURE_INTERNAL_ERROR = 0;
+
+ /**
+ * Indicates that the operation failed because it is already active.
+ */
+ public static final int FAILURE_ALREADY_ACTIVE = 3;
+
+ /**
+ * Indicates that the operation failed because the maximum outstanding
+ * requests from the applications have reached.
+ */
+ public static final int FAILURE_MAX_LIMIT = 4;
+
+ /** Interface for callback invocation for service discovery */
+ public interface DiscoveryListener {
+
+ public void onStartDiscoveryFailed(String serviceType, int errorCode);
+
+ public void onStopDiscoveryFailed(String serviceType, int errorCode);
+
+ public void onDiscoveryStarted(String serviceType);
+
+ public void onDiscoveryStopped(String serviceType);
+
+ public void onServiceFound(NsdServiceInfo serviceInfo);
+
+ public void onServiceLost(NsdServiceInfo serviceInfo);
+ }
+
+ /** Interface for callback invocation for service registration */
+ public interface RegistrationListener {
+
+ public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode);
+
+ public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode);
+
+ public void onServiceRegistered(NsdServiceInfo serviceInfo);
+
+ public void onServiceUnregistered(NsdServiceInfo serviceInfo);
+ }
+
+ /** Interface for callback invocation for service resolution */
+ public interface ResolveListener {
+
+ public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode);
+
+ public void onServiceResolved(NsdServiceInfo serviceInfo);
+ }
+
+ @VisibleForTesting
+ class ServiceHandler extends Handler {
+ ServiceHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ final int what = message.what;
+ final int key = message.arg2;
+ switch (what) {
+ case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
+ mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
+ return;
+ case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
+ mConnected.countDown();
+ return;
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+ Log.e(TAG, "Channel lost");
+ return;
+ default:
+ break;
+ }
+ final Object listener;
+ final NsdServiceInfo ns;
+ synchronized (mMapLock) {
+ listener = mListenerMap.get(key);
+ ns = mServiceMap.get(key);
+ }
+ if (listener == null) {
+ Log.d(TAG, "Stale key " + message.arg2);
+ return;
+ }
+ if (DBG) {
+ Log.d(TAG, "received " + nameOf(what) + " for key " + key + ", service " + ns);
+ }
+ switch (what) {
+ case DISCOVER_SERVICES_STARTED:
+ String s = getNsdServiceInfoType((NsdServiceInfo) message.obj);
+ ((DiscoveryListener) listener).onDiscoveryStarted(s);
+ break;
+ case DISCOVER_SERVICES_FAILED:
+ removeListener(key);
+ ((DiscoveryListener) listener).onStartDiscoveryFailed(getNsdServiceInfoType(ns),
+ message.arg1);
+ break;
+ case SERVICE_FOUND:
+ ((DiscoveryListener) listener).onServiceFound((NsdServiceInfo) message.obj);
+ break;
+ case SERVICE_LOST:
+ ((DiscoveryListener) listener).onServiceLost((NsdServiceInfo) message.obj);
+ break;
+ case STOP_DISCOVERY_FAILED:
+ // TODO: failure to stop discovery should be internal and retried internally, as
+ // the effect for the client is indistinguishable from STOP_DISCOVERY_SUCCEEDED
+ removeListener(key);
+ ((DiscoveryListener) listener).onStopDiscoveryFailed(getNsdServiceInfoType(ns),
+ message.arg1);
+ break;
+ case STOP_DISCOVERY_SUCCEEDED:
+ removeListener(key);
+ ((DiscoveryListener) listener).onDiscoveryStopped(getNsdServiceInfoType(ns));
+ break;
+ case REGISTER_SERVICE_FAILED:
+ removeListener(key);
+ ((RegistrationListener) listener).onRegistrationFailed(ns, message.arg1);
+ break;
+ case REGISTER_SERVICE_SUCCEEDED:
+ ((RegistrationListener) listener).onServiceRegistered(
+ (NsdServiceInfo) message.obj);
+ break;
+ case UNREGISTER_SERVICE_FAILED:
+ removeListener(key);
+ ((RegistrationListener) listener).onUnregistrationFailed(ns, message.arg1);
+ break;
+ case UNREGISTER_SERVICE_SUCCEEDED:
+ // TODO: do not unregister listener until service is unregistered, or provide
+ // alternative way for unregistering ?
+ removeListener(message.arg2);
+ ((RegistrationListener) listener).onServiceUnregistered(ns);
+ break;
+ case RESOLVE_SERVICE_FAILED:
+ removeListener(key);
+ ((ResolveListener) listener).onResolveFailed(ns, message.arg1);
+ break;
+ case RESOLVE_SERVICE_SUCCEEDED:
+ removeListener(key);
+ ((ResolveListener) listener).onServiceResolved((NsdServiceInfo) message.obj);
+ break;
+ default:
+ Log.d(TAG, "Ignored " + message);
+ break;
+ }
+ }
+ }
+
+ private int nextListenerKey() {
+ // Ensure mListenerKey >= FIRST_LISTENER_KEY;
+ mListenerKey = Math.max(FIRST_LISTENER_KEY, mListenerKey + 1);
+ return mListenerKey;
+ }
+
+ // Assert that the listener is not in the map, then add it and returns its key
+ private int putListener(Object listener, NsdServiceInfo s) {
+ checkListener(listener);
+ final int key;
+ synchronized (mMapLock) {
+ int valueIndex = mListenerMap.indexOfValue(listener);
+ checkArgument(valueIndex == -1, "listener already in use");
+ key = nextListenerKey();
+ mListenerMap.put(key, listener);
+ mServiceMap.put(key, s);
+ }
+ return key;
+ }
+
+ private void removeListener(int key) {
+ synchronized (mMapLock) {
+ mListenerMap.remove(key);
+ mServiceMap.remove(key);
+ }
+ }
+
+ private int getListenerKey(Object listener) {
+ checkListener(listener);
+ synchronized (mMapLock) {
+ int valueIndex = mListenerMap.indexOfValue(listener);
+ checkArgument(valueIndex != -1, "listener not registered");
+ return mListenerMap.keyAt(valueIndex);
+ }
+ }
+
+ private static String getNsdServiceInfoType(NsdServiceInfo s) {
+ if (s == null) return "?";
+ return s.getServiceType();
+ }
+
+ /**
+ * Initialize AsyncChannel
+ */
+ private void init() {
+ final Messenger messenger = getMessenger();
+ if (messenger == null) {
+ fatal("Failed to obtain service Messenger");
+ }
+ HandlerThread t = new HandlerThread("NsdManager");
+ t.start();
+ mHandler = new ServiceHandler(t.getLooper());
+ mAsyncChannel.connect(mContext, mHandler, messenger);
+ try {
+ mConnected.await();
+ } catch (InterruptedException e) {
+ fatal("Interrupted wait at init");
+ }
+ }
+
+ private static void fatal(String msg) {
+ Log.e(TAG, msg);
+ throw new RuntimeException(msg);
+ }
+
+ /**
+ * Register a service to be discovered by other services.
+ *
+ * <p> The function call immediately returns after sending a request to register service
+ * to the framework. The application is notified of a successful registration
+ * through the callback {@link RegistrationListener#onServiceRegistered} or a failure
+ * through {@link RegistrationListener#onRegistrationFailed}.
+ *
+ * <p> The application should call {@link #unregisterService} when the service
+ * registration is no longer required, and/or whenever the application is stopped.
+ *
+ * @param serviceInfo The service being registered
+ * @param protocolType The service discovery protocol
+ * @param listener The listener notifies of a successful registration and is used to
+ * unregister this service through a call on {@link #unregisterService}. Cannot be null.
+ * Cannot be in use for an active service registration.
+ */
+ public void registerService(NsdServiceInfo serviceInfo, int protocolType,
+ RegistrationListener listener) {
+ checkArgument(serviceInfo.getPort() > 0, "Invalid port number");
+ checkServiceInfo(serviceInfo);
+ checkProtocol(protocolType);
+ int key = putListener(listener, serviceInfo);
+ mAsyncChannel.sendMessage(REGISTER_SERVICE, 0, key, serviceInfo);
+ }
+
+ /**
+ * Unregister a service registered through {@link #registerService}. A successful
+ * unregister is notified to the application with a call to
+ * {@link RegistrationListener#onServiceUnregistered}.
+ *
+ * @param listener This should be the listener object that was passed to
+ * {@link #registerService}. It identifies the service that should be unregistered
+ * and notifies of a successful or unsuccessful unregistration via the listener
+ * callbacks. In API versions 20 and above, the listener object may be used for
+ * another service registration once the callback has been called. In API versions <= 19,
+ * there is no entirely reliable way to know when a listener may be re-used, and a new
+ * listener should be created for each service registration request.
+ */
+ public void unregisterService(RegistrationListener listener) {
+ int id = getListenerKey(listener);
+ mAsyncChannel.sendMessage(UNREGISTER_SERVICE, 0, id);
+ }
+
+ /**
+ * Initiate service discovery to browse for instances of a service type. Service discovery
+ * consumes network bandwidth and will continue until the application calls
+ * {@link #stopServiceDiscovery}.
+ *
+ * <p> The function call immediately returns after sending a request to start service
+ * discovery to the framework. The application is notified of a success to initiate
+ * discovery through the callback {@link DiscoveryListener#onDiscoveryStarted} or a failure
+ * through {@link DiscoveryListener#onStartDiscoveryFailed}.
+ *
+ * <p> Upon successful start, application is notified when a service is found with
+ * {@link DiscoveryListener#onServiceFound} or when a service is lost with
+ * {@link DiscoveryListener#onServiceLost}.
+ *
+ * <p> Upon failure to start, service discovery is not active and application does
+ * not need to invoke {@link #stopServiceDiscovery}
+ *
+ * <p> The application should call {@link #stopServiceDiscovery} when discovery of this
+ * service type is no longer required, and/or whenever the application is paused or
+ * stopped.
+ *
+ * @param serviceType The service type being discovered. Examples include "_http._tcp" for
+ * http services or "_ipp._tcp" for printers
+ * @param protocolType The service discovery protocol
+ * @param listener The listener notifies of a successful discovery and is used
+ * to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}.
+ * Cannot be null. Cannot be in use for an active service discovery.
+ */
+ public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) {
+ checkStringNotEmpty(serviceType, "Service type cannot be empty");
+ checkProtocol(protocolType);
+
+ NsdServiceInfo s = new NsdServiceInfo();
+ s.setServiceType(serviceType);
+
+ int key = putListener(listener, s);
+ mAsyncChannel.sendMessage(DISCOVER_SERVICES, 0, key, s);
+ }
+
+ /**
+ * Stop service discovery initiated with {@link #discoverServices}. An active service
+ * discovery is notified to the application with {@link DiscoveryListener#onDiscoveryStarted}
+ * and it stays active until the application invokes a stop service discovery. A successful
+ * stop is notified to with a call to {@link DiscoveryListener#onDiscoveryStopped}.
+ *
+ * <p> Upon failure to stop service discovery, application is notified through
+ * {@link DiscoveryListener#onStopDiscoveryFailed}.
+ *
+ * @param listener This should be the listener object that was passed to {@link #discoverServices}.
+ * It identifies the discovery that should be stopped and notifies of a successful or
+ * unsuccessful stop. In API versions 20 and above, the listener object may be used for
+ * another service discovery once the callback has been called. In API versions <= 19,
+ * there is no entirely reliable way to know when a listener may be re-used, and a new
+ * listener should be created for each service discovery request.
+ */
+ public void stopServiceDiscovery(DiscoveryListener listener) {
+ int id = getListenerKey(listener);
+ mAsyncChannel.sendMessage(STOP_DISCOVERY, 0, id);
+ }
+
+ /**
+ * Resolve a discovered service. An application can resolve a service right before
+ * establishing a connection to fetch the IP and port details on which to setup
+ * the connection.
+ *
+ * @param serviceInfo service to be resolved
+ * @param listener to receive callback upon success or failure. Cannot be null.
+ * Cannot be in use for an active service resolution.
+ */
+ public void resolveService(NsdServiceInfo serviceInfo, ResolveListener listener) {
+ checkServiceInfo(serviceInfo);
+ int key = putListener(listener, serviceInfo);
+ mAsyncChannel.sendMessage(RESOLVE_SERVICE, 0, key, serviceInfo);
+ }
+
+ /** Internal use only @hide */
+ public void setEnabled(boolean enabled) {
+ try {
+ mService.setEnabled(enabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get a reference to NsdService handler. This is used to establish
+ * an AsyncChannel communication with the service
+ *
+ * @return Messenger pointing to the NsdService handler
+ */
+ private Messenger getMessenger() {
+ try {
+ return mService.getMessenger();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private static void checkListener(Object listener) {
+ checkNotNull(listener, "listener cannot be null");
+ }
+
+ private static void checkProtocol(int protocolType) {
+ checkArgument(protocolType == PROTOCOL_DNS_SD, "Unsupported protocol");
+ }
+
+ private static void checkServiceInfo(NsdServiceInfo serviceInfo) {
+ checkNotNull(serviceInfo, "NsdServiceInfo cannot be null");
+ checkStringNotEmpty(serviceInfo.getServiceName(), "Service name cannot be empty");
+ checkStringNotEmpty(serviceInfo.getServiceType(), "Service type cannot be empty");
+ }
+}
diff --git a/android/net/nsd/NsdServiceInfo.java b/android/net/nsd/NsdServiceInfo.java
new file mode 100644
index 0000000..459b140
--- /dev/null
+++ b/android/net/nsd/NsdServiceInfo.java
@@ -0,0 +1,391 @@
+/*
+ * Copyright (C) 2012 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.nsd;
+
+import android.annotation.NonNull;
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcelable;
+import android.os.Parcel;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+import android.util.ArrayMap;
+
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * A class representing service information for network service discovery
+ * {@see NsdManager}
+ */
+public final class NsdServiceInfo implements Parcelable {
+
+ private static final String TAG = "NsdServiceInfo";
+
+ private String mServiceName;
+
+ private String mServiceType;
+
+ private final ArrayMap<String, byte[]> mTxtRecord = new ArrayMap<>();
+
+ private InetAddress mHost;
+
+ private int mPort;
+
+ public NsdServiceInfo() {
+ }
+
+ /** @hide */
+ public NsdServiceInfo(String sn, String rt) {
+ mServiceName = sn;
+ mServiceType = rt;
+ }
+
+ /** Get the service name */
+ public String getServiceName() {
+ return mServiceName;
+ }
+
+ /** Set the service name */
+ public void setServiceName(String s) {
+ mServiceName = s;
+ }
+
+ /** Get the service type */
+ public String getServiceType() {
+ return mServiceType;
+ }
+
+ /** Set the service type */
+ public void setServiceType(String s) {
+ mServiceType = s;
+ }
+
+ /** Get the host address. The host address is valid for a resolved service. */
+ public InetAddress getHost() {
+ return mHost;
+ }
+
+ /** Set the host address */
+ public void setHost(InetAddress s) {
+ mHost = s;
+ }
+
+ /** Get port number. The port number is valid for a resolved service. */
+ public int getPort() {
+ return mPort;
+ }
+
+ /** Set port number */
+ public void setPort(int p) {
+ mPort = p;
+ }
+
+ /**
+ * Unpack txt information from a base-64 encoded byte array.
+ *
+ * @param rawRecords The raw base64 encoded records string read from netd.
+ *
+ * @hide
+ */
+ public void setTxtRecords(@NonNull String rawRecords) {
+ byte[] txtRecordsRawBytes = Base64.decode(rawRecords, Base64.DEFAULT);
+
+ // There can be multiple TXT records after each other. Each record has to following format:
+ //
+ // byte type required meaning
+ // ------------------- ------------------- -------- ----------------------------------
+ // 0 unsigned 8 bit yes size of record excluding this byte
+ // 1 - n ASCII but not '=' yes key
+ // n + 1 '=' optional separator of key and value
+ // n + 2 - record size uninterpreted bytes optional value
+ //
+ // Example legal records:
+ // [11, 'm', 'y', 'k', 'e', 'y', '=', 0x0, 0x4, 0x65, 0x7, 0xff]
+ // [17, 'm', 'y', 'K', 'e', 'y', 'W', 'i', 't', 'h', 'N', 'o', 'V', 'a', 'l', 'u', 'e', '=']
+ // [12, 'm', 'y', 'B', 'o', 'o', 'l', 'e', 'a', 'n', 'K', 'e', 'y']
+ //
+ // Example corrupted records
+ // [3, =, 1, 2] <- key is empty
+ // [3, 0, =, 2] <- key contains non-ASCII character. We handle this by replacing the
+ // invalid characters instead of skipping the record.
+ // [30, 'a', =, 2] <- length exceeds total left over bytes in the TXT records array, we
+ // handle this by reducing the length of the record as needed.
+ int pos = 0;
+ while (pos < txtRecordsRawBytes.length) {
+ // recordLen is an unsigned 8 bit value
+ int recordLen = txtRecordsRawBytes[pos] & 0xff;
+ pos += 1;
+
+ try {
+ if (recordLen == 0) {
+ throw new IllegalArgumentException("Zero sized txt record");
+ } else if (pos + recordLen > txtRecordsRawBytes.length) {
+ Log.w(TAG, "Corrupt record length (pos = " + pos + "): " + recordLen);
+ recordLen = txtRecordsRawBytes.length - pos;
+ }
+
+ // Decode key-value records
+ String key = null;
+ byte[] value = null;
+ int valueLen = 0;
+ for (int i = pos; i < pos + recordLen; i++) {
+ if (key == null) {
+ if (txtRecordsRawBytes[i] == '=') {
+ key = new String(txtRecordsRawBytes, pos, i - pos,
+ StandardCharsets.US_ASCII);
+ }
+ } else {
+ if (value == null) {
+ value = new byte[recordLen - key.length() - 1];
+ }
+ value[valueLen] = txtRecordsRawBytes[i];
+ valueLen++;
+ }
+ }
+
+ // If '=' was not found we have a boolean record
+ if (key == null) {
+ key = new String(txtRecordsRawBytes, pos, recordLen, StandardCharsets.US_ASCII);
+ }
+
+ if (TextUtils.isEmpty(key)) {
+ // Empty keys are not allowed (RFC6763 6.4)
+ throw new IllegalArgumentException("Invalid txt record (key is empty)");
+ }
+
+ if (getAttributes().containsKey(key)) {
+ // When we have a duplicate record, the later ones are ignored (RFC6763 6.4)
+ throw new IllegalArgumentException("Invalid txt record (duplicate key \"" + key + "\")");
+ }
+
+ setAttribute(key, value);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "While parsing txt records (pos = " + pos + "): " + e.getMessage());
+ }
+
+ pos += recordLen;
+ }
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public void setAttribute(String key, byte[] value) {
+ if (TextUtils.isEmpty(key)) {
+ throw new IllegalArgumentException("Key cannot be empty");
+ }
+
+ // Key must be printable US-ASCII, excluding =.
+ for (int i = 0; i < key.length(); ++i) {
+ char character = key.charAt(i);
+ if (character < 0x20 || character > 0x7E) {
+ throw new IllegalArgumentException("Key strings must be printable US-ASCII");
+ } else if (character == 0x3D) {
+ throw new IllegalArgumentException("Key strings must not include '='");
+ }
+ }
+
+ // Key length + value length must be < 255.
+ if (key.length() + (value == null ? 0 : value.length) >= 255) {
+ throw new IllegalArgumentException("Key length + value length must be < 255 bytes");
+ }
+
+ // Warn if key is > 9 characters, as recommended by RFC 6763 section 6.4.
+ if (key.length() > 9) {
+ Log.w(TAG, "Key lengths > 9 are discouraged: " + key);
+ }
+
+ // Check against total TXT record size limits.
+ // Arbitrary 400 / 1300 byte limits taken from RFC 6763 section 6.2.
+ int txtRecordSize = getTxtRecordSize();
+ int futureSize = txtRecordSize + key.length() + (value == null ? 0 : value.length) + 2;
+ if (futureSize > 1300) {
+ throw new IllegalArgumentException("Total length of attributes must be < 1300 bytes");
+ } else if (futureSize > 400) {
+ Log.w(TAG, "Total length of all attributes exceeds 400 bytes; truncation may occur");
+ }
+
+ mTxtRecord.put(key, value);
+ }
+
+ /**
+ * Add a service attribute as a key/value pair.
+ *
+ * <p> Service attributes are included as DNS-SD TXT record pairs.
+ *
+ * <p> The key must be US-ASCII printable characters, excluding the '=' character. Values may
+ * be UTF-8 strings or null. The total length of key + value must be less than 255 bytes.
+ *
+ * <p> Keys should be short, ideally no more than 9 characters, and unique per instance of
+ * {@link NsdServiceInfo}. Calling {@link #setAttribute} twice with the same key will overwrite
+ * first value.
+ */
+ public void setAttribute(String key, String value) {
+ try {
+ setAttribute(key, value == null ? (byte []) null : value.getBytes("UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalArgumentException("Value must be UTF-8");
+ }
+ }
+
+ /** Remove an attribute by key */
+ public void removeAttribute(String key) {
+ mTxtRecord.remove(key);
+ }
+
+ /**
+ * Retrieve attributes as a map of String keys to byte[] values. The attributes map is only
+ * valid for a resolved service.
+ *
+ * <p> The returned map is unmodifiable; changes must be made through {@link #setAttribute} and
+ * {@link #removeAttribute}.
+ */
+ public Map<String, byte[]> getAttributes() {
+ return Collections.unmodifiableMap(mTxtRecord);
+ }
+
+ private int getTxtRecordSize() {
+ int txtRecordSize = 0;
+ for (Map.Entry<String, byte[]> entry : mTxtRecord.entrySet()) {
+ txtRecordSize += 2; // One for the length byte, one for the = between key and value.
+ txtRecordSize += entry.getKey().length();
+ byte[] value = entry.getValue();
+ txtRecordSize += value == null ? 0 : value.length;
+ }
+ return txtRecordSize;
+ }
+
+ /** @hide */
+ public @NonNull byte[] getTxtRecord() {
+ int txtRecordSize = getTxtRecordSize();
+ if (txtRecordSize == 0) {
+ return new byte[]{};
+ }
+
+ byte[] txtRecord = new byte[txtRecordSize];
+ int ptr = 0;
+ for (Map.Entry<String, byte[]> entry : mTxtRecord.entrySet()) {
+ String key = entry.getKey();
+ byte[] value = entry.getValue();
+
+ // One byte to record the length of this key/value pair.
+ txtRecord[ptr++] = (byte) (key.length() + (value == null ? 0 : value.length) + 1);
+
+ // The key, in US-ASCII.
+ // Note: use the StandardCharsets const here because it doesn't raise exceptions and we
+ // already know the key is ASCII at this point.
+ System.arraycopy(key.getBytes(StandardCharsets.US_ASCII), 0, txtRecord, ptr,
+ key.length());
+ ptr += key.length();
+
+ // US-ASCII '=' character.
+ txtRecord[ptr++] = (byte)'=';
+
+ // The value, as any raw bytes.
+ if (value != null) {
+ System.arraycopy(value, 0, txtRecord, ptr, value.length);
+ ptr += value.length;
+ }
+ }
+ return txtRecord;
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append("name: ").append(mServiceName)
+ .append(", type: ").append(mServiceType)
+ .append(", host: ").append(mHost)
+ .append(", port: ").append(mPort);
+
+ byte[] txtRecord = getTxtRecord();
+ if (txtRecord != null) {
+ sb.append(", txtRecord: ").append(new String(txtRecord, StandardCharsets.UTF_8));
+ }
+ return sb.toString();
+ }
+
+ /** Implement the Parcelable interface */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mServiceName);
+ dest.writeString(mServiceType);
+ if (mHost != null) {
+ dest.writeInt(1);
+ dest.writeByteArray(mHost.getAddress());
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(mPort);
+
+ // TXT record key/value pairs.
+ dest.writeInt(mTxtRecord.size());
+ for (String key : mTxtRecord.keySet()) {
+ byte[] value = mTxtRecord.get(key);
+ if (value != null) {
+ dest.writeInt(1);
+ dest.writeInt(value.length);
+ dest.writeByteArray(value);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeString(key);
+ }
+ }
+
+ /** Implement the Parcelable interface */
+ public static final @android.annotation.NonNull Creator<NsdServiceInfo> CREATOR =
+ new Creator<NsdServiceInfo>() {
+ public NsdServiceInfo createFromParcel(Parcel in) {
+ NsdServiceInfo info = new NsdServiceInfo();
+ info.mServiceName = in.readString();
+ info.mServiceType = in.readString();
+
+ if (in.readInt() == 1) {
+ try {
+ info.mHost = InetAddress.getByAddress(in.createByteArray());
+ } catch (java.net.UnknownHostException e) {}
+ }
+
+ info.mPort = in.readInt();
+
+ // TXT record key/value pairs.
+ int recordCount = in.readInt();
+ for (int i = 0; i < recordCount; ++i) {
+ byte[] valueArray = null;
+ if (in.readInt() == 1) {
+ int valueLength = in.readInt();
+ valueArray = new byte[valueLength];
+ in.readByteArray(valueArray);
+ }
+ info.mTxtRecord.put(in.readString(), valueArray);
+ }
+ return info;
+ }
+
+ public NsdServiceInfo[] newArray(int size) {
+ return new NsdServiceInfo[size];
+ }
+ };
+}
diff --git a/android/net/rtp/AudioCodec.java b/android/net/rtp/AudioCodec.java
new file mode 100644
index 0000000..85255c8
--- /dev/null
+++ b/android/net/rtp/AudioCodec.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2010 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.rtp;
+
+import java.util.Arrays;
+
+/**
+ * This class defines a collection of audio codecs to be used with
+ * {@link AudioStream}s. Their parameters are designed to be exchanged using
+ * Session Description Protocol (SDP). Most of the values listed here can be
+ * found in RFC 3551, while others are described in separated standards.
+ *
+ * <p>Few simple configurations are defined as public static instances for the
+ * convenience of direct uses. More complicated ones could be obtained using
+ * {@link #getCodec(int, String, String)}. For example, one can use the
+ * following snippet to create a mode-1-only AMR codec.</p>
+ * <pre>
+ * AudioCodec codec = AudioCodec.getCodec(100, "AMR/8000", "mode-set=1");
+ * </pre>
+ *
+ * @see AudioStream
+ */
+public class AudioCodec {
+ /**
+ * The RTP payload type of the encoding.
+ */
+ public final int type;
+
+ /**
+ * The encoding parameters to be used in the corresponding SDP attribute.
+ */
+ public final String rtpmap;
+
+ /**
+ * The format parameters to be used in the corresponding SDP attribute.
+ */
+ public final String fmtp;
+
+ /**
+ * G.711 u-law audio codec.
+ */
+ public static final AudioCodec PCMU = new AudioCodec(0, "PCMU/8000", null);
+
+ /**
+ * G.711 a-law audio codec.
+ */
+ public static final AudioCodec PCMA = new AudioCodec(8, "PCMA/8000", null);
+
+ /**
+ * GSM Full-Rate audio codec, also known as GSM-FR, GSM 06.10, GSM, or
+ * simply FR.
+ */
+ public static final AudioCodec GSM = new AudioCodec(3, "GSM/8000", null);
+
+ /**
+ * GSM Enhanced Full-Rate audio codec, also known as GSM-EFR, GSM 06.60, or
+ * simply EFR.
+ */
+ public static final AudioCodec GSM_EFR = new AudioCodec(96, "GSM-EFR/8000", null);
+
+ /**
+ * Adaptive Multi-Rate narrowband audio codec, also known as AMR or AMR-NB.
+ * Currently CRC, robust sorting, and interleaving are not supported. See
+ * more details about these features in RFC 4867.
+ */
+ public static final AudioCodec AMR = new AudioCodec(97, "AMR/8000", null);
+
+ private static final AudioCodec[] sCodecs = {GSM_EFR, AMR, GSM, PCMU, PCMA};
+
+ private AudioCodec(int type, String rtpmap, String fmtp) {
+ this.type = type;
+ this.rtpmap = rtpmap;
+ this.fmtp = fmtp;
+ }
+
+ /**
+ * Returns system supported audio codecs.
+ */
+ public static AudioCodec[] getCodecs() {
+ return Arrays.copyOf(sCodecs, sCodecs.length);
+ }
+
+ /**
+ * Creates an AudioCodec according to the given configuration.
+ *
+ * @param type The payload type of the encoding defined in RTP/AVP.
+ * @param rtpmap The encoding parameters specified in the corresponding SDP
+ * attribute, or null if it is not available.
+ * @param fmtp The format parameters specified in the corresponding SDP
+ * attribute, or null if it is not available.
+ * @return The configured AudioCodec or {@code null} if it is not supported.
+ */
+ public static AudioCodec getCodec(int type, String rtpmap, String fmtp) {
+ if (type < 0 || type > 127) {
+ return null;
+ }
+
+ AudioCodec hint = null;
+ if (rtpmap != null) {
+ String clue = rtpmap.trim().toUpperCase();
+ for (AudioCodec codec : sCodecs) {
+ if (clue.startsWith(codec.rtpmap)) {
+ String channels = clue.substring(codec.rtpmap.length());
+ if (channels.length() == 0 || channels.equals("/1")) {
+ hint = codec;
+ }
+ break;
+ }
+ }
+ } else if (type < 96) {
+ for (AudioCodec codec : sCodecs) {
+ if (type == codec.type) {
+ hint = codec;
+ rtpmap = codec.rtpmap;
+ break;
+ }
+ }
+ }
+
+ if (hint == null) {
+ return null;
+ }
+ if (hint == AMR && fmtp != null) {
+ String clue = fmtp.toLowerCase();
+ if (clue.contains("crc=1") || clue.contains("robust-sorting=1") ||
+ clue.contains("interleaving=")) {
+ return null;
+ }
+ }
+ return new AudioCodec(type, rtpmap, fmtp);
+ }
+}
diff --git a/android/net/rtp/AudioGroup.java b/android/net/rtp/AudioGroup.java
new file mode 100644
index 0000000..15390d3
--- /dev/null
+++ b/android/net/rtp/AudioGroup.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2010 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.rtp;
+
+import android.app.ActivityThread;
+import android.media.AudioManager;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * An AudioGroup is an audio hub for the speaker, the microphone, and
+ * {@link AudioStream}s. Each of these components can be logically turned on
+ * or off by calling {@link #setMode(int)} or {@link RtpStream#setMode(int)}.
+ * The AudioGroup will go through these components and process them one by one
+ * within its execution loop. The loop consists of four steps. First, for each
+ * AudioStream not in {@link RtpStream#MODE_SEND_ONLY}, decodes its incoming
+ * packets and stores in its buffer. Then, if the microphone is enabled,
+ * processes the recorded audio and stores in its buffer. Third, if the speaker
+ * is enabled, mixes all AudioStream buffers and plays back. Finally, for each
+ * AudioStream not in {@link RtpStream#MODE_RECEIVE_ONLY}, mixes all other
+ * buffers and sends back the encoded packets. An AudioGroup does nothing if
+ * there is no AudioStream in it.
+ *
+ * <p>Few things must be noticed before using these classes. The performance is
+ * highly related to the system load and the network bandwidth. Usually a
+ * simpler {@link AudioCodec} costs fewer CPU cycles but requires more network
+ * bandwidth, and vise versa. Using two AudioStreams at the same time doubles
+ * not only the load but also the bandwidth. The condition varies from one
+ * device to another, and developers should choose the right combination in
+ * order to get the best result.</p>
+ *
+ * <p>It is sometimes useful to keep multiple AudioGroups at the same time. For
+ * example, a Voice over IP (VoIP) application might want to put a conference
+ * call on hold in order to make a new call but still allow people in the
+ * conference call talking to each other. This can be done easily using two
+ * AudioGroups, but there are some limitations. Since the speaker and the
+ * microphone are globally shared resources, only one AudioGroup at a time is
+ * allowed to run in a mode other than {@link #MODE_ON_HOLD}. The others will
+ * be unable to acquire these resources and fail silently.</p>
+ *
+ * <p class="note">Using this class requires
+ * {@link android.Manifest.permission#RECORD_AUDIO} permission. Developers
+ * should set the audio mode to {@link AudioManager#MODE_IN_COMMUNICATION}
+ * using {@link AudioManager#setMode(int)} and change it back when none of
+ * the AudioGroups is in use.</p>
+ *
+ * @see AudioStream
+ */
+public class AudioGroup {
+ /**
+ * This mode is similar to {@link #MODE_NORMAL} except the speaker and
+ * the microphone are both disabled.
+ */
+ public static final int MODE_ON_HOLD = 0;
+
+ /**
+ * This mode is similar to {@link #MODE_NORMAL} except the microphone is
+ * disabled.
+ */
+ public static final int MODE_MUTED = 1;
+
+ /**
+ * This mode indicates that the speaker, the microphone, and all
+ * {@link AudioStream}s in the group are enabled. First, the packets
+ * received from the streams are decoded and mixed with the audio recorded
+ * from the microphone. Then, the results are played back to the speaker,
+ * encoded and sent back to each stream.
+ */
+ public static final int MODE_NORMAL = 2;
+
+ /**
+ * This mode is similar to {@link #MODE_NORMAL} except the echo suppression
+ * is enabled. It should be only used when the speaker phone is on.
+ */
+ public static final int MODE_ECHO_SUPPRESSION = 3;
+
+ private static final int MODE_LAST = 3;
+
+ private final Map<AudioStream, Long> mStreams;
+ private int mMode = MODE_ON_HOLD;
+
+ private long mNative;
+ static {
+ System.loadLibrary("rtp_jni");
+ }
+
+ /**
+ * Creates an empty AudioGroup.
+ */
+ public AudioGroup() {
+ mStreams = new HashMap<AudioStream, Long>();
+ }
+
+ /**
+ * Returns the {@link AudioStream}s in this group.
+ */
+ public AudioStream[] getStreams() {
+ synchronized (this) {
+ return mStreams.keySet().toArray(new AudioStream[mStreams.size()]);
+ }
+ }
+
+ /**
+ * Returns the current mode.
+ */
+ public int getMode() {
+ return mMode;
+ }
+
+ /**
+ * Changes the current mode. It must be one of {@link #MODE_ON_HOLD},
+ * {@link #MODE_MUTED}, {@link #MODE_NORMAL}, and
+ * {@link #MODE_ECHO_SUPPRESSION}.
+ *
+ * @param mode The mode to change to.
+ * @throws IllegalArgumentException if the mode is invalid.
+ */
+ public void setMode(int mode) {
+ if (mode < 0 || mode > MODE_LAST) {
+ throw new IllegalArgumentException("Invalid mode");
+ }
+ synchronized (this) {
+ nativeSetMode(mode);
+ mMode = mode;
+ }
+ }
+
+ private native void nativeSetMode(int mode);
+
+ // Package-private method used by AudioStream.join().
+ synchronized void add(AudioStream stream) {
+ if (!mStreams.containsKey(stream)) {
+ try {
+ AudioCodec codec = stream.getCodec();
+ String codecSpec = String.format(Locale.US, "%d %s %s", codec.type,
+ codec.rtpmap, codec.fmtp);
+ long id = nativeAdd(stream.getMode(), stream.getSocket(),
+ stream.getRemoteAddress().getHostAddress(),
+ stream.getRemotePort(), codecSpec, stream.getDtmfType(),
+ ActivityThread.currentOpPackageName());
+ mStreams.put(stream, id);
+ } catch (NullPointerException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+
+ private native long nativeAdd(int mode, int socket, String remoteAddress,
+ int remotePort, String codecSpec, int dtmfType, String opPackageName);
+
+ // Package-private method used by AudioStream.join().
+ synchronized void remove(AudioStream stream) {
+ Long id = mStreams.remove(stream);
+ if (id != null) {
+ nativeRemove(id);
+ }
+ }
+
+ private native void nativeRemove(long id);
+
+ /**
+ * Sends a DTMF digit to every {@link AudioStream} in this group. Currently
+ * only event {@code 0} to {@code 15} are supported.
+ *
+ * @throws IllegalArgumentException if the event is invalid.
+ */
+ public void sendDtmf(int event) {
+ if (event < 0 || event > 15) {
+ throw new IllegalArgumentException("Invalid event");
+ }
+ synchronized (this) {
+ nativeSendDtmf(event);
+ }
+ }
+
+ private native void nativeSendDtmf(int event);
+
+ /**
+ * Removes every {@link AudioStream} in this group.
+ */
+ public void clear() {
+ for (AudioStream stream : getStreams()) {
+ stream.join(null);
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ nativeRemove(0L);
+ super.finalize();
+ }
+}
diff --git a/android/net/rtp/AudioStream.java b/android/net/rtp/AudioStream.java
new file mode 100644
index 0000000..5cd1abc
--- /dev/null
+++ b/android/net/rtp/AudioStream.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2010 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.rtp;
+
+import java.net.InetAddress;
+import java.net.SocketException;
+
+/**
+ * An AudioStream is a {@link RtpStream} which carrys audio payloads over
+ * Real-time Transport Protocol (RTP). Two different classes are developed in
+ * order to support various usages such as audio conferencing. An AudioStream
+ * represents a remote endpoint which consists of a network mapping and a
+ * configured {@link AudioCodec}. On the other side, An {@link AudioGroup}
+ * represents a local endpoint which mixes all the AudioStreams and optionally
+ * interacts with the speaker and the microphone at the same time. The simplest
+ * usage includes one for each endpoints. For other combinations, developers
+ * should be aware of the limitations described in {@link AudioGroup}.
+ *
+ * <p>An AudioStream becomes busy when it joins an AudioGroup. In this case most
+ * of the setter methods are disabled. This is designed to ease the task of
+ * managing native resources. One can always make an AudioStream leave its
+ * AudioGroup by calling {@link #join(AudioGroup)} with {@code null} and put it
+ * back after the modification is done.</p>
+ *
+ * <p class="note">Using this class requires
+ * {@link android.Manifest.permission#INTERNET} permission.</p>
+ *
+ * @see RtpStream
+ * @see AudioGroup
+ */
+public class AudioStream extends RtpStream {
+ private AudioCodec mCodec;
+ private int mDtmfType = -1;
+ private AudioGroup mGroup;
+
+ /**
+ * Creates an AudioStream on the given local address. Note that the local
+ * port is assigned automatically to conform with RFC 3550.
+ *
+ * @param address The network address of the local host to bind to.
+ * @throws SocketException if the address cannot be bound or a problem
+ * occurs during binding.
+ */
+ public AudioStream(InetAddress address) throws SocketException {
+ super(address);
+ }
+
+ /**
+ * Returns {@code true} if the stream has already joined an
+ * {@link AudioGroup}.
+ */
+ @Override
+ public final boolean isBusy() {
+ return mGroup != null;
+ }
+
+ /**
+ * Returns the joined {@link AudioGroup}.
+ */
+ public AudioGroup getGroup() {
+ return mGroup;
+ }
+
+ /**
+ * Joins an {@link AudioGroup}. Each stream can join only one group at a
+ * time. The group can be changed by passing a different one or removed
+ * by calling this method with {@code null}.
+ *
+ * @param group The AudioGroup to join or {@code null} to leave.
+ * @throws IllegalStateException if the stream is not properly configured.
+ * @see AudioGroup
+ */
+ public void join(AudioGroup group) {
+ synchronized (this) {
+ if (mGroup == group) {
+ return;
+ }
+ if (mGroup != null) {
+ mGroup.remove(this);
+ mGroup = null;
+ }
+ if (group != null) {
+ group.add(this);
+ mGroup = group;
+ }
+ }
+ }
+
+ /**
+ * Returns the {@link AudioCodec}, or {@code null} if it is not set.
+ *
+ * @see #setCodec(AudioCodec)
+ */
+ public AudioCodec getCodec() {
+ return mCodec;
+ }
+
+ /**
+ * Sets the {@link AudioCodec}.
+ *
+ * @param codec The AudioCodec to be used.
+ * @throws IllegalArgumentException if its type is used by DTMF.
+ * @throws IllegalStateException if the stream is busy.
+ */
+ public void setCodec(AudioCodec codec) {
+ if (isBusy()) {
+ throw new IllegalStateException("Busy");
+ }
+ if (codec.type == mDtmfType) {
+ throw new IllegalArgumentException("The type is used by DTMF");
+ }
+ mCodec = codec;
+ }
+
+ /**
+ * Returns the RTP payload type for dual-tone multi-frequency (DTMF) digits,
+ * or {@code -1} if it is not enabled.
+ *
+ * @see #setDtmfType(int)
+ */
+ public int getDtmfType() {
+ return mDtmfType;
+ }
+
+ /**
+ * Sets the RTP payload type for dual-tone multi-frequency (DTMF) digits.
+ * The primary usage is to send digits to the remote gateway to perform
+ * certain tasks, such as second-stage dialing. According to RFC 2833, the
+ * RTP payload type for DTMF is assigned dynamically, so it must be in the
+ * range of 96 and 127. One can use {@code -1} to disable DTMF and free up
+ * the previous assigned type. This method cannot be called when the stream
+ * already joined an {@link AudioGroup}.
+ *
+ * @param type The RTP payload type to be used or {@code -1} to disable it.
+ * @throws IllegalArgumentException if the type is invalid or used by codec.
+ * @throws IllegalStateException if the stream is busy.
+ * @see AudioGroup#sendDtmf(int)
+ */
+ public void setDtmfType(int type) {
+ if (isBusy()) {
+ throw new IllegalStateException("Busy");
+ }
+ if (type != -1) {
+ if (type < 96 || type > 127) {
+ throw new IllegalArgumentException("Invalid type");
+ }
+ if (mCodec != null && type == mCodec.type) {
+ throw new IllegalArgumentException("The type is used by codec");
+ }
+ }
+ mDtmfType = type;
+ }
+}
diff --git a/android/net/rtp/RtpStream.java b/android/net/rtp/RtpStream.java
new file mode 100644
index 0000000..b9d75cd
--- /dev/null
+++ b/android/net/rtp/RtpStream.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2010 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.rtp;
+
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.SocketException;
+
+/**
+ * RtpStream represents the base class of streams which send and receive network
+ * packets with media payloads over Real-time Transport Protocol (RTP).
+ *
+ * <p class="note">Using this class requires
+ * {@link android.Manifest.permission#INTERNET} permission.</p>
+ */
+public class RtpStream {
+ /**
+ * This mode indicates that the stream sends and receives packets at the
+ * same time. This is the initial mode for new streams.
+ */
+ public static final int MODE_NORMAL = 0;
+
+ /**
+ * This mode indicates that the stream only sends packets.
+ */
+ public static final int MODE_SEND_ONLY = 1;
+
+ /**
+ * This mode indicates that the stream only receives packets.
+ */
+ public static final int MODE_RECEIVE_ONLY = 2;
+
+ private static final int MODE_LAST = 2;
+
+ private final InetAddress mLocalAddress;
+ private final int mLocalPort;
+
+ private InetAddress mRemoteAddress;
+ private int mRemotePort = -1;
+ private int mMode = MODE_NORMAL;
+
+ private int mSocket = -1;
+ static {
+ System.loadLibrary("rtp_jni");
+ }
+
+ /**
+ * Creates a RtpStream on the given local address. Note that the local
+ * port is assigned automatically to conform with RFC 3550.
+ *
+ * @param address The network address of the local host to bind to.
+ * @throws SocketException if the address cannot be bound or a problem
+ * occurs during binding.
+ */
+ RtpStream(InetAddress address) throws SocketException {
+ mLocalPort = create(address.getHostAddress());
+ mLocalAddress = address;
+ }
+
+ private native int create(String address) throws SocketException;
+
+ /**
+ * Returns the network address of the local host.
+ */
+ public InetAddress getLocalAddress() {
+ return mLocalAddress;
+ }
+
+ /**
+ * Returns the network port of the local host.
+ */
+ public int getLocalPort() {
+ return mLocalPort;
+ }
+
+ /**
+ * Returns the network address of the remote host or {@code null} if the
+ * stream is not associated.
+ */
+ public InetAddress getRemoteAddress() {
+ return mRemoteAddress;
+ }
+
+ /**
+ * Returns the network port of the remote host or {@code -1} if the stream
+ * is not associated.
+ */
+ public int getRemotePort() {
+ return mRemotePort;
+ }
+
+ /**
+ * Returns {@code true} if the stream is busy. In this case most of the
+ * setter methods are disabled. This method is intended to be overridden
+ * by subclasses.
+ */
+ public boolean isBusy() {
+ return false;
+ }
+
+ /**
+ * Returns the current mode.
+ */
+ public int getMode() {
+ return mMode;
+ }
+
+ /**
+ * Changes the current mode. It must be one of {@link #MODE_NORMAL},
+ * {@link #MODE_SEND_ONLY}, and {@link #MODE_RECEIVE_ONLY}.
+ *
+ * @param mode The mode to change to.
+ * @throws IllegalArgumentException if the mode is invalid.
+ * @throws IllegalStateException if the stream is busy.
+ * @see #isBusy()
+ */
+ public void setMode(int mode) {
+ if (isBusy()) {
+ throw new IllegalStateException("Busy");
+ }
+ if (mode < 0 || mode > MODE_LAST) {
+ throw new IllegalArgumentException("Invalid mode");
+ }
+ mMode = mode;
+ }
+
+ /**
+ * Associates with a remote host. This defines the destination of the
+ * outgoing packets.
+ *
+ * @param address The network address of the remote host.
+ * @param port The network port of the remote host.
+ * @throws IllegalArgumentException if the address is not supported or the
+ * port is invalid.
+ * @throws IllegalStateException if the stream is busy.
+ * @see #isBusy()
+ */
+ public void associate(InetAddress address, int port) {
+ if (isBusy()) {
+ throw new IllegalStateException("Busy");
+ }
+ if (!(address instanceof Inet4Address && mLocalAddress instanceof Inet4Address) &&
+ !(address instanceof Inet6Address && mLocalAddress instanceof Inet6Address)) {
+ throw new IllegalArgumentException("Unsupported address");
+ }
+ if (port < 0 || port > 65535) {
+ throw new IllegalArgumentException("Invalid port");
+ }
+ mRemoteAddress = address;
+ mRemotePort = port;
+ }
+
+ int getSocket() {
+ return mSocket;
+ }
+
+ /**
+ * Releases allocated resources. The stream becomes inoperable after calling
+ * this method.
+ *
+ * @throws IllegalStateException if the stream is busy.
+ * @see #isBusy()
+ */
+ public void release() {
+ synchronized (this) {
+ if (isBusy()) {
+ throw new IllegalStateException("Busy");
+ }
+ close();
+ }
+ }
+
+ private native void close();
+
+ @Override
+ protected void finalize() throws Throwable {
+ close();
+ super.finalize();
+ }
+}
diff --git a/android/net/shared/Inet4AddressUtils.java b/android/net/shared/Inet4AddressUtils.java
new file mode 100644
index 0000000..bec0c84
--- /dev/null
+++ b/android/net/shared/Inet4AddressUtils.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.shared;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Collection of utilities to work with IPv4 addresses.
+ * @hide
+ */
+public class Inet4AddressUtils {
+
+ /**
+ * Convert a IPv4 address from an integer to an InetAddress (0x04030201 -> 1.2.3.4)
+ *
+ * <p>This method uses the higher-order int bytes as the lower-order IPv4 address bytes,
+ * which is an unusual convention. Consider {@link #intToInet4AddressHTH(int)} instead.
+ * @param hostAddress an int coding for an IPv4 address, where higher-order int byte is
+ * lower-order IPv4 address byte
+ */
+ public static Inet4Address intToInet4AddressHTL(int hostAddress) {
+ return intToInet4AddressHTH(Integer.reverseBytes(hostAddress));
+ }
+
+ /**
+ * Convert a IPv4 address from an integer to an InetAddress (0x01020304 -> 1.2.3.4)
+ * @param hostAddress an int coding for an IPv4 address
+ */
+ public static Inet4Address intToInet4AddressHTH(int hostAddress) {
+ byte[] addressBytes = { (byte) (0xff & (hostAddress >> 24)),
+ (byte) (0xff & (hostAddress >> 16)),
+ (byte) (0xff & (hostAddress >> 8)),
+ (byte) (0xff & hostAddress) };
+
+ try {
+ return (Inet4Address) InetAddress.getByAddress(addressBytes);
+ } catch (UnknownHostException e) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Convert an IPv4 address from an InetAddress to an integer (1.2.3.4 -> 0x01020304)
+ *
+ * <p>This conversion can help order IP addresses: considering the ordering
+ * 192.0.2.1 < 192.0.2.2 < ..., resulting ints will follow that ordering if read as unsigned
+ * integers with {@link Integer#toUnsignedLong}.
+ * @param inetAddr is an InetAddress corresponding to the IPv4 address
+ * @return the IP address as integer
+ */
+ public static int inet4AddressToIntHTH(Inet4Address inetAddr)
+ throws IllegalArgumentException {
+ byte [] addr = inetAddr.getAddress();
+ return ((addr[0] & 0xff) << 24) | ((addr[1] & 0xff) << 16)
+ | ((addr[2] & 0xff) << 8) | (addr[3] & 0xff);
+ }
+
+ /**
+ * Convert a IPv4 address from an InetAddress to an integer (1.2.3.4 -> 0x04030201)
+ *
+ * <p>This method stores the higher-order IPv4 address bytes in the lower-order int bytes,
+ * which is an unusual convention. Consider {@link #inet4AddressToIntHTH(Inet4Address)} instead.
+ * @param inetAddr is an InetAddress corresponding to the IPv4 address
+ * @return the IP address as integer
+ */
+ public static int inet4AddressToIntHTL(Inet4Address inetAddr) {
+ return Integer.reverseBytes(inet4AddressToIntHTH(inetAddr));
+ }
+
+ /**
+ * Convert a network prefix length to an IPv4 netmask integer (prefixLength 17 -> 0xffff8000)
+ * @return the IPv4 netmask as an integer
+ */
+ public static int prefixLengthToV4NetmaskIntHTH(int prefixLength)
+ throws IllegalArgumentException {
+ if (prefixLength < 0 || prefixLength > 32) {
+ throw new IllegalArgumentException("Invalid prefix length (0 <= prefix <= 32)");
+ }
+ // (int)a << b is equivalent to a << (b & 0x1f): can't shift by 32 (-1 << 32 == -1)
+ return prefixLength == 0 ? 0 : 0xffffffff << (32 - prefixLength);
+ }
+
+ /**
+ * Convert a network prefix length to an IPv4 netmask integer (prefixLength 17 -> 0x0080ffff).
+ *
+ * <p>This method stores the higher-order IPv4 address bytes in the lower-order int bytes,
+ * which is an unusual convention. Consider {@link #prefixLengthToV4NetmaskIntHTH(int)} instead.
+ * @return the IPv4 netmask as an integer
+ */
+ public static int prefixLengthToV4NetmaskIntHTL(int prefixLength)
+ throws IllegalArgumentException {
+ return Integer.reverseBytes(prefixLengthToV4NetmaskIntHTH(prefixLength));
+ }
+
+ /**
+ * Convert an IPv4 netmask to a prefix length, checking that the netmask is contiguous.
+ * @param netmask as a {@code Inet4Address}.
+ * @return the network prefix length
+ * @throws IllegalArgumentException the specified netmask was not contiguous.
+ * @hide
+ */
+ public static int netmaskToPrefixLength(Inet4Address netmask) {
+ // inetAddressToInt returns an int in *network* byte order.
+ int i = inet4AddressToIntHTH(netmask);
+ int prefixLength = Integer.bitCount(i);
+ int trailingZeros = Integer.numberOfTrailingZeros(i);
+ if (trailingZeros != 32 - prefixLength) {
+ throw new IllegalArgumentException("Non-contiguous netmask: " + Integer.toHexString(i));
+ }
+ return prefixLength;
+ }
+
+ /**
+ * Returns the implicit netmask of an IPv4 address, as was the custom before 1993.
+ */
+ public static int getImplicitNetmask(Inet4Address address) {
+ int firstByte = address.getAddress()[0] & 0xff; // Convert to an unsigned value.
+ if (firstByte < 128) {
+ return 8;
+ } else if (firstByte < 192) {
+ return 16;
+ } else if (firstByte < 224) {
+ return 24;
+ } else {
+ return 32; // Will likely not end well for other reasons.
+ }
+ }
+
+ /**
+ * Get the broadcast address for a given prefix.
+ *
+ * <p>For example 192.168.0.1/24 -> 192.168.0.255
+ */
+ public static Inet4Address getBroadcastAddress(Inet4Address addr, int prefixLength)
+ throws IllegalArgumentException {
+ final int intBroadcastAddr = inet4AddressToIntHTH(addr)
+ | ~prefixLengthToV4NetmaskIntHTH(prefixLength);
+ return intToInet4AddressHTH(intBroadcastAddr);
+ }
+
+ /**
+ * Get a prefix mask as Inet4Address for a given prefix length.
+ *
+ * <p>For example 20 -> 255.255.240.0
+ */
+ public static Inet4Address getPrefixMaskAsInet4Address(int prefixLength)
+ throws IllegalArgumentException {
+ return intToInet4AddressHTH(prefixLengthToV4NetmaskIntHTH(prefixLength));
+ }
+}
diff --git a/android/net/shared/InetAddressUtils.java b/android/net/shared/InetAddressUtils.java
new file mode 100644
index 0000000..c9ee3a7
--- /dev/null
+++ b/android/net/shared/InetAddressUtils.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2012 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.shared;
+
+import android.os.Parcel;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Collection of utilities to interact with {@link InetAddress}
+ * @hide
+ */
+public class InetAddressUtils {
+
+ /**
+ * Writes an InetAddress to a parcel. The address may be null. This is likely faster than
+ * calling writeSerializable.
+ * @hide
+ */
+ public static void parcelInetAddress(Parcel parcel, InetAddress address, int flags) {
+ byte[] addressArray = (address != null) ? address.getAddress() : null;
+ parcel.writeByteArray(addressArray);
+ }
+
+ /**
+ * Reads an InetAddress from a parcel. Returns null if the address that was written was null
+ * or if the data is invalid.
+ * @hide
+ */
+ public static InetAddress unparcelInetAddress(Parcel in) {
+ byte[] addressArray = in.createByteArray();
+ if (addressArray == null) {
+ return null;
+ }
+ try {
+ return InetAddress.getByAddress(addressArray);
+ } catch (UnknownHostException e) {
+ return null;
+ }
+ }
+
+ private InetAddressUtils() {}
+}
diff --git a/android/net/shared/InitialConfiguration.java b/android/net/shared/InitialConfiguration.java
new file mode 100644
index 0000000..007c8ca
--- /dev/null
+++ b/android/net/shared/InitialConfiguration.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.shared;
+
+import static android.net.shared.ParcelableUtil.fromParcelableArray;
+import static android.net.shared.ParcelableUtil.toParcelableArray;
+import static android.text.TextUtils.join;
+
+import android.net.InetAddresses;
+import android.net.InitialConfigurationParcelable;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.RouteInfo;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
+
+/** @hide */
+public class InitialConfiguration {
+ public final Set<LinkAddress> ipAddresses = new HashSet<>();
+ public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>();
+ public final Set<InetAddress> dnsServers = new HashSet<>();
+
+ private static final int RFC6177_MIN_PREFIX_LENGTH = 48;
+ private static final int RFC7421_PREFIX_LENGTH = 64;
+
+ public static final InetAddress INET6_ANY = InetAddresses.parseNumericAddress("::");
+
+ /**
+ * Create a InitialConfiguration that is a copy of the specified configuration.
+ */
+ public static InitialConfiguration copy(InitialConfiguration config) {
+ if (config == null) {
+ return null;
+ }
+ InitialConfiguration configCopy = new InitialConfiguration();
+ configCopy.ipAddresses.addAll(config.ipAddresses);
+ configCopy.directlyConnectedRoutes.addAll(config.directlyConnectedRoutes);
+ configCopy.dnsServers.addAll(config.dnsServers);
+ return configCopy;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "InitialConfiguration(IPs: {%s}, prefixes: {%s}, DNS: {%s})",
+ join(", ", ipAddresses), join(", ", directlyConnectedRoutes),
+ join(", ", dnsServers));
+ }
+
+ /**
+ * Tests whether the contents of this IpConfiguration represent a valid configuration.
+ */
+ public boolean isValid() {
+ if (ipAddresses.isEmpty()) {
+ return false;
+ }
+
+ // For every IP address, there must be at least one prefix containing that address.
+ for (LinkAddress addr : ipAddresses) {
+ if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) {
+ return false;
+ }
+ }
+ // For every dns server, there must be at least one prefix containing that address.
+ for (InetAddress addr : dnsServers) {
+ if (!any(directlyConnectedRoutes, (p) -> p.contains(addr))) {
+ return false;
+ }
+ }
+ // All IPv6 LinkAddresses have an RFC7421-suitable prefix length
+ // (read: compliant with RFC4291#section2.5.4).
+ if (any(ipAddresses, not(InitialConfiguration::isPrefixLengthCompliant))) {
+ return false;
+ }
+ // If directlyConnectedRoutes contains an IPv6 default route
+ // then ipAddresses MUST contain at least one non-ULA GUA.
+ if (any(directlyConnectedRoutes, InitialConfiguration::isIPv6DefaultRoute)
+ && all(ipAddresses, not(InitialConfiguration::isIPv6GUA))) {
+ return false;
+ }
+ // The prefix length of routes in directlyConnectedRoutes be within reasonable
+ // bounds for IPv6: /48-/64 just as we’d accept in RIOs.
+ if (any(directlyConnectedRoutes, not(InitialConfiguration::isPrefixLengthCompliant))) {
+ return false;
+ }
+ // There no more than one IPv4 address
+ if (ipAddresses.stream().filter(InitialConfiguration::isIPv4).count() > 1) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @return true if the given list of addressess and routes satisfies provisioning for this
+ * InitialConfiguration. LinkAddresses and RouteInfo objects are not compared with equality
+ * because addresses and routes seen by Netlink will contain additional fields like flags,
+ * interfaces, and so on. If this InitialConfiguration has no IP address specified, the
+ * provisioning check always fails.
+ *
+ * If the given list of routes is null, only addresses are taken into considerations.
+ */
+ public boolean isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes) {
+ if (ipAddresses.isEmpty()) {
+ return false;
+ }
+
+ for (LinkAddress addr : ipAddresses) {
+ if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) {
+ return false;
+ }
+ }
+
+ if (routes != null) {
+ for (IpPrefix prefix : directlyConnectedRoutes) {
+ if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Convert this configuration to a {@link InitialConfigurationParcelable}.
+ */
+ public InitialConfigurationParcelable toStableParcelable() {
+ final InitialConfigurationParcelable p = new InitialConfigurationParcelable();
+ p.ipAddresses = ipAddresses.toArray(new LinkAddress[0]);
+ p.directlyConnectedRoutes = directlyConnectedRoutes.toArray(new IpPrefix[0]);
+ p.dnsServers = toParcelableArray(
+ dnsServers, IpConfigurationParcelableUtil::parcelAddress, String.class);
+ return p;
+ }
+
+ /**
+ * Create an instance of {@link InitialConfiguration} based on the contents of the specified
+ * {@link InitialConfigurationParcelable}.
+ */
+ public static InitialConfiguration fromStableParcelable(InitialConfigurationParcelable p) {
+ if (p == null) return null;
+ final InitialConfiguration config = new InitialConfiguration();
+ config.ipAddresses.addAll(Arrays.asList(p.ipAddresses));
+ config.directlyConnectedRoutes.addAll(Arrays.asList(p.directlyConnectedRoutes));
+ config.dnsServers.addAll(
+ fromParcelableArray(p.dnsServers, IpConfigurationParcelableUtil::unparcelAddress));
+ return config;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof InitialConfiguration)) return false;
+ final InitialConfiguration other = (InitialConfiguration) obj;
+ return ipAddresses.equals(other.ipAddresses)
+ && directlyConnectedRoutes.equals(other.directlyConnectedRoutes)
+ && dnsServers.equals(other.dnsServers);
+ }
+
+ private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) {
+ return !route.hasGateway() && prefix.equals(route.getDestination());
+ }
+
+ private static boolean isPrefixLengthCompliant(LinkAddress addr) {
+ return isIPv4(addr) || isCompliantIPv6PrefixLength(addr.getPrefixLength());
+ }
+
+ private static boolean isPrefixLengthCompliant(IpPrefix prefix) {
+ return isIPv4(prefix) || isCompliantIPv6PrefixLength(prefix.getPrefixLength());
+ }
+
+ private static boolean isCompliantIPv6PrefixLength(int prefixLength) {
+ return (RFC6177_MIN_PREFIX_LENGTH <= prefixLength)
+ && (prefixLength <= RFC7421_PREFIX_LENGTH);
+ }
+
+ private static boolean isIPv4(IpPrefix prefix) {
+ return prefix.getAddress() instanceof Inet4Address;
+ }
+
+ private static boolean isIPv4(LinkAddress addr) {
+ return addr.getAddress() instanceof Inet4Address;
+ }
+
+ private static boolean isIPv6DefaultRoute(IpPrefix prefix) {
+ return prefix.getAddress().equals(INET6_ANY);
+ }
+
+ private static boolean isIPv6GUA(LinkAddress addr) {
+ return addr.isIpv6() && addr.isGlobalPreferred();
+ }
+
+ // TODO: extract out into CollectionUtils.
+
+ /**
+ * Indicate whether any element of the specified iterable verifies the specified predicate.
+ */
+ public static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
+ for (T t : coll) {
+ if (fn.test(t)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Indicate whether all elements of the specified iterable verifies the specified predicate.
+ */
+ public static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
+ return !any(coll, not(fn));
+ }
+
+ /**
+ * Create a predicate that returns the opposite value of the specified predicate.
+ */
+ public static <T> Predicate<T> not(Predicate<T> fn) {
+ return (t) -> !fn.test(t);
+ }
+}
diff --git a/android/net/shared/IpConfigurationParcelableUtil.java b/android/net/shared/IpConfigurationParcelableUtil.java
new file mode 100644
index 0000000..172dc24
--- /dev/null
+++ b/android/net/shared/IpConfigurationParcelableUtil.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.shared;
+
+import android.annotation.Nullable;
+import android.net.DhcpResults;
+import android.net.DhcpResultsParcelable;
+import android.net.InetAddresses;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+
+/**
+ * Collection of utility methods to convert to and from stable AIDL parcelables for IpClient
+ * configuration classes.
+ * @hide
+ */
+public final class IpConfigurationParcelableUtil {
+ /**
+ * Convert DhcpResults to a DhcpResultsParcelable.
+ */
+ public static DhcpResultsParcelable toStableParcelable(@Nullable DhcpResults results) {
+ if (results == null) return null;
+ final DhcpResultsParcelable p = new DhcpResultsParcelable();
+ p.baseConfiguration = results.toStaticIpConfiguration();
+ p.leaseDuration = results.leaseDuration;
+ p.mtu = results.mtu;
+ p.serverAddress = parcelAddress(results.serverAddress);
+ p.vendorInfo = results.vendorInfo;
+ p.serverHostName = results.serverHostName;
+ return p;
+ }
+
+ /**
+ * Convert a DhcpResultsParcelable to DhcpResults.
+ */
+ public static DhcpResults fromStableParcelable(@Nullable DhcpResultsParcelable p) {
+ if (p == null) return null;
+ final DhcpResults results = new DhcpResults(p.baseConfiguration);
+ results.leaseDuration = p.leaseDuration;
+ results.mtu = p.mtu;
+ results.serverAddress = (Inet4Address) unparcelAddress(p.serverAddress);
+ results.vendorInfo = p.vendorInfo;
+ results.serverHostName = p.serverHostName;
+ return results;
+ }
+
+ /**
+ * Convert InetAddress to String.
+ * TODO: have an InetAddressParcelable
+ */
+ public static String parcelAddress(@Nullable InetAddress addr) {
+ if (addr == null) return null;
+ return addr.getHostAddress();
+ }
+
+ /**
+ * Convert String to InetAddress.
+ * TODO: have an InetAddressParcelable
+ */
+ public static InetAddress unparcelAddress(@Nullable String addr) {
+ if (addr == null) return null;
+ return InetAddresses.parseNumericAddress(addr);
+ }
+}
diff --git a/android/net/shared/LinkPropertiesParcelableUtil.java b/android/net/shared/LinkPropertiesParcelableUtil.java
new file mode 100644
index 0000000..1729da6
--- /dev/null
+++ b/android/net/shared/LinkPropertiesParcelableUtil.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.shared;
+
+import android.annotation.Nullable;
+import android.net.LinkProperties;
+import android.net.ProxyInfo;
+
+/**
+ * Collection of utility methods to convert to and from stable AIDL parcelables for LinkProperties
+ * and its attributes.
+ * @hide
+ */
+public final class LinkPropertiesParcelableUtil {
+ // Temporary methods to facilitate migrating clients away from LinkPropertiesParcelable
+ // TODO: remove the following methods after migrating clients.
+
+ /**
+ * @deprecated conversion to stable parcelable is no longer necessary.
+ */
+ @Deprecated
+ public static LinkProperties toStableParcelable(@Nullable LinkProperties lp) {
+ return lp;
+ }
+
+ /**
+ * @deprecated conversion to stable parcelable is no longer necessary.
+ */
+ @Deprecated
+ public static ProxyInfo toStableParcelable(@Nullable ProxyInfo info) {
+ return info;
+ }
+}
diff --git a/android/net/shared/NetworkMonitorUtils.java b/android/net/shared/NetworkMonitorUtils.java
new file mode 100644
index 0000000..46e9c73
--- /dev/null
+++ b/android/net/shared/NetworkMonitorUtils.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.shared;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+
+import android.net.NetworkCapabilities;
+
+/** @hide */
+public class NetworkMonitorUtils {
+
+ // Network conditions broadcast constants
+ public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
+ "android.net.conn.NETWORK_CONDITIONS_MEASURED";
+ public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type";
+ public static final String EXTRA_NETWORK_TYPE = "extra_network_type";
+ public static final String EXTRA_RESPONSE_RECEIVED = "extra_response_received";
+ public static final String EXTRA_IS_CAPTIVE_PORTAL = "extra_is_captive_portal";
+ public static final String EXTRA_CELL_ID = "extra_cellid";
+ public static final String EXTRA_SSID = "extra_ssid";
+ public static final String EXTRA_BSSID = "extra_bssid";
+ /** real time since boot */
+ public static final String EXTRA_REQUEST_TIMESTAMP_MS = "extra_request_timestamp_ms";
+ public static final String EXTRA_RESPONSE_TIMESTAMP_MS = "extra_response_timestamp_ms";
+ public static final String PERMISSION_ACCESS_NETWORK_CONDITIONS =
+ "android.permission.ACCESS_NETWORK_CONDITIONS";
+
+ /**
+ * Return whether validation is required for private DNS in strict mode.
+ * @param nc Network capabilities of the network to test.
+ */
+ public static boolean isPrivateDnsValidationRequired(NetworkCapabilities nc) {
+ // TODO: Consider requiring validation for DUN networks.
+ return nc != null
+ && nc.hasCapability(NET_CAPABILITY_INTERNET)
+ && nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ && nc.hasCapability(NET_CAPABILITY_TRUSTED);
+ }
+
+ /**
+ * Return whether validation is required for a network.
+ * @param nc Network capabilities of the network to test.
+ */
+ public static boolean isValidationRequired(NetworkCapabilities nc) {
+ // TODO: Consider requiring validation for DUN networks.
+ return isPrivateDnsValidationRequired(nc) && nc.hasCapability(NET_CAPABILITY_NOT_VPN);
+ }
+}
diff --git a/android/net/shared/ParcelableUtil.java b/android/net/shared/ParcelableUtil.java
new file mode 100644
index 0000000..3f40300
--- /dev/null
+++ b/android/net/shared/ParcelableUtil.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.shared;
+
+import android.annotation.NonNull;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.function.Function;
+
+/**
+ * Utility methods to help convert to/from stable parcelables.
+ * @hide
+ */
+public final class ParcelableUtil {
+ // Below methods could be implemented easily with streams, but streams are frowned upon in
+ // frameworks code.
+
+ /**
+ * Convert a list of BaseType items to an array of ParcelableType items using the specified
+ * converter function.
+ */
+ public static <ParcelableType, BaseType> ParcelableType[] toParcelableArray(
+ @NonNull Collection<BaseType> base,
+ @NonNull Function<BaseType, ParcelableType> conv,
+ @NonNull Class<ParcelableType> parcelClass) {
+ final ParcelableType[] out = (ParcelableType[]) Array.newInstance(parcelClass, base.size());
+ int i = 0;
+ for (BaseType b : base) {
+ out[i] = conv.apply(b);
+ i++;
+ }
+ return out;
+ }
+
+ /**
+ * Convert an array of ParcelableType items to a list of BaseType items using the specified
+ * converter function.
+ */
+ public static <ParcelableType, BaseType> ArrayList<BaseType> fromParcelableArray(
+ @NonNull ParcelableType[] parceled, @NonNull Function<ParcelableType, BaseType> conv) {
+ final ArrayList<BaseType> out = new ArrayList<>(parceled.length);
+ for (ParcelableType t : parceled) {
+ out.add(conv.apply(t));
+ }
+ return out;
+ }
+}
diff --git a/android/net/shared/PrivateDnsConfig.java b/android/net/shared/PrivateDnsConfig.java
new file mode 100644
index 0000000..c7dc530
--- /dev/null
+++ b/android/net/shared/PrivateDnsConfig.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.shared;
+
+import static android.net.shared.ParcelableUtil.fromParcelableArray;
+import static android.net.shared.ParcelableUtil.toParcelableArray;
+
+import android.net.PrivateDnsConfigParcel;
+import android.text.TextUtils;
+
+import java.net.InetAddress;
+import java.util.Arrays;
+
+/** @hide */
+public class PrivateDnsConfig {
+ public final boolean useTls;
+ public final String hostname;
+ public final InetAddress[] ips;
+
+ public PrivateDnsConfig() {
+ this(false);
+ }
+
+ public PrivateDnsConfig(boolean useTls) {
+ this.useTls = useTls;
+ this.hostname = "";
+ this.ips = new InetAddress[0];
+ }
+
+ public PrivateDnsConfig(String hostname, InetAddress[] ips) {
+ this.useTls = !TextUtils.isEmpty(hostname);
+ this.hostname = useTls ? hostname : "";
+ this.ips = (ips != null) ? ips : new InetAddress[0];
+ }
+
+ public PrivateDnsConfig(PrivateDnsConfig cfg) {
+ useTls = cfg.useTls;
+ hostname = cfg.hostname;
+ ips = cfg.ips;
+ }
+
+ /**
+ * Indicates whether this is a strict mode private DNS configuration.
+ */
+ public boolean inStrictMode() {
+ return useTls && !TextUtils.isEmpty(hostname);
+ }
+
+ @Override
+ public String toString() {
+ return PrivateDnsConfig.class.getSimpleName()
+ + "{" + useTls + ":" + hostname + "/" + Arrays.toString(ips) + "}";
+ }
+
+ /**
+ * Create a stable AIDL-compatible parcel from the current instance.
+ */
+ public PrivateDnsConfigParcel toParcel() {
+ final PrivateDnsConfigParcel parcel = new PrivateDnsConfigParcel();
+ parcel.hostname = hostname;
+ parcel.ips = toParcelableArray(
+ Arrays.asList(ips), IpConfigurationParcelableUtil::parcelAddress, String.class);
+
+ return parcel;
+ }
+
+ /**
+ * Build a configuration from a stable AIDL-compatible parcel.
+ */
+ public static PrivateDnsConfig fromParcel(PrivateDnsConfigParcel parcel) {
+ InetAddress[] ips = new InetAddress[parcel.ips.length];
+ ips = fromParcelableArray(parcel.ips, IpConfigurationParcelableUtil::unparcelAddress)
+ .toArray(ips);
+ return new PrivateDnsConfig(parcel.hostname, ips);
+ }
+}
diff --git a/android/net/shared/ProvisioningConfiguration.java b/android/net/shared/ProvisioningConfiguration.java
new file mode 100644
index 0000000..6f9c294
--- /dev/null
+++ b/android/net/shared/ProvisioningConfiguration.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.shared;
+
+import android.annotation.Nullable;
+import android.net.INetd;
+import android.net.Network;
+import android.net.ProvisioningConfigurationParcelable;
+import android.net.StaticIpConfiguration;
+import android.net.apf.ApfCapabilities;
+import android.net.ip.IIpClient;
+
+import java.util.Objects;
+import java.util.StringJoiner;
+
+/**
+ * This class encapsulates parameters to be passed to
+ * IpClient#startProvisioning(). A defensive copy is made by IpClient
+ * and the values specified herein are in force until IpClient#stop()
+ * is called.
+ *
+ * Example use:
+ *
+ * final ProvisioningConfiguration config =
+ * new ProvisioningConfiguration.Builder()
+ * .withPreDhcpAction()
+ * .withProvisioningTimeoutMs(36 * 1000)
+ * .build();
+ * mIpClient.startProvisioning(config.toStableParcelable());
+ * ...
+ * mIpClient.stop();
+ *
+ * The specified provisioning configuration will only be active until
+ * IIpClient#stop() is called. Future calls to IIpClient#startProvisioning()
+ * must specify the configuration again.
+ * @hide
+ */
+public class ProvisioningConfiguration {
+ // TODO: Delete this default timeout once those callers that care are
+ // fixed to pass in their preferred timeout.
+ //
+ // We pick 36 seconds so we can send DHCP requests at
+ //
+ // t=0, t=2, t=6, t=14, t=30
+ //
+ // allowing for 10% jitter.
+ private static final int DEFAULT_TIMEOUT_MS = 36 * 1000;
+
+ /**
+ * Builder to create a {@link ProvisioningConfiguration}.
+ */
+ public static class Builder {
+ protected ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
+
+ /**
+ * Specify that the configuration should not enable IPv4. It is enabled by default.
+ */
+ public Builder withoutIPv4() {
+ mConfig.mEnableIPv4 = false;
+ return this;
+ }
+
+ /**
+ * Specify that the configuration should not enable IPv6. It is enabled by default.
+ */
+ public Builder withoutIPv6() {
+ mConfig.mEnableIPv6 = false;
+ return this;
+ }
+
+ /**
+ * Specify that the configuration should not use a MultinetworkPolicyTracker. It is used
+ * by default.
+ */
+ public Builder withoutMultinetworkPolicyTracker() {
+ mConfig.mUsingMultinetworkPolicyTracker = false;
+ return this;
+ }
+
+ /**
+ * Specify that the configuration should not use a IpReachabilityMonitor. It is used by
+ * default.
+ */
+ public Builder withoutIpReachabilityMonitor() {
+ mConfig.mUsingIpReachabilityMonitor = false;
+ return this;
+ }
+
+ /**
+ * Identical to {@link #withPreDhcpAction(int)}, using a default timeout.
+ * @see #withPreDhcpAction(int)
+ */
+ public Builder withPreDhcpAction() {
+ mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS;
+ return this;
+ }
+
+ /**
+ * Specify that {@link IpClientCallbacks#onPreDhcpAction()} should be called. Clients must
+ * call {@link IIpClient#completedPreDhcpAction()} when the callback called. This behavior
+ * is disabled by default.
+ * @param dhcpActionTimeoutMs Timeout for clients to call completedPreDhcpAction().
+ */
+ public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
+ mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs;
+ return this;
+ }
+
+ /**
+ * Specify the initial provisioning configuration.
+ */
+ public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
+ mConfig.mInitialConfig = initialConfig;
+ return this;
+ }
+
+ /**
+ * Specify a static configuration for provisioning.
+ */
+ public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
+ mConfig.mStaticIpConfig = staticConfig;
+ return this;
+ }
+
+ /**
+ * Specify ApfCapabilities.
+ */
+ public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
+ mConfig.mApfCapabilities = apfCapabilities;
+ return this;
+ }
+
+ /**
+ * Specify the timeout to use for provisioning.
+ */
+ public Builder withProvisioningTimeoutMs(int timeoutMs) {
+ mConfig.mProvisioningTimeoutMs = timeoutMs;
+ return this;
+ }
+
+ /**
+ * Specify that IPv6 address generation should use a random MAC address.
+ */
+ public Builder withRandomMacAddress() {
+ mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64;
+ return this;
+ }
+
+ /**
+ * Specify that IPv6 address generation should use a stable MAC address.
+ */
+ public Builder withStableMacAddress() {
+ mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+ return this;
+ }
+
+ /**
+ * Specify the network to use for provisioning.
+ */
+ public Builder withNetwork(Network network) {
+ mConfig.mNetwork = network;
+ return this;
+ }
+
+ /**
+ * Specify the display name that the IpClient should use.
+ */
+ public Builder withDisplayName(String displayName) {
+ mConfig.mDisplayName = displayName;
+ return this;
+ }
+
+ /**
+ * Build the configuration using previously specified parameters.
+ */
+ public ProvisioningConfiguration build() {
+ return new ProvisioningConfiguration(mConfig);
+ }
+ }
+
+ public boolean mEnableIPv4 = true;
+ public boolean mEnableIPv6 = true;
+ public boolean mUsingMultinetworkPolicyTracker = true;
+ public boolean mUsingIpReachabilityMonitor = true;
+ public int mRequestedPreDhcpActionMs;
+ public InitialConfiguration mInitialConfig;
+ public StaticIpConfiguration mStaticIpConfig;
+ public ApfCapabilities mApfCapabilities;
+ public int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
+ public int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+ public Network mNetwork = null;
+ public String mDisplayName = null;
+
+ public ProvisioningConfiguration() {} // used by Builder
+
+ public ProvisioningConfiguration(ProvisioningConfiguration other) {
+ mEnableIPv4 = other.mEnableIPv4;
+ mEnableIPv6 = other.mEnableIPv6;
+ mUsingMultinetworkPolicyTracker = other.mUsingMultinetworkPolicyTracker;
+ mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
+ mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
+ mInitialConfig = InitialConfiguration.copy(other.mInitialConfig);
+ mStaticIpConfig = other.mStaticIpConfig == null
+ ? null
+ : new StaticIpConfiguration(other.mStaticIpConfig);
+ mApfCapabilities = other.mApfCapabilities;
+ mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
+ mIPv6AddrGenMode = other.mIPv6AddrGenMode;
+ mNetwork = other.mNetwork;
+ mDisplayName = other.mDisplayName;
+ }
+
+ /**
+ * Create a ProvisioningConfigurationParcelable from this ProvisioningConfiguration.
+ */
+ public ProvisioningConfigurationParcelable toStableParcelable() {
+ final ProvisioningConfigurationParcelable p = new ProvisioningConfigurationParcelable();
+ p.enableIPv4 = mEnableIPv4;
+ p.enableIPv6 = mEnableIPv6;
+ p.usingMultinetworkPolicyTracker = mUsingMultinetworkPolicyTracker;
+ p.usingIpReachabilityMonitor = mUsingIpReachabilityMonitor;
+ p.requestedPreDhcpActionMs = mRequestedPreDhcpActionMs;
+ p.initialConfig = mInitialConfig == null ? null : mInitialConfig.toStableParcelable();
+ p.staticIpConfig = mStaticIpConfig == null
+ ? null
+ : new StaticIpConfiguration(mStaticIpConfig);
+ p.apfCapabilities = mApfCapabilities; // ApfCapabilities is immutable
+ p.provisioningTimeoutMs = mProvisioningTimeoutMs;
+ p.ipv6AddrGenMode = mIPv6AddrGenMode;
+ p.network = mNetwork;
+ p.displayName = mDisplayName;
+ return p;
+ }
+
+ /**
+ * Create a ProvisioningConfiguration from a ProvisioningConfigurationParcelable.
+ */
+ public static ProvisioningConfiguration fromStableParcelable(
+ @Nullable ProvisioningConfigurationParcelable p) {
+ if (p == null) return null;
+ final ProvisioningConfiguration config = new ProvisioningConfiguration();
+ config.mEnableIPv4 = p.enableIPv4;
+ config.mEnableIPv6 = p.enableIPv6;
+ config.mUsingMultinetworkPolicyTracker = p.usingMultinetworkPolicyTracker;
+ config.mUsingIpReachabilityMonitor = p.usingIpReachabilityMonitor;
+ config.mRequestedPreDhcpActionMs = p.requestedPreDhcpActionMs;
+ config.mInitialConfig = InitialConfiguration.fromStableParcelable(p.initialConfig);
+ config.mStaticIpConfig = p.staticIpConfig == null
+ ? null
+ : new StaticIpConfiguration(p.staticIpConfig);
+ config.mApfCapabilities = p.apfCapabilities; // ApfCapabilities is immutable
+ config.mProvisioningTimeoutMs = p.provisioningTimeoutMs;
+ config.mIPv6AddrGenMode = p.ipv6AddrGenMode;
+ config.mNetwork = p.network;
+ config.mDisplayName = p.displayName;
+ return config;
+ }
+
+ @Override
+ public String toString() {
+ return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
+ .add("mEnableIPv4: " + mEnableIPv4)
+ .add("mEnableIPv6: " + mEnableIPv6)
+ .add("mUsingMultinetworkPolicyTracker: " + mUsingMultinetworkPolicyTracker)
+ .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
+ .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
+ .add("mInitialConfig: " + mInitialConfig)
+ .add("mStaticIpConfig: " + mStaticIpConfig)
+ .add("mApfCapabilities: " + mApfCapabilities)
+ .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
+ .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode)
+ .add("mNetwork: " + mNetwork)
+ .add("mDisplayName: " + mDisplayName)
+ .toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ProvisioningConfiguration)) return false;
+ final ProvisioningConfiguration other = (ProvisioningConfiguration) obj;
+ return mEnableIPv4 == other.mEnableIPv4
+ && mEnableIPv6 == other.mEnableIPv6
+ && mUsingMultinetworkPolicyTracker == other.mUsingMultinetworkPolicyTracker
+ && mUsingIpReachabilityMonitor == other.mUsingIpReachabilityMonitor
+ && mRequestedPreDhcpActionMs == other.mRequestedPreDhcpActionMs
+ && Objects.equals(mInitialConfig, other.mInitialConfig)
+ && Objects.equals(mStaticIpConfig, other.mStaticIpConfig)
+ && Objects.equals(mApfCapabilities, other.mApfCapabilities)
+ && mProvisioningTimeoutMs == other.mProvisioningTimeoutMs
+ && mIPv6AddrGenMode == other.mIPv6AddrGenMode
+ && Objects.equals(mNetwork, other.mNetwork)
+ && Objects.equals(mDisplayName, other.mDisplayName);
+ }
+
+ public boolean isValid() {
+ return (mInitialConfig == null) || mInitialConfig.isValid();
+ }
+}
diff --git a/android/net/sip/SimpleSessionDescription.java b/android/net/sip/SimpleSessionDescription.java
new file mode 100644
index 0000000..9fcd21d
--- /dev/null
+++ b/android/net/sip/SimpleSessionDescription.java
@@ -0,0 +1,613 @@
+/*
+ * Copyright (C) 2010 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.sip;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * An object used to manipulate messages of Session Description Protocol (SDP).
+ * It is mainly designed for the uses of Session Initiation Protocol (SIP).
+ * Therefore, it only handles connection addresses ("c="), bandwidth limits,
+ * ("b="), encryption keys ("k="), and attribute fields ("a="). Currently this
+ * implementation does not support multicast sessions.
+ *
+ * <p>Here is an example code to create a session description.</p>
+ * <pre>
+ * SimpleSessionDescription description = new SimpleSessionDescription(
+ * System.currentTimeMillis(), "1.2.3.4");
+ * Media media = description.newMedia("audio", 56789, 1, "RTP/AVP");
+ * media.setRtpPayload(0, "PCMU/8000", null);
+ * media.setRtpPayload(8, "PCMA/8000", null);
+ * media.setRtpPayload(127, "telephone-event/8000", "0-15");
+ * media.setAttribute("sendrecv", "");
+ * </pre>
+ * <p>Invoking <code>description.encode()</code> will produce a result like the
+ * one below.</p>
+ * <pre>
+ * v=0
+ * o=- 1284970442706 1284970442709 IN IP4 1.2.3.4
+ * s=-
+ * c=IN IP4 1.2.3.4
+ * t=0 0
+ * m=audio 56789 RTP/AVP 0 8 127
+ * a=rtpmap:0 PCMU/8000
+ * a=rtpmap:8 PCMA/8000
+ * a=rtpmap:127 telephone-event/8000
+ * a=fmtp:127 0-15
+ * a=sendrecv
+ * </pre>
+ * @hide
+ */
+public class SimpleSessionDescription {
+ private final Fields mFields = new Fields("voscbtka");
+ private final ArrayList<Media> mMedia = new ArrayList<Media>();
+
+ /**
+ * Creates a minimal session description from the given session ID and
+ * unicast address. The address is used in the origin field ("o=") and the
+ * connection field ("c="). See {@link SimpleSessionDescription} for an
+ * example of its usage.
+ */
+ public SimpleSessionDescription(long sessionId, String address) {
+ address = (address.indexOf(':') < 0 ? "IN IP4 " : "IN IP6 ") + address;
+ mFields.parse("v=0");
+ mFields.parse(String.format(Locale.US, "o=- %d %d %s", sessionId,
+ System.currentTimeMillis(), address));
+ mFields.parse("s=-");
+ mFields.parse("t=0 0");
+ mFields.parse("c=" + address);
+ }
+
+ /**
+ * Creates a session description from the given message.
+ *
+ * @throws IllegalArgumentException if message is invalid.
+ */
+ public SimpleSessionDescription(String message) {
+ String[] lines = message.trim().replaceAll(" +", " ").split("[\r\n]+");
+ Fields fields = mFields;
+
+ for (String line : lines) {
+ try {
+ if (line.charAt(1) != '=') {
+ throw new IllegalArgumentException();
+ }
+ if (line.charAt(0) == 'm') {
+ String[] parts = line.substring(2).split(" ", 4);
+ String[] ports = parts[1].split("/", 2);
+ Media media = newMedia(parts[0], Integer.parseInt(ports[0]),
+ (ports.length < 2) ? 1 : Integer.parseInt(ports[1]),
+ parts[2]);
+ for (String format : parts[3].split(" ")) {
+ media.setFormat(format, null);
+ }
+ fields = media;
+ } else {
+ fields.parse(line);
+ }
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Invalid SDP: " + line);
+ }
+ }
+ }
+
+ /**
+ * Creates a new media description in this session description.
+ *
+ * @param type The media type, e.g. {@code "audio"}.
+ * @param port The first transport port used by this media.
+ * @param portCount The number of contiguous ports used by this media.
+ * @param protocol The transport protocol, e.g. {@code "RTP/AVP"}.
+ */
+ public Media newMedia(String type, int port, int portCount,
+ String protocol) {
+ Media media = new Media(type, port, portCount, protocol);
+ mMedia.add(media);
+ return media;
+ }
+
+ /**
+ * Returns all the media descriptions in this session description.
+ */
+ public Media[] getMedia() {
+ return mMedia.toArray(new Media[mMedia.size()]);
+ }
+
+ /**
+ * Encodes the session description and all its media descriptions in a
+ * string. Note that the result might be incomplete if a required field
+ * has never been added before.
+ */
+ public String encode() {
+ StringBuilder buffer = new StringBuilder();
+ mFields.write(buffer);
+ for (Media media : mMedia) {
+ media.write(buffer);
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * Returns the connection address or {@code null} if it is not present.
+ */
+ public String getAddress() {
+ return mFields.getAddress();
+ }
+
+ /**
+ * Sets the connection address. The field will be removed if the address
+ * is {@code null}.
+ */
+ public void setAddress(String address) {
+ mFields.setAddress(address);
+ }
+
+ /**
+ * Returns the encryption method or {@code null} if it is not present.
+ */
+ public String getEncryptionMethod() {
+ return mFields.getEncryptionMethod();
+ }
+
+ /**
+ * Returns the encryption key or {@code null} if it is not present.
+ */
+ public String getEncryptionKey() {
+ return mFields.getEncryptionKey();
+ }
+
+ /**
+ * Sets the encryption method and the encryption key. The field will be
+ * removed if the method is {@code null}.
+ */
+ public void setEncryption(String method, String key) {
+ mFields.setEncryption(method, key);
+ }
+
+ /**
+ * Returns the types of the bandwidth limits.
+ */
+ public String[] getBandwidthTypes() {
+ return mFields.getBandwidthTypes();
+ }
+
+ /**
+ * Returns the bandwidth limit of the given type or {@code -1} if it is not
+ * present.
+ */
+ public int getBandwidth(String type) {
+ return mFields.getBandwidth(type);
+ }
+
+ /**
+ * Sets the bandwith limit for the given type. The field will be removed if
+ * the value is negative.
+ */
+ public void setBandwidth(String type, int value) {
+ mFields.setBandwidth(type, value);
+ }
+
+ /**
+ * Returns the names of all the attributes.
+ */
+ public String[] getAttributeNames() {
+ return mFields.getAttributeNames();
+ }
+
+ /**
+ * Returns the attribute of the given name or {@code null} if it is not
+ * present.
+ */
+ public String getAttribute(String name) {
+ return mFields.getAttribute(name);
+ }
+
+ /**
+ * Sets the attribute for the given name. The field will be removed if
+ * the value is {@code null}. To set a binary attribute, use an empty
+ * string as the value.
+ */
+ public void setAttribute(String name, String value) {
+ mFields.setAttribute(name, value);
+ }
+
+ /**
+ * This class represents a media description of a session description. It
+ * can only be created by {@link SimpleSessionDescription#newMedia}. Since
+ * the syntax is more restricted for RTP based protocols, two sets of access
+ * methods are implemented. See {@link SimpleSessionDescription} for an
+ * example of its usage.
+ */
+ public static class Media extends Fields {
+ private final String mType;
+ private final int mPort;
+ private final int mPortCount;
+ private final String mProtocol;
+ private ArrayList<String> mFormats = new ArrayList<String>();
+
+ private Media(String type, int port, int portCount, String protocol) {
+ super("icbka");
+ mType = type;
+ mPort = port;
+ mPortCount = portCount;
+ mProtocol = protocol;
+ }
+
+ /**
+ * Returns the media type.
+ */
+ public String getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the first transport port used by this media.
+ */
+ public int getPort() {
+ return mPort;
+ }
+
+ /**
+ * Returns the number of contiguous ports used by this media.
+ */
+ public int getPortCount() {
+ return mPortCount;
+ }
+
+ /**
+ * Returns the transport protocol.
+ */
+ public String getProtocol() {
+ return mProtocol;
+ }
+
+ /**
+ * Returns the media formats.
+ */
+ public String[] getFormats() {
+ return mFormats.toArray(new String[mFormats.size()]);
+ }
+
+ /**
+ * Returns the {@code fmtp} attribute of the given format or
+ * {@code null} if it is not present.
+ */
+ public String getFmtp(String format) {
+ return super.get("a=fmtp:" + format, ' ');
+ }
+
+ /**
+ * Sets a format and its {@code fmtp} attribute. If the attribute is
+ * {@code null}, the corresponding field will be removed.
+ */
+ public void setFormat(String format, String fmtp) {
+ mFormats.remove(format);
+ mFormats.add(format);
+ super.set("a=rtpmap:" + format, ' ', null);
+ super.set("a=fmtp:" + format, ' ', fmtp);
+ }
+
+ /**
+ * Removes a format and its {@code fmtp} attribute.
+ */
+ public void removeFormat(String format) {
+ mFormats.remove(format);
+ super.set("a=rtpmap:" + format, ' ', null);
+ super.set("a=fmtp:" + format, ' ', null);
+ }
+
+ /**
+ * Returns the RTP payload types.
+ */
+ public int[] getRtpPayloadTypes() {
+ int[] types = new int[mFormats.size()];
+ int length = 0;
+ for (String format : mFormats) {
+ try {
+ types[length] = Integer.parseInt(format);
+ ++length;
+ } catch (NumberFormatException e) { }
+ }
+ return Arrays.copyOf(types, length);
+ }
+
+ /**
+ * Returns the {@code rtpmap} attribute of the given RTP payload type
+ * or {@code null} if it is not present.
+ */
+ public String getRtpmap(int type) {
+ return super.get("a=rtpmap:" + type, ' ');
+ }
+
+ /**
+ * Returns the {@code fmtp} attribute of the given RTP payload type or
+ * {@code null} if it is not present.
+ */
+ public String getFmtp(int type) {
+ return super.get("a=fmtp:" + type, ' ');
+ }
+
+ /**
+ * Sets a RTP payload type and its {@code rtpmap} and {@code fmtp}
+ * attributes. If any of the attributes is {@code null}, the
+ * corresponding field will be removed. See
+ * {@link SimpleSessionDescription} for an example of its usage.
+ */
+ public void setRtpPayload(int type, String rtpmap, String fmtp) {
+ String format = String.valueOf(type);
+ mFormats.remove(format);
+ mFormats.add(format);
+ super.set("a=rtpmap:" + format, ' ', rtpmap);
+ super.set("a=fmtp:" + format, ' ', fmtp);
+ }
+
+ /**
+ * Removes a RTP payload and its {@code rtpmap} and {@code fmtp}
+ * attributes.
+ */
+ public void removeRtpPayload(int type) {
+ removeFormat(String.valueOf(type));
+ }
+
+ private void write(StringBuilder buffer) {
+ buffer.append("m=").append(mType).append(' ').append(mPort);
+ if (mPortCount != 1) {
+ buffer.append('/').append(mPortCount);
+ }
+ buffer.append(' ').append(mProtocol);
+ for (String format : mFormats) {
+ buffer.append(' ').append(format);
+ }
+ buffer.append("\r\n");
+ super.write(buffer);
+ }
+ }
+
+ /**
+ * This class acts as a set of fields, and the size of the set is expected
+ * to be small. Therefore, it uses a simple list instead of maps. Each field
+ * has three parts: a key, a delimiter, and a value. Delimiters are special
+ * because they are not included in binary attributes. As a result, the
+ * private methods, which are the building blocks of this class, all take
+ * the delimiter as an argument.
+ */
+ private static class Fields {
+ private final String mOrder;
+ private final ArrayList<String> mLines = new ArrayList<String>();
+
+ Fields(String order) {
+ mOrder = order;
+ }
+
+ /**
+ * Returns the connection address or {@code null} if it is not present.
+ */
+ public String getAddress() {
+ String address = get("c", '=');
+ if (address == null) {
+ return null;
+ }
+ String[] parts = address.split(" ");
+ if (parts.length != 3) {
+ return null;
+ }
+ int slash = parts[2].indexOf('/');
+ return (slash < 0) ? parts[2] : parts[2].substring(0, slash);
+ }
+
+ /**
+ * Sets the connection address. The field will be removed if the address
+ * is {@code null}.
+ */
+ public void setAddress(String address) {
+ if (address != null) {
+ address = (address.indexOf(':') < 0 ? "IN IP4 " : "IN IP6 ") +
+ address;
+ }
+ set("c", '=', address);
+ }
+
+ /**
+ * Returns the encryption method or {@code null} if it is not present.
+ */
+ public String getEncryptionMethod() {
+ String encryption = get("k", '=');
+ if (encryption == null) {
+ return null;
+ }
+ int colon = encryption.indexOf(':');
+ return (colon == -1) ? encryption : encryption.substring(0, colon);
+ }
+
+ /**
+ * Returns the encryption key or {@code null} if it is not present.
+ */
+ public String getEncryptionKey() {
+ String encryption = get("k", '=');
+ if (encryption == null) {
+ return null;
+ }
+ int colon = encryption.indexOf(':');
+ return (colon == -1) ? null : encryption.substring(0, colon + 1);
+ }
+
+ /**
+ * Sets the encryption method and the encryption key. The field will be
+ * removed if the method is {@code null}.
+ */
+ public void setEncryption(String method, String key) {
+ set("k", '=', (method == null || key == null) ?
+ method : method + ':' + key);
+ }
+
+ /**
+ * Returns the types of the bandwidth limits.
+ */
+ public String[] getBandwidthTypes() {
+ return cut("b=", ':');
+ }
+
+ /**
+ * Returns the bandwidth limit of the given type or {@code -1} if it is
+ * not present.
+ */
+ public int getBandwidth(String type) {
+ String value = get("b=" + type, ':');
+ if (value != null) {
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) { }
+ setBandwidth(type, -1);
+ }
+ return -1;
+ }
+
+ /**
+ * Sets the bandwith limit for the given type. The field will be removed
+ * if the value is negative.
+ */
+ public void setBandwidth(String type, int value) {
+ set("b=" + type, ':', (value < 0) ? null : String.valueOf(value));
+ }
+
+ /**
+ * Returns the names of all the attributes.
+ */
+ public String[] getAttributeNames() {
+ return cut("a=", ':');
+ }
+
+ /**
+ * Returns the attribute of the given name or {@code null} if it is not
+ * present.
+ */
+ public String getAttribute(String name) {
+ return get("a=" + name, ':');
+ }
+
+ /**
+ * Sets the attribute for the given name. The field will be removed if
+ * the value is {@code null}. To set a binary attribute, use an empty
+ * string as the value.
+ */
+ public void setAttribute(String name, String value) {
+ set("a=" + name, ':', value);
+ }
+
+ private void write(StringBuilder buffer) {
+ for (int i = 0; i < mOrder.length(); ++i) {
+ char type = mOrder.charAt(i);
+ for (String line : mLines) {
+ if (line.charAt(0) == type) {
+ buffer.append(line).append("\r\n");
+ }
+ }
+ }
+ }
+
+ /**
+ * Invokes {@link #set} after splitting the line into three parts.
+ */
+ private void parse(String line) {
+ char type = line.charAt(0);
+ if (mOrder.indexOf(type) == -1) {
+ return;
+ }
+ char delimiter = '=';
+ if (line.startsWith("a=rtpmap:") || line.startsWith("a=fmtp:")) {
+ delimiter = ' ';
+ } else if (type == 'b' || type == 'a') {
+ delimiter = ':';
+ }
+ int i = line.indexOf(delimiter);
+ if (i == -1) {
+ set(line, delimiter, "");
+ } else {
+ set(line.substring(0, i), delimiter, line.substring(i + 1));
+ }
+ }
+
+ /**
+ * Finds the key with the given prefix and returns its suffix.
+ */
+ private String[] cut(String prefix, char delimiter) {
+ String[] names = new String[mLines.size()];
+ int length = 0;
+ for (String line : mLines) {
+ if (line.startsWith(prefix)) {
+ int i = line.indexOf(delimiter);
+ if (i == -1) {
+ i = line.length();
+ }
+ names[length] = line.substring(prefix.length(), i);
+ ++length;
+ }
+ }
+ return Arrays.copyOf(names, length);
+ }
+
+ /**
+ * Returns the index of the key.
+ */
+ private int find(String key, char delimiter) {
+ int length = key.length();
+ for (int i = mLines.size() - 1; i >= 0; --i) {
+ String line = mLines.get(i);
+ if (line.startsWith(key) && (line.length() == length ||
+ line.charAt(length) == delimiter)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Sets the key with the value or removes the key if the value is
+ * {@code null}.
+ */
+ private void set(String key, char delimiter, String value) {
+ int index = find(key, delimiter);
+ if (value != null) {
+ if (value.length() != 0) {
+ key = key + delimiter + value;
+ }
+ if (index == -1) {
+ mLines.add(key);
+ } else {
+ mLines.set(index, key);
+ }
+ } else if (index != -1) {
+ mLines.remove(index);
+ }
+ }
+
+ /**
+ * Returns the value of the key.
+ */
+ private String get(String key, char delimiter) {
+ int index = find(key, delimiter);
+ if (index == -1) {
+ return null;
+ }
+ String line = mLines.get(index);
+ int length = key.length();
+ return (line.length() == length) ? "" : line.substring(length + 1);
+ }
+ }
+}
diff --git a/android/net/sip/SipAudioCall.java b/android/net/sip/SipAudioCall.java
new file mode 100644
index 0000000..ea943e9
--- /dev/null
+++ b/android/net/sip/SipAudioCall.java
@@ -0,0 +1,1143 @@
+/*
+ * Copyright (C) 2010 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.sip;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.net.rtp.AudioCodec;
+import android.net.rtp.AudioGroup;
+import android.net.rtp.AudioStream;
+import android.net.rtp.RtpStream;
+import android.net.sip.SimpleSessionDescription.Media;
+import android.net.wifi.WifiManager;
+import android.os.Message;
+import android.telephony.Rlog;
+import android.text.TextUtils;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Handles an Internet audio call over SIP. You can instantiate this class with {@link SipManager},
+ * using {@link SipManager#makeAudioCall makeAudioCall()} and {@link SipManager#takeAudioCall
+ * takeAudioCall()}.
+ *
+ * <p class="note"><strong>Note:</strong> Using this class require the
+ * {@link android.Manifest.permission#INTERNET} and
+ * {@link android.Manifest.permission#USE_SIP} permissions. In addition, {@link
+ * #startAudio} requires the
+ * {@link android.Manifest.permission#RECORD_AUDIO},
+ * {@link android.Manifest.permission#ACCESS_WIFI_STATE}, and
+ * {@link android.Manifest.permission#WAKE_LOCK} permissions; and {@link #setSpeakerMode
+ * setSpeakerMode()} requires the
+ * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.</p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using SIP, read the
+ * <a href="{@docRoot}guide/topics/network/sip.html">Session Initiation Protocol</a>
+ * developer guide.</p>
+ * </div>
+ */
+public class SipAudioCall {
+ private static final String LOG_TAG = SipAudioCall.class.getSimpleName();
+ private static final boolean DBG = true;
+ private static final boolean RELEASE_SOCKET = true;
+ private static final boolean DONT_RELEASE_SOCKET = false;
+ private static final int SESSION_TIMEOUT = 5; // in seconds
+ private static final int TRANSFER_TIMEOUT = 15; // in seconds
+
+ /** Listener for events relating to a SIP call, such as when a call is being
+ * recieved ("on ringing") or a call is outgoing ("on calling").
+ * <p>Many of these events are also received by {@link SipSession.Listener}.</p>
+ */
+ public static class Listener {
+ /**
+ * Called when the call object is ready to make another call.
+ * The default implementation calls {@link #onChanged}.
+ *
+ * @param call the call object that is ready to make another call
+ */
+ public void onReadyToCall(SipAudioCall call) {
+ onChanged(call);
+ }
+
+ /**
+ * Called when a request is sent out to initiate a new call.
+ * The default implementation calls {@link #onChanged}.
+ *
+ * @param call the call object that carries out the audio call
+ */
+ public void onCalling(SipAudioCall call) {
+ onChanged(call);
+ }
+
+ /**
+ * Called when a new call comes in.
+ * The default implementation calls {@link #onChanged}.
+ *
+ * @param call the call object that carries out the audio call
+ * @param caller the SIP profile of the caller
+ */
+ public void onRinging(SipAudioCall call, SipProfile caller) {
+ onChanged(call);
+ }
+
+ /**
+ * Called when a RINGING response is received for the INVITE request
+ * sent. The default implementation calls {@link #onChanged}.
+ *
+ * @param call the call object that carries out the audio call
+ */
+ public void onRingingBack(SipAudioCall call) {
+ onChanged(call);
+ }
+
+ /**
+ * Called when the session is established.
+ * The default implementation calls {@link #onChanged}.
+ *
+ * @param call the call object that carries out the audio call
+ */
+ public void onCallEstablished(SipAudioCall call) {
+ onChanged(call);
+ }
+
+ /**
+ * Called when the session is terminated.
+ * The default implementation calls {@link #onChanged}.
+ *
+ * @param call the call object that carries out the audio call
+ */
+ public void onCallEnded(SipAudioCall call) {
+ onChanged(call);
+ }
+
+ /**
+ * Called when the peer is busy during session initialization.
+ * The default implementation calls {@link #onChanged}.
+ *
+ * @param call the call object that carries out the audio call
+ */
+ public void onCallBusy(SipAudioCall call) {
+ onChanged(call);
+ }
+
+ /**
+ * Called when the call is on hold.
+ * The default implementation calls {@link #onChanged}.
+ *
+ * @param call the call object that carries out the audio call
+ */
+ public void onCallHeld(SipAudioCall call) {
+ onChanged(call);
+ }
+
+ /**
+ * Called when an error occurs. The default implementation is no op.
+ *
+ * @param call the call object that carries out the audio call
+ * @param errorCode error code of this error
+ * @param errorMessage error message
+ * @see SipErrorCode
+ */
+ public void onError(SipAudioCall call, int errorCode,
+ String errorMessage) {
+ // no-op
+ }
+
+ /**
+ * Called when an event occurs and the corresponding callback is not
+ * overridden. The default implementation is no op. Error events are
+ * not re-directed to this callback and are handled in {@link #onError}.
+ */
+ public void onChanged(SipAudioCall call) {
+ // no-op
+ }
+ }
+
+ private Context mContext;
+ private SipProfile mLocalProfile;
+ private SipAudioCall.Listener mListener;
+ private SipSession mSipSession;
+ private SipSession mTransferringSession;
+
+ private long mSessionId = System.currentTimeMillis();
+ private String mPeerSd;
+
+ private AudioStream mAudioStream;
+ private AudioGroup mAudioGroup;
+
+ private boolean mInCall = false;
+ private boolean mMuted = false;
+ private boolean mHold = false;
+
+ private WifiManager mWm;
+ private WifiManager.WifiLock mWifiHighPerfLock;
+
+ private int mErrorCode = SipErrorCode.NO_ERROR;
+ private String mErrorMessage;
+
+ /**
+ * Creates a call object with the local SIP profile.
+ * @param context the context for accessing system services such as
+ * ringtone, audio, WIFI etc
+ */
+ public SipAudioCall(Context context, SipProfile localProfile) {
+ mContext = context;
+ mLocalProfile = localProfile;
+ mWm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ }
+
+ /**
+ * Sets the listener to listen to the audio call events. The method calls
+ * {@link #setListener setListener(listener, false)}.
+ *
+ * @param listener to listen to the audio call events of this object
+ * @see #setListener(Listener, boolean)
+ */
+ public void setListener(SipAudioCall.Listener listener) {
+ setListener(listener, false);
+ }
+
+ /**
+ * Sets the listener to listen to the audio call events. A
+ * {@link SipAudioCall} can only hold one listener at a time. Subsequent
+ * calls to this method override the previous listener.
+ *
+ * @param listener to listen to the audio call events of this object
+ * @param callbackImmediately set to true if the caller wants to be called
+ * back immediately on the current state
+ */
+ public void setListener(SipAudioCall.Listener listener,
+ boolean callbackImmediately) {
+ mListener = listener;
+ try {
+ if ((listener == null) || !callbackImmediately) {
+ // do nothing
+ } else if (mErrorCode != SipErrorCode.NO_ERROR) {
+ listener.onError(this, mErrorCode, mErrorMessage);
+ } else if (mInCall) {
+ if (mHold) {
+ listener.onCallHeld(this);
+ } else {
+ listener.onCallEstablished(this);
+ }
+ } else {
+ int state = getState();
+ switch (state) {
+ case SipSession.State.READY_TO_CALL:
+ listener.onReadyToCall(this);
+ break;
+ case SipSession.State.INCOMING_CALL:
+ listener.onRinging(this, getPeerProfile());
+ break;
+ case SipSession.State.OUTGOING_CALL:
+ listener.onCalling(this);
+ break;
+ case SipSession.State.OUTGOING_CALL_RING_BACK:
+ listener.onRingingBack(this);
+ break;
+ }
+ }
+ } catch (Throwable t) {
+ loge("setListener()", t);
+ }
+ }
+
+ /**
+ * Checks if the call is established.
+ *
+ * @return true if the call is established
+ */
+ public boolean isInCall() {
+ synchronized (this) {
+ return mInCall;
+ }
+ }
+
+ /**
+ * Checks if the call is on hold.
+ *
+ * @return true if the call is on hold
+ */
+ public boolean isOnHold() {
+ synchronized (this) {
+ return mHold;
+ }
+ }
+
+ /**
+ * Closes this object. This object is not usable after being closed.
+ */
+ public void close() {
+ close(true);
+ }
+
+ private synchronized void close(boolean closeRtp) {
+ if (closeRtp) stopCall(RELEASE_SOCKET);
+
+ mInCall = false;
+ mHold = false;
+ mSessionId = System.currentTimeMillis();
+ mErrorCode = SipErrorCode.NO_ERROR;
+ mErrorMessage = null;
+
+ if (mSipSession != null) {
+ mSipSession.setListener(null);
+ mSipSession = null;
+ }
+ }
+
+ /**
+ * Gets the local SIP profile.
+ *
+ * @return the local SIP profile
+ */
+ public SipProfile getLocalProfile() {
+ synchronized (this) {
+ return mLocalProfile;
+ }
+ }
+
+ /**
+ * Gets the peer's SIP profile.
+ *
+ * @return the peer's SIP profile
+ */
+ public SipProfile getPeerProfile() {
+ synchronized (this) {
+ return (mSipSession == null) ? null : mSipSession.getPeerProfile();
+ }
+ }
+
+ /**
+ * Gets the state of the {@link SipSession} that carries this call.
+ * The value returned must be one of the states in {@link SipSession.State}.
+ *
+ * @return the session state
+ */
+ public int getState() {
+ synchronized (this) {
+ if (mSipSession == null) return SipSession.State.READY_TO_CALL;
+ return mSipSession.getState();
+ }
+ }
+
+
+ /**
+ * Gets the {@link SipSession} that carries this call.
+ *
+ * @return the session object that carries this call
+ * @hide
+ */
+ public SipSession getSipSession() {
+ synchronized (this) {
+ return mSipSession;
+ }
+ }
+
+ private synchronized void transferToNewSession() {
+ if (mTransferringSession == null) return;
+ SipSession origin = mSipSession;
+ mSipSession = mTransferringSession;
+ mTransferringSession = null;
+
+ // stop the replaced call.
+ if (mAudioStream != null) {
+ mAudioStream.join(null);
+ } else {
+ try {
+ mAudioStream = new AudioStream(InetAddress.getByName(
+ getLocalIp()));
+ } catch (Throwable t) {
+ loge("transferToNewSession():", t);
+ }
+ }
+ if (origin != null) origin.endCall();
+ startAudio();
+ }
+
+ private SipSession.Listener createListener() {
+ return new SipSession.Listener() {
+ @Override
+ public void onCalling(SipSession session) {
+ if (DBG) log("onCalling: session=" + session);
+ Listener listener = mListener;
+ if (listener != null) {
+ try {
+ listener.onCalling(SipAudioCall.this);
+ } catch (Throwable t) {
+ loge("onCalling():", t);
+ }
+ }
+ }
+
+ @Override
+ public void onRingingBack(SipSession session) {
+ if (DBG) log("onRingingBackk: " + session);
+ Listener listener = mListener;
+ if (listener != null) {
+ try {
+ listener.onRingingBack(SipAudioCall.this);
+ } catch (Throwable t) {
+ loge("onRingingBack():", t);
+ }
+ }
+ }
+
+ @Override
+ public void onRinging(SipSession session,
+ SipProfile peerProfile, String sessionDescription) {
+ // this callback is triggered only for reinvite.
+ synchronized (SipAudioCall.this) {
+ if ((mSipSession == null) || !mInCall
+ || !session.getCallId().equals(
+ mSipSession.getCallId())) {
+ // should not happen
+ session.endCall();
+ return;
+ }
+
+ // session changing request
+ try {
+ String answer = createAnswer(sessionDescription).encode();
+ mSipSession.answerCall(answer, SESSION_TIMEOUT);
+ } catch (Throwable e) {
+ loge("onRinging():", e);
+ session.endCall();
+ }
+ }
+ }
+
+ @Override
+ public void onCallEstablished(SipSession session,
+ String sessionDescription) {
+ mPeerSd = sessionDescription;
+ if (DBG) log("onCallEstablished(): " + mPeerSd);
+
+ // TODO: how to notify the UI that the remote party is changed
+ if ((mTransferringSession != null)
+ && (session == mTransferringSession)) {
+ transferToNewSession();
+ return;
+ }
+
+ Listener listener = mListener;
+ if (listener != null) {
+ try {
+ if (mHold) {
+ listener.onCallHeld(SipAudioCall.this);
+ } else {
+ listener.onCallEstablished(SipAudioCall.this);
+ }
+ } catch (Throwable t) {
+ loge("onCallEstablished(): ", t);
+ }
+ }
+ }
+
+ @Override
+ public void onCallEnded(SipSession session) {
+ if (DBG) log("onCallEnded: " + session + " mSipSession:" + mSipSession);
+ // reset the trasnferring session if it is the one.
+ if (session == mTransferringSession) {
+ mTransferringSession = null;
+ return;
+ }
+ // or ignore the event if the original session is being
+ // transferred to the new one.
+ if ((mTransferringSession != null) ||
+ (session != mSipSession)) return;
+
+ Listener listener = mListener;
+ if (listener != null) {
+ try {
+ listener.onCallEnded(SipAudioCall.this);
+ } catch (Throwable t) {
+ loge("onCallEnded(): ", t);
+ }
+ }
+ close();
+ }
+
+ @Override
+ public void onCallBusy(SipSession session) {
+ if (DBG) log("onCallBusy: " + session);
+ Listener listener = mListener;
+ if (listener != null) {
+ try {
+ listener.onCallBusy(SipAudioCall.this);
+ } catch (Throwable t) {
+ loge("onCallBusy(): ", t);
+ }
+ }
+ close(false);
+ }
+
+ @Override
+ public void onCallChangeFailed(SipSession session, int errorCode,
+ String message) {
+ if (DBG) log("onCallChangedFailed: " + message);
+ mErrorCode = errorCode;
+ mErrorMessage = message;
+ Listener listener = mListener;
+ if (listener != null) {
+ try {
+ listener.onError(SipAudioCall.this, mErrorCode,
+ message);
+ } catch (Throwable t) {
+ loge("onCallBusy():", t);
+ }
+ }
+ }
+
+ @Override
+ public void onError(SipSession session, int errorCode,
+ String message) {
+ SipAudioCall.this.onError(errorCode, message);
+ }
+
+ @Override
+ public void onRegistering(SipSession session) {
+ // irrelevant
+ }
+
+ @Override
+ public void onRegistrationTimeout(SipSession session) {
+ // irrelevant
+ }
+
+ @Override
+ public void onRegistrationFailed(SipSession session, int errorCode,
+ String message) {
+ // irrelevant
+ }
+
+ @Override
+ public void onRegistrationDone(SipSession session, int duration) {
+ // irrelevant
+ }
+
+ @Override
+ public void onCallTransferring(SipSession newSession,
+ String sessionDescription) {
+ if (DBG) log("onCallTransferring: mSipSession="
+ + mSipSession + " newSession=" + newSession);
+ mTransferringSession = newSession;
+ try {
+ if (sessionDescription == null) {
+ newSession.makeCall(newSession.getPeerProfile(),
+ createOffer().encode(), TRANSFER_TIMEOUT);
+ } else {
+ String answer = createAnswer(sessionDescription).encode();
+ newSession.answerCall(answer, SESSION_TIMEOUT);
+ }
+ } catch (Throwable e) {
+ loge("onCallTransferring()", e);
+ newSession.endCall();
+ }
+ }
+ };
+ }
+
+ private void onError(int errorCode, String message) {
+ if (DBG) log("onError: "
+ + SipErrorCode.toString(errorCode) + ": " + message);
+ mErrorCode = errorCode;
+ mErrorMessage = message;
+ Listener listener = mListener;
+ if (listener != null) {
+ try {
+ listener.onError(this, errorCode, message);
+ } catch (Throwable t) {
+ loge("onError():", t);
+ }
+ }
+ synchronized (this) {
+ if ((errorCode == SipErrorCode.DATA_CONNECTION_LOST)
+ || !isInCall()) {
+ close(true);
+ }
+ }
+ }
+
+ /**
+ * Attaches an incoming call to this call object.
+ *
+ * @param session the session that receives the incoming call
+ * @param sessionDescription the session description of the incoming call
+ * @throws SipException if the SIP service fails to attach this object to
+ * the session or VOIP API is not supported by the device
+ * @see SipManager#isVoipSupported
+ */
+ public void attachCall(SipSession session, String sessionDescription)
+ throws SipException {
+ if (!SipManager.isVoipSupported(mContext)) {
+ throw new SipException("VOIP API is not supported");
+ }
+
+ synchronized (this) {
+ mSipSession = session;
+ mPeerSd = sessionDescription;
+ if (DBG) log("attachCall(): " + mPeerSd);
+ try {
+ session.setListener(createListener());
+ } catch (Throwable e) {
+ loge("attachCall()", e);
+ throwSipException(e);
+ }
+ }
+ }
+
+ /**
+ * Initiates an audio call to the specified profile. The attempt will be
+ * timed out if the call is not established within {@code timeout} seconds
+ * and {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
+ * will be called.
+ *
+ * @param peerProfile the SIP profile to make the call to
+ * @param sipSession the {@link SipSession} for carrying out the call
+ * @param timeout the timeout value in seconds. Default value (defined by
+ * SIP protocol) is used if {@code timeout} is zero or negative.
+ * @see Listener#onError
+ * @throws SipException if the SIP service fails to create a session for the
+ * call or VOIP API is not supported by the device
+ * @see SipManager#isVoipSupported
+ */
+ public void makeCall(SipProfile peerProfile, SipSession sipSession,
+ int timeout) throws SipException {
+ if (DBG) log("makeCall: " + peerProfile + " session=" + sipSession + " timeout=" + timeout);
+ if (!SipManager.isVoipSupported(mContext)) {
+ throw new SipException("VOIP API is not supported");
+ }
+
+ synchronized (this) {
+ mSipSession = sipSession;
+ try {
+ mAudioStream = new AudioStream(InetAddress.getByName(
+ getLocalIp()));
+ sipSession.setListener(createListener());
+ sipSession.makeCall(peerProfile, createOffer().encode(),
+ timeout);
+ } catch (IOException e) {
+ loge("makeCall:", e);
+ throw new SipException("makeCall()", e);
+ }
+ }
+ }
+
+ /**
+ * Ends a call.
+ * @throws SipException if the SIP service fails to end the call
+ */
+ public void endCall() throws SipException {
+ if (DBG) log("endCall: mSipSession" + mSipSession);
+ synchronized (this) {
+ stopCall(RELEASE_SOCKET);
+ mInCall = false;
+
+ // perform the above local ops first and then network op
+ if (mSipSession != null) mSipSession.endCall();
+ }
+ }
+
+ /**
+ * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is
+ * called. The attempt will be timed out if the call is not established
+ * within {@code timeout} seconds and
+ * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
+ * will be called.
+ *
+ * @param timeout the timeout value in seconds. Default value (defined by
+ * SIP protocol) is used if {@code timeout} is zero or negative.
+ * @see Listener#onError
+ * @throws SipException if the SIP service fails to hold the call
+ */
+ public void holdCall(int timeout) throws SipException {
+ if (DBG) log("holdCall: mSipSession" + mSipSession + " timeout=" + timeout);
+ synchronized (this) {
+ if (mHold) return;
+ if (mSipSession == null) {
+ loge("holdCall:");
+ throw new SipException("Not in a call to hold call");
+ }
+ mSipSession.changeCall(createHoldOffer().encode(), timeout);
+ mHold = true;
+ setAudioGroupMode();
+ }
+ }
+
+ /**
+ * Answers a call. The attempt will be timed out if the call is not
+ * established within {@code timeout} seconds and
+ * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
+ * will be called.
+ *
+ * @param timeout the timeout value in seconds. Default value (defined by
+ * SIP protocol) is used if {@code timeout} is zero or negative.
+ * @see Listener#onError
+ * @throws SipException if the SIP service fails to answer the call
+ */
+ public void answerCall(int timeout) throws SipException {
+ if (DBG) log("answerCall: mSipSession" + mSipSession + " timeout=" + timeout);
+ synchronized (this) {
+ if (mSipSession == null) {
+ throw new SipException("No call to answer");
+ }
+ try {
+ mAudioStream = new AudioStream(InetAddress.getByName(
+ getLocalIp()));
+ mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout);
+ } catch (IOException e) {
+ loge("answerCall:", e);
+ throw new SipException("answerCall()", e);
+ }
+ }
+ }
+
+ /**
+ * Continues a call that's on hold. When succeeds,
+ * {@link Listener#onCallEstablished} is called. The attempt will be timed
+ * out if the call is not established within {@code timeout} seconds and
+ * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
+ * will be called.
+ *
+ * @param timeout the timeout value in seconds. Default value (defined by
+ * SIP protocol) is used if {@code timeout} is zero or negative.
+ * @see Listener#onError
+ * @throws SipException if the SIP service fails to unhold the call
+ */
+ public void continueCall(int timeout) throws SipException {
+ if (DBG) log("continueCall: mSipSession" + mSipSession + " timeout=" + timeout);
+ synchronized (this) {
+ if (!mHold) return;
+ mSipSession.changeCall(createContinueOffer().encode(), timeout);
+ mHold = false;
+ setAudioGroupMode();
+ }
+ }
+
+ private SimpleSessionDescription createOffer() {
+ SimpleSessionDescription offer =
+ new SimpleSessionDescription(mSessionId, getLocalIp());
+ AudioCodec[] codecs = AudioCodec.getCodecs();
+ Media media = offer.newMedia(
+ "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
+ for (AudioCodec codec : AudioCodec.getCodecs()) {
+ media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
+ }
+ media.setRtpPayload(127, "telephone-event/8000", "0-15");
+ if (DBG) log("createOffer: offer=" + offer);
+ return offer;
+ }
+
+ private SimpleSessionDescription createAnswer(String offerSd) {
+ if (TextUtils.isEmpty(offerSd)) return createOffer();
+ SimpleSessionDescription offer =
+ new SimpleSessionDescription(offerSd);
+ SimpleSessionDescription answer =
+ new SimpleSessionDescription(mSessionId, getLocalIp());
+ AudioCodec codec = null;
+ for (Media media : offer.getMedia()) {
+ if ((codec == null) && (media.getPort() > 0)
+ && "audio".equals(media.getType())
+ && "RTP/AVP".equals(media.getProtocol())) {
+ // Find the first audio codec we supported.
+ for (int type : media.getRtpPayloadTypes()) {
+ codec = AudioCodec.getCodec(type, media.getRtpmap(type),
+ media.getFmtp(type));
+ if (codec != null) {
+ break;
+ }
+ }
+ if (codec != null) {
+ Media reply = answer.newMedia(
+ "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
+ reply.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
+
+ // Check if DTMF is supported in the same media.
+ for (int type : media.getRtpPayloadTypes()) {
+ String rtpmap = media.getRtpmap(type);
+ if ((type != codec.type) && (rtpmap != null)
+ && rtpmap.startsWith("telephone-event")) {
+ reply.setRtpPayload(
+ type, rtpmap, media.getFmtp(type));
+ }
+ }
+
+ // Handle recvonly and sendonly.
+ if (media.getAttribute("recvonly") != null) {
+ answer.setAttribute("sendonly", "");
+ } else if(media.getAttribute("sendonly") != null) {
+ answer.setAttribute("recvonly", "");
+ } else if(offer.getAttribute("recvonly") != null) {
+ answer.setAttribute("sendonly", "");
+ } else if(offer.getAttribute("sendonly") != null) {
+ answer.setAttribute("recvonly", "");
+ }
+ continue;
+ }
+ }
+ // Reject the media.
+ Media reply = answer.newMedia(
+ media.getType(), 0, 1, media.getProtocol());
+ for (String format : media.getFormats()) {
+ reply.setFormat(format, null);
+ }
+ }
+ if (codec == null) {
+ loge("createAnswer: no suitable codes");
+ throw new IllegalStateException("Reject SDP: no suitable codecs");
+ }
+ if (DBG) log("createAnswer: answer=" + answer);
+ return answer;
+ }
+
+ private SimpleSessionDescription createHoldOffer() {
+ SimpleSessionDescription offer = createContinueOffer();
+ offer.setAttribute("sendonly", "");
+ if (DBG) log("createHoldOffer: offer=" + offer);
+ return offer;
+ }
+
+ private SimpleSessionDescription createContinueOffer() {
+ if (DBG) log("createContinueOffer");
+ SimpleSessionDescription offer =
+ new SimpleSessionDescription(mSessionId, getLocalIp());
+ Media media = offer.newMedia(
+ "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
+ AudioCodec codec = mAudioStream.getCodec();
+ media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
+ int dtmfType = mAudioStream.getDtmfType();
+ if (dtmfType != -1) {
+ media.setRtpPayload(dtmfType, "telephone-event/8000", "0-15");
+ }
+ return offer;
+ }
+
+ private void grabWifiHighPerfLock() {
+ if (mWifiHighPerfLock == null) {
+ if (DBG) log("grabWifiHighPerfLock:");
+ mWifiHighPerfLock = ((WifiManager)
+ mContext.getSystemService(Context.WIFI_SERVICE))
+ .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, LOG_TAG);
+ mWifiHighPerfLock.acquire();
+ }
+ }
+
+ private void releaseWifiHighPerfLock() {
+ if (mWifiHighPerfLock != null) {
+ if (DBG) log("releaseWifiHighPerfLock:");
+ mWifiHighPerfLock.release();
+ mWifiHighPerfLock = null;
+ }
+ }
+
+ private boolean isWifiOn() {
+ return (mWm.getConnectionInfo().getBSSID() == null) ? false : true;
+ }
+
+ /** Toggles mute. */
+ public void toggleMute() {
+ synchronized (this) {
+ mMuted = !mMuted;
+ setAudioGroupMode();
+ }
+ }
+
+ /**
+ * Checks if the call is muted.
+ *
+ * @return true if the call is muted
+ */
+ public boolean isMuted() {
+ synchronized (this) {
+ return mMuted;
+ }
+ }
+
+ /**
+ * Puts the device to speaker mode.
+ * <p class="note"><strong>Note:</strong> Requires the
+ * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.</p>
+ *
+ * @param speakerMode set true to enable speaker mode; false to disable
+ */
+ public void setSpeakerMode(boolean speakerMode) {
+ synchronized (this) {
+ ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
+ .setSpeakerphoneOn(speakerMode);
+ setAudioGroupMode();
+ }
+ }
+
+ private boolean isSpeakerOn() {
+ return ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
+ .isSpeakerphoneOn();
+ }
+
+ /**
+ * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>,
+ * event 0--9 maps to decimal
+ * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event
+ * flash to 16. Currently, event flash is not supported.
+ *
+ * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid
+ * inputs.
+ */
+ public void sendDtmf(int code) {
+ sendDtmf(code, null);
+ }
+
+ /**
+ * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>,
+ * event 0--9 maps to decimal
+ * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event
+ * flash to 16. Currently, event flash is not supported.
+ *
+ * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid
+ * inputs.
+ * @param result the result message to send when done
+ */
+ public void sendDtmf(int code, Message result) {
+ synchronized (this) {
+ AudioGroup audioGroup = getAudioGroup();
+ if ((audioGroup != null) && (mSipSession != null)
+ && (SipSession.State.IN_CALL == getState())) {
+ if (DBG) log("sendDtmf: code=" + code + " result=" + result);
+ audioGroup.sendDtmf(code);
+ }
+ if (result != null) result.sendToTarget();
+ }
+ }
+
+ /**
+ * Gets the {@link AudioStream} object used in this call. The object
+ * represents the RTP stream that carries the audio data to and from the
+ * peer. The object may not be created before the call is established. And
+ * it is undefined after the call ends or the {@link #close} method is
+ * called.
+ *
+ * @return the {@link AudioStream} object or null if the RTP stream has not
+ * yet been set up
+ * @hide
+ */
+ public AudioStream getAudioStream() {
+ synchronized (this) {
+ return mAudioStream;
+ }
+ }
+
+ /**
+ * Gets the {@link AudioGroup} object which the {@link AudioStream} object
+ * joins. The group object may not exist before the call is established.
+ * Also, the {@code AudioStream} may change its group during a call (e.g.,
+ * after the call is held/un-held). Finally, the {@code AudioGroup} object
+ * returned by this method is undefined after the call ends or the
+ * {@link #close} method is called. If a group object is set by
+ * {@link #setAudioGroup(AudioGroup)}, then this method returns that object.
+ *
+ * @return the {@link AudioGroup} object or null if the RTP stream has not
+ * yet been set up
+ * @see #getAudioStream
+ * @hide
+ */
+ public AudioGroup getAudioGroup() {
+ synchronized (this) {
+ if (mAudioGroup != null) return mAudioGroup;
+ return ((mAudioStream == null) ? null : mAudioStream.getGroup());
+ }
+ }
+
+ /**
+ * Sets the {@link AudioGroup} object which the {@link AudioStream} object
+ * joins. If {@code audioGroup} is null, then the {@code AudioGroup} object
+ * will be dynamically created when needed. Note that the mode of the
+ * {@code AudioGroup} is not changed according to the audio settings (i.e.,
+ * hold, mute, speaker phone) of this object. This is mainly used to merge
+ * multiple {@code SipAudioCall} objects to form a conference call. The
+ * settings of the first object (that merges others) override others'.
+ *
+ * @see #getAudioStream
+ * @hide
+ */
+ public void setAudioGroup(AudioGroup group) {
+ synchronized (this) {
+ if (DBG) log("setAudioGroup: group=" + group);
+ if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) {
+ mAudioStream.join(group);
+ }
+ mAudioGroup = group;
+ }
+ }
+
+ /**
+ * Starts the audio for the established call. This method should be called
+ * after {@link Listener#onCallEstablished} is called.
+ * <p class="note"><strong>Note:</strong> Requires the
+ * {@link android.Manifest.permission#RECORD_AUDIO},
+ * {@link android.Manifest.permission#ACCESS_WIFI_STATE} and
+ * {@link android.Manifest.permission#WAKE_LOCK} permissions.</p>
+ */
+ public void startAudio() {
+ try {
+ startAudioInternal();
+ } catch (UnknownHostException e) {
+ onError(SipErrorCode.PEER_NOT_REACHABLE, e.getMessage());
+ } catch (Throwable e) {
+ onError(SipErrorCode.CLIENT_ERROR, e.getMessage());
+ }
+ }
+
+ private synchronized void startAudioInternal() throws UnknownHostException {
+ if (DBG) loge("startAudioInternal: mPeerSd=" + mPeerSd);
+ if (mPeerSd == null) {
+ throw new IllegalStateException("mPeerSd = null");
+ }
+
+ stopCall(DONT_RELEASE_SOCKET);
+ mInCall = true;
+
+ // Run exact the same logic in createAnswer() to setup mAudioStream.
+ SimpleSessionDescription offer =
+ new SimpleSessionDescription(mPeerSd);
+ AudioStream stream = mAudioStream;
+ AudioCodec codec = null;
+ for (Media media : offer.getMedia()) {
+ if ((codec == null) && (media.getPort() > 0)
+ && "audio".equals(media.getType())
+ && "RTP/AVP".equals(media.getProtocol())) {
+ // Find the first audio codec we supported.
+ for (int type : media.getRtpPayloadTypes()) {
+ codec = AudioCodec.getCodec(
+ type, media.getRtpmap(type), media.getFmtp(type));
+ if (codec != null) {
+ break;
+ }
+ }
+
+ if (codec != null) {
+ // Associate with the remote host.
+ String address = media.getAddress();
+ if (address == null) {
+ address = offer.getAddress();
+ }
+ stream.associate(InetAddress.getByName(address),
+ media.getPort());
+
+ stream.setDtmfType(-1);
+ stream.setCodec(codec);
+ // Check if DTMF is supported in the same media.
+ for (int type : media.getRtpPayloadTypes()) {
+ String rtpmap = media.getRtpmap(type);
+ if ((type != codec.type) && (rtpmap != null)
+ && rtpmap.startsWith("telephone-event")) {
+ stream.setDtmfType(type);
+ }
+ }
+
+ // Handle recvonly and sendonly.
+ if (mHold) {
+ stream.setMode(RtpStream.MODE_NORMAL);
+ } else if (media.getAttribute("recvonly") != null) {
+ stream.setMode(RtpStream.MODE_SEND_ONLY);
+ } else if(media.getAttribute("sendonly") != null) {
+ stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
+ } else if(offer.getAttribute("recvonly") != null) {
+ stream.setMode(RtpStream.MODE_SEND_ONLY);
+ } else if(offer.getAttribute("sendonly") != null) {
+ stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
+ } else {
+ stream.setMode(RtpStream.MODE_NORMAL);
+ }
+ break;
+ }
+ }
+ }
+ if (codec == null) {
+ throw new IllegalStateException("Reject SDP: no suitable codecs");
+ }
+
+ if (isWifiOn()) grabWifiHighPerfLock();
+
+ // AudioGroup logic:
+ AudioGroup audioGroup = getAudioGroup();
+ if (mHold) {
+ // don't create an AudioGroup here; doing so will fail if
+ // there's another AudioGroup out there that's active
+ } else {
+ if (audioGroup == null) audioGroup = new AudioGroup();
+ stream.join(audioGroup);
+ }
+ setAudioGroupMode();
+ }
+
+ // set audio group mode based on current audio configuration
+ private void setAudioGroupMode() {
+ AudioGroup audioGroup = getAudioGroup();
+ if (DBG) log("setAudioGroupMode: audioGroup=" + audioGroup);
+ if (audioGroup != null) {
+ if (mHold) {
+ audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
+ } else if (mMuted) {
+ audioGroup.setMode(AudioGroup.MODE_MUTED);
+ } else if (isSpeakerOn()) {
+ audioGroup.setMode(AudioGroup.MODE_ECHO_SUPPRESSION);
+ } else {
+ audioGroup.setMode(AudioGroup.MODE_NORMAL);
+ }
+ }
+ }
+
+ private void stopCall(boolean releaseSocket) {
+ if (DBG) log("stopCall: releaseSocket=" + releaseSocket);
+ releaseWifiHighPerfLock();
+ if (mAudioStream != null) {
+ mAudioStream.join(null);
+
+ if (releaseSocket) {
+ mAudioStream.release();
+ mAudioStream = null;
+ }
+ }
+ }
+
+ private String getLocalIp() {
+ return mSipSession.getLocalIp();
+ }
+
+ private void throwSipException(Throwable throwable) throws SipException {
+ if (throwable instanceof SipException) {
+ throw (SipException) throwable;
+ } else {
+ throw new SipException("", throwable);
+ }
+ }
+
+ private void log(String s) {
+ Rlog.d(LOG_TAG, s);
+ }
+
+ private void loge(String s) {
+ Rlog.e(LOG_TAG, s);
+ }
+
+ private void loge(String s, Throwable t) {
+ Rlog.e(LOG_TAG, s, t);
+ }
+}
diff --git a/android/net/sip/SipErrorCode.java b/android/net/sip/SipErrorCode.java
new file mode 100644
index 0000000..509728f
--- /dev/null
+++ b/android/net/sip/SipErrorCode.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2010 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.sip;
+
+/**
+ * Defines error codes returned during SIP actions. For example, during
+ * {@link SipRegistrationListener#onRegistrationFailed onRegistrationFailed()},
+ * {@link SipSession.Listener#onError onError()},
+ * {@link SipSession.Listener#onCallChangeFailed onCallChangeFailed()} and
+ * {@link SipSession.Listener#onRegistrationFailed onRegistrationFailed()}.
+ */
+public class SipErrorCode {
+ /** Not an error. */
+ public static final int NO_ERROR = 0;
+
+ /** When some socket error occurs. */
+ public static final int SOCKET_ERROR = -1;
+
+ /** When server responds with an error. */
+ public static final int SERVER_ERROR = -2;
+
+ /** When transaction is terminated unexpectedly. */
+ public static final int TRANSACTION_TERMINTED = -3;
+
+ /** When some error occurs on the device, possibly due to a bug. */
+ public static final int CLIENT_ERROR = -4;
+
+ /** When the transaction gets timed out. */
+ public static final int TIME_OUT = -5;
+
+ /** When the remote URI is not valid. */
+ public static final int INVALID_REMOTE_URI = -6;
+
+ /** When the peer is not reachable. */
+ public static final int PEER_NOT_REACHABLE = -7;
+
+ /** When invalid credentials are provided. */
+ public static final int INVALID_CREDENTIALS = -8;
+
+ /** The client is in a transaction and cannot initiate a new one. */
+ public static final int IN_PROGRESS = -9;
+
+ /** When data connection is lost. */
+ public static final int DATA_CONNECTION_LOST = -10;
+
+ /** Cross-domain authentication required. */
+ public static final int CROSS_DOMAIN_AUTHENTICATION = -11;
+
+ /** When the server is not reachable. */
+ public static final int SERVER_UNREACHABLE = -12;
+
+ public static String toString(int errorCode) {
+ switch (errorCode) {
+ case NO_ERROR:
+ return "NO_ERROR";
+ case SOCKET_ERROR:
+ return "SOCKET_ERROR";
+ case SERVER_ERROR:
+ return "SERVER_ERROR";
+ case TRANSACTION_TERMINTED:
+ return "TRANSACTION_TERMINTED";
+ case CLIENT_ERROR:
+ return "CLIENT_ERROR";
+ case TIME_OUT:
+ return "TIME_OUT";
+ case INVALID_REMOTE_URI:
+ return "INVALID_REMOTE_URI";
+ case PEER_NOT_REACHABLE:
+ return "PEER_NOT_REACHABLE";
+ case INVALID_CREDENTIALS:
+ return "INVALID_CREDENTIALS";
+ case IN_PROGRESS:
+ return "IN_PROGRESS";
+ case DATA_CONNECTION_LOST:
+ return "DATA_CONNECTION_LOST";
+ case CROSS_DOMAIN_AUTHENTICATION:
+ return "CROSS_DOMAIN_AUTHENTICATION";
+ case SERVER_UNREACHABLE:
+ return "SERVER_UNREACHABLE";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ private SipErrorCode() {
+ }
+}
diff --git a/android/net/sip/SipException.java b/android/net/sip/SipException.java
new file mode 100644
index 0000000..0339395
--- /dev/null
+++ b/android/net/sip/SipException.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2010 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.sip;
+
+/**
+ * Indicates a general SIP-related exception.
+ */
+public class SipException extends Exception {
+ public SipException() {
+ }
+
+ public SipException(String message) {
+ super(message);
+ }
+
+ public SipException(String message, Throwable cause) {
+ // we want to eliminate the dependency on javax.sip.SipException
+ super(message, ((cause instanceof javax.sip.SipException)
+ && (cause.getCause() != null))
+ ? cause.getCause()
+ : cause);
+ }
+}
diff --git a/android/net/sip/SipManager.java b/android/net/sip/SipManager.java
new file mode 100644
index 0000000..0cb1feb
--- /dev/null
+++ b/android/net/sip/SipManager.java
@@ -0,0 +1,661 @@
+/*
+ * Copyright (C) 2010 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.sip;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.telephony.Rlog;
+
+import java.text.ParseException;
+
+/**
+ * Provides APIs for SIP tasks, such as initiating SIP connections, and provides access to related
+ * SIP services. This class is the starting point for any SIP actions. You can acquire an instance
+ * of it with {@link #newInstance newInstance()}.</p>
+ * <p>The APIs in this class allows you to:</p>
+ * <ul>
+ * <li>Create a {@link SipSession} to get ready for making calls or listen for incoming calls. See
+ * {@link #createSipSession createSipSession()} and {@link #getSessionFor getSessionFor()}.</li>
+ * <li>Initiate and receive generic SIP calls or audio-only SIP calls. Generic SIP calls may
+ * be video, audio, or other, and are initiated with {@link #open open()}. Audio-only SIP calls
+ * should be handled with a {@link SipAudioCall}, which you can acquire with {@link
+ * #makeAudioCall makeAudioCall()} and {@link #takeAudioCall takeAudioCall()}.</li>
+ * <li>Register and unregister with a SIP service provider, with
+ * {@link #register register()} and {@link #unregister unregister()}.</li>
+ * <li>Verify session connectivity, with {@link #isOpened isOpened()} and
+ * {@link #isRegistered isRegistered()}.</li>
+ * </ul>
+ * <p class="note"><strong>Note:</strong> Not all Android-powered devices support VOIP calls using
+ * SIP. You should always call {@link android.net.sip.SipManager#isVoipSupported
+ * isVoipSupported()} to verify that the device supports VOIP calling and {@link
+ * android.net.sip.SipManager#isApiSupported isApiSupported()} to verify that the device supports
+ * the SIP APIs. Your application must also request the {@link
+ * android.Manifest.permission#INTERNET} and {@link android.Manifest.permission#USE_SIP}
+ * permissions.</p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using SIP, read the
+ * <a href="{@docRoot}guide/topics/network/sip.html">Session Initiation Protocol</a>
+ * developer guide.</p>
+ * </div>
+ */
+public class SipManager {
+ /**
+ * The result code to be sent back with the incoming call
+ * {@link PendingIntent}.
+ * @see #open(SipProfile, PendingIntent, SipRegistrationListener)
+ */
+ public static final int INCOMING_CALL_RESULT_CODE = 101;
+
+ /**
+ * Key to retrieve the call ID from an incoming call intent.
+ * @see #open(SipProfile, PendingIntent, SipRegistrationListener)
+ */
+ public static final String EXTRA_CALL_ID = "android:sipCallID";
+
+ /**
+ * Key to retrieve the offered session description from an incoming call
+ * intent.
+ * @see #open(SipProfile, PendingIntent, SipRegistrationListener)
+ */
+ public static final String EXTRA_OFFER_SD = "android:sipOfferSD";
+
+ /**
+ * Action to broadcast when SipService is up.
+ * Internal use only.
+ * @hide
+ */
+ public static final String ACTION_SIP_SERVICE_UP =
+ "android.net.sip.SIP_SERVICE_UP";
+ /**
+ * Action string for the incoming call intent for the Phone app.
+ * Internal use only.
+ * @hide
+ */
+ public static final String ACTION_SIP_INCOMING_CALL =
+ "com.android.phone.SIP_INCOMING_CALL";
+ /**
+ * Action string for the add-phone intent.
+ * Internal use only.
+ * @hide
+ */
+ public static final String ACTION_SIP_ADD_PHONE =
+ "com.android.phone.SIP_ADD_PHONE";
+ /**
+ * Action string for the remove-phone intent.
+ * Internal use only.
+ * @hide
+ */
+ public static final String ACTION_SIP_REMOVE_PHONE =
+ "com.android.phone.SIP_REMOVE_PHONE";
+
+ /**
+ * Action string for the SIP call option configuration changed intent.
+ * This is used to communicate change to the SIP call option, triggering re-registration of
+ * the SIP phone accounts.
+ * Internal use only.
+ * @hide
+ */
+ public static final String ACTION_SIP_CALL_OPTION_CHANGED =
+ "com.android.phone.SIP_CALL_OPTION_CHANGED";
+
+ /**
+ * Part of the ACTION_SIP_ADD_PHONE and ACTION_SIP_REMOVE_PHONE intents.
+ * Internal use only.
+ * @hide
+ */
+ public static final String EXTRA_LOCAL_URI = "android:localSipUri";
+
+ private static final String TAG = "SipManager";
+
+ private ISipService mSipService;
+ private Context mContext;
+
+ /**
+ * Creates a manager instance. Returns null if SIP API is not supported.
+ *
+ * @param context application context for creating the manager object
+ * @return the manager instance or null if SIP API is not supported
+ */
+ public static SipManager newInstance(Context context) {
+ return (isApiSupported(context) ? new SipManager(context) : null);
+ }
+
+ /**
+ * Returns true if the SIP API is supported by the system.
+ */
+ public static boolean isApiSupported(Context context) {
+ return context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_SIP);
+ }
+
+ /**
+ * Returns true if the system supports SIP-based VOIP API.
+ */
+ public static boolean isVoipSupported(Context context) {
+ return context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_SIP_VOIP) && isApiSupported(context);
+ }
+
+ /**
+ * Returns true if SIP is only available on WIFI.
+ */
+ public static boolean isSipWifiOnly(Context context) {
+ return context.getResources().getBoolean(
+ com.android.internal.R.bool.config_sip_wifi_only);
+ }
+
+ private SipManager(Context context) {
+ mContext = context;
+ createSipService();
+ }
+
+ private void createSipService() {
+ if (mSipService == null) {
+ IBinder b = ServiceManager.getService(Context.SIP_SERVICE);
+ mSipService = ISipService.Stub.asInterface(b);
+ }
+ }
+
+ private void checkSipServiceConnection() throws SipException {
+ createSipService();
+ if (mSipService == null) {
+ throw new SipException("SipService is dead and is restarting...", new Exception());
+ }
+ }
+
+ /**
+ * Opens the profile for making generic SIP calls. The caller may make subsequent calls
+ * through {@link #makeAudioCall}. If one also wants to receive calls on the
+ * profile, use
+ * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)}
+ * instead.
+ *
+ * @param localProfile the SIP profile to make calls from
+ * @throws SipException if the profile contains incorrect settings or
+ * calling the SIP service results in an error
+ */
+ public void open(SipProfile localProfile) throws SipException {
+ try {
+ checkSipServiceConnection();
+ mSipService.open(localProfile, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw new SipException("open()", e);
+ }
+ }
+
+ /**
+ * Opens the profile for making calls and/or receiving generic SIP calls. The caller may
+ * make subsequent calls through {@link #makeAudioCall}. If the
+ * auto-registration option is enabled in the profile, the SIP service
+ * will register the profile to the corresponding SIP provider periodically
+ * in order to receive calls from the provider. When the SIP service
+ * receives a new call, it will send out an intent with the provided action
+ * string. The intent contains a call ID extra and an offer session
+ * description string extra. Use {@link #getCallId} and
+ * {@link #getOfferSessionDescription} to retrieve those extras.
+ *
+ * @param localProfile the SIP profile to receive incoming calls for
+ * @param incomingCallPendingIntent When an incoming call is received, the
+ * SIP service will call
+ * {@link PendingIntent#send(Context, int, Intent)} to send back the
+ * intent to the caller with {@link #INCOMING_CALL_RESULT_CODE} as the
+ * result code and the intent to fill in the call ID and session
+ * description information. It cannot be null.
+ * @param listener to listen to registration events; can be null
+ * @see #getCallId
+ * @see #getOfferSessionDescription
+ * @see #takeAudioCall
+ * @throws NullPointerException if {@code incomingCallPendingIntent} is null
+ * @throws SipException if the profile contains incorrect settings or
+ * calling the SIP service results in an error
+ * @see #isIncomingCallIntent
+ * @see #getCallId
+ * @see #getOfferSessionDescription
+ */
+ public void open(SipProfile localProfile,
+ PendingIntent incomingCallPendingIntent,
+ SipRegistrationListener listener) throws SipException {
+ if (incomingCallPendingIntent == null) {
+ throw new NullPointerException(
+ "incomingCallPendingIntent cannot be null");
+ }
+ try {
+ checkSipServiceConnection();
+ mSipService.open3(localProfile, incomingCallPendingIntent,
+ createRelay(listener, localProfile.getUriString()),
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw new SipException("open()", e);
+ }
+ }
+
+ /**
+ * Sets the listener to listen to registration events. No effect if the
+ * profile has not been opened to receive calls (see
+ * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)}).
+ *
+ * @param localProfileUri the URI of the profile
+ * @param listener to listen to registration events; can be null
+ * @throws SipException if calling the SIP service results in an error
+ */
+ public void setRegistrationListener(String localProfileUri,
+ SipRegistrationListener listener) throws SipException {
+ try {
+ checkSipServiceConnection();
+ mSipService.setRegistrationListener(
+ localProfileUri, createRelay(listener, localProfileUri),
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw new SipException("setRegistrationListener()", e);
+ }
+ }
+
+ /**
+ * Closes the specified profile to not make/receive calls. All the resources
+ * that were allocated to the profile are also released.
+ *
+ * @param localProfileUri the URI of the profile to close
+ * @throws SipException if calling the SIP service results in an error
+ */
+ public void close(String localProfileUri) throws SipException {
+ try {
+ checkSipServiceConnection();
+ mSipService.close(localProfileUri, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw new SipException("close()", e);
+ }
+ }
+
+ /**
+ * Checks if the specified profile is opened in the SIP service for
+ * making and/or receiving calls.
+ *
+ * @param localProfileUri the URI of the profile in question
+ * @return true if the profile is enabled to receive calls
+ * @throws SipException if calling the SIP service results in an error
+ */
+ public boolean isOpened(String localProfileUri) throws SipException {
+ try {
+ checkSipServiceConnection();
+ return mSipService.isOpened(localProfileUri, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw new SipException("isOpened()", e);
+ }
+ }
+
+ /**
+ * Checks if the SIP service has successfully registered the profile to the
+ * SIP provider (specified in the profile) for receiving calls. Returning
+ * true from this method also implies the profile is opened
+ * ({@link #isOpened}).
+ *
+ * @param localProfileUri the URI of the profile in question
+ * @return true if the profile is registered to the SIP provider; false if
+ * the profile has not been opened in the SIP service or the SIP
+ * service has not yet successfully registered the profile to the SIP
+ * provider
+ * @throws SipException if calling the SIP service results in an error
+ */
+ public boolean isRegistered(String localProfileUri) throws SipException {
+ try {
+ checkSipServiceConnection();
+ return mSipService.isRegistered(localProfileUri, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw new SipException("isRegistered()", e);
+ }
+ }
+
+ /**
+ * Creates a {@link SipAudioCall} to make a call. The attempt will be timed
+ * out if the call is not established within {@code timeout} seconds and
+ * {@link SipAudioCall.Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
+ * will be called.
+ *
+ * @param localProfile the SIP profile to make the call from
+ * @param peerProfile the SIP profile to make the call to
+ * @param listener to listen to the call events from {@link SipAudioCall};
+ * can be null
+ * @param timeout the timeout value in seconds. Default value (defined by
+ * SIP protocol) is used if {@code timeout} is zero or negative.
+ * @return a {@link SipAudioCall} object
+ * @throws SipException if calling the SIP service results in an error or
+ * VOIP API is not supported by the device
+ * @see SipAudioCall.Listener#onError
+ * @see #isVoipSupported
+ */
+ public SipAudioCall makeAudioCall(SipProfile localProfile,
+ SipProfile peerProfile, SipAudioCall.Listener listener, int timeout)
+ throws SipException {
+ if (!isVoipSupported(mContext)) {
+ throw new SipException("VOIP API is not supported");
+ }
+ SipAudioCall call = new SipAudioCall(mContext, localProfile);
+ call.setListener(listener);
+ SipSession s = createSipSession(localProfile, null);
+ call.makeCall(peerProfile, s, timeout);
+ return call;
+ }
+
+ /**
+ * Creates a {@link SipAudioCall} to make an audio call. The attempt will be
+ * timed out if the call is not established within {@code timeout} seconds
+ * and
+ * {@link SipAudioCall.Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
+ * will be called.
+ *
+ * @param localProfileUri URI of the SIP profile to make the call from
+ * @param peerProfileUri URI of the SIP profile to make the call to
+ * @param listener to listen to the call events from {@link SipAudioCall};
+ * can be null
+ * @param timeout the timeout value in seconds. Default value (defined by
+ * SIP protocol) is used if {@code timeout} is zero or negative.
+ * @return a {@link SipAudioCall} object
+ * @throws SipException if calling the SIP service results in an error or
+ * VOIP API is not supported by the device
+ * @see SipAudioCall.Listener#onError
+ * @see #isVoipSupported
+ */
+ public SipAudioCall makeAudioCall(String localProfileUri,
+ String peerProfileUri, SipAudioCall.Listener listener, int timeout)
+ throws SipException {
+ if (!isVoipSupported(mContext)) {
+ throw new SipException("VOIP API is not supported");
+ }
+ try {
+ return makeAudioCall(
+ new SipProfile.Builder(localProfileUri).build(),
+ new SipProfile.Builder(peerProfileUri).build(), listener,
+ timeout);
+ } catch (ParseException e) {
+ throw new SipException("build SipProfile", e);
+ }
+ }
+
+ /**
+ * Creates a {@link SipAudioCall} to take an incoming call. Before the call
+ * is returned, the listener will receive a
+ * {@link SipAudioCall.Listener#onRinging}
+ * callback.
+ *
+ * @param incomingCallIntent the incoming call broadcast intent
+ * @param listener to listen to the call events from {@link SipAudioCall};
+ * can be null
+ * @return a {@link SipAudioCall} object
+ * @throws SipException if calling the SIP service results in an error
+ */
+ public SipAudioCall takeAudioCall(Intent incomingCallIntent,
+ SipAudioCall.Listener listener) throws SipException {
+ if (incomingCallIntent == null) {
+ throw new SipException("Cannot retrieve session with null intent");
+ }
+
+ String callId = getCallId(incomingCallIntent);
+ if (callId == null) {
+ throw new SipException("Call ID missing in incoming call intent");
+ }
+
+ String offerSd = getOfferSessionDescription(incomingCallIntent);
+ if (offerSd == null) {
+ throw new SipException("Session description missing in incoming "
+ + "call intent");
+ }
+
+ try {
+ checkSipServiceConnection();
+ ISipSession session = mSipService.getPendingSession(callId,
+ mContext.getOpPackageName());
+ if (session == null) {
+ throw new SipException("No pending session for the call");
+ }
+ SipAudioCall call = new SipAudioCall(
+ mContext, session.getLocalProfile());
+ call.attachCall(new SipSession(session), offerSd);
+ call.setListener(listener);
+ return call;
+ } catch (Throwable t) {
+ throw new SipException("takeAudioCall()", t);
+ }
+ }
+
+ /**
+ * Checks if the intent is an incoming call broadcast intent.
+ *
+ * @param intent the intent in question
+ * @return true if the intent is an incoming call broadcast intent
+ */
+ public static boolean isIncomingCallIntent(Intent intent) {
+ if (intent == null) return false;
+ String callId = getCallId(intent);
+ String offerSd = getOfferSessionDescription(intent);
+ return ((callId != null) && (offerSd != null));
+ }
+
+ /**
+ * Gets the call ID from the specified incoming call broadcast intent.
+ *
+ * @param incomingCallIntent the incoming call broadcast intent
+ * @return the call ID or null if the intent does not contain it
+ */
+ public static String getCallId(Intent incomingCallIntent) {
+ return incomingCallIntent.getStringExtra(EXTRA_CALL_ID);
+ }
+
+ /**
+ * Gets the offer session description from the specified incoming call
+ * broadcast intent.
+ *
+ * @param incomingCallIntent the incoming call broadcast intent
+ * @return the offer session description or null if the intent does not
+ * have it
+ */
+ public static String getOfferSessionDescription(Intent incomingCallIntent) {
+ return incomingCallIntent.getStringExtra(EXTRA_OFFER_SD);
+ }
+
+ /**
+ * Creates an incoming call broadcast intent.
+ *
+ * @param callId the call ID of the incoming call
+ * @param sessionDescription the session description of the incoming call
+ * @return the incoming call intent
+ * @hide
+ */
+ public static Intent createIncomingCallBroadcast(String callId,
+ String sessionDescription) {
+ Intent intent = new Intent();
+ intent.putExtra(EXTRA_CALL_ID, callId);
+ intent.putExtra(EXTRA_OFFER_SD, sessionDescription);
+ return intent;
+ }
+
+ /**
+ * Manually registers the profile to the corresponding SIP provider for
+ * receiving calls.
+ * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)} is
+ * still needed to be called at least once in order for the SIP service to
+ * notify the caller with the {@link android.app.PendingIntent} when an incoming call is
+ * received.
+ *
+ * @param localProfile the SIP profile to register with
+ * @param expiryTime registration expiration time (in seconds)
+ * @param listener to listen to the registration events
+ * @throws SipException if calling the SIP service results in an error
+ */
+ public void register(SipProfile localProfile, int expiryTime,
+ SipRegistrationListener listener) throws SipException {
+ try {
+ checkSipServiceConnection();
+ ISipSession session = mSipService.createSession(localProfile,
+ createRelay(listener, localProfile.getUriString()),
+ mContext.getOpPackageName());
+ if (session == null) {
+ throw new SipException(
+ "SipService.createSession() returns null");
+ }
+ session.register(expiryTime);
+ } catch (RemoteException e) {
+ throw new SipException("register()", e);
+ }
+ }
+
+ /**
+ * Manually unregisters the profile from the corresponding SIP provider for
+ * stop receiving further calls. This may interference with the auto
+ * registration process in the SIP service if the auto-registration option
+ * in the profile is enabled.
+ *
+ * @param localProfile the SIP profile to register with
+ * @param listener to listen to the registration events
+ * @throws SipException if calling the SIP service results in an error
+ */
+ public void unregister(SipProfile localProfile,
+ SipRegistrationListener listener) throws SipException {
+ try {
+ checkSipServiceConnection();
+ ISipSession session = mSipService.createSession(localProfile,
+ createRelay(listener, localProfile.getUriString()),
+ mContext.getOpPackageName());
+ if (session == null) {
+ throw new SipException(
+ "SipService.createSession() returns null");
+ }
+ session.unregister();
+ } catch (RemoteException e) {
+ throw new SipException("unregister()", e);
+ }
+ }
+
+ /**
+ * Gets the {@link SipSession} that handles the incoming call. For audio
+ * calls, consider to use {@link SipAudioCall} to handle the incoming call.
+ * See {@link #takeAudioCall}. Note that the method may be called only once
+ * for the same intent. For subsequent calls on the same intent, the method
+ * returns null.
+ *
+ * @param incomingCallIntent the incoming call broadcast intent
+ * @return the session object that handles the incoming call
+ */
+ public SipSession getSessionFor(Intent incomingCallIntent)
+ throws SipException {
+ try {
+ checkSipServiceConnection();
+ String callId = getCallId(incomingCallIntent);
+ ISipSession s = mSipService.getPendingSession(callId,
+ mContext.getOpPackageName());
+ return ((s == null) ? null : new SipSession(s));
+ } catch (RemoteException e) {
+ throw new SipException("getSessionFor()", e);
+ }
+ }
+
+ private static ISipSessionListener createRelay(
+ SipRegistrationListener listener, String uri) {
+ return ((listener == null) ? null : new ListenerRelay(listener, uri));
+ }
+
+ /**
+ * Creates a {@link SipSession} with the specified profile. Use other
+ * methods, if applicable, instead of interacting with {@link SipSession}
+ * directly.
+ *
+ * @param localProfile the SIP profile the session is associated with
+ * @param listener to listen to SIP session events
+ */
+ public SipSession createSipSession(SipProfile localProfile,
+ SipSession.Listener listener) throws SipException {
+ try {
+ checkSipServiceConnection();
+ ISipSession s = mSipService.createSession(localProfile, null,
+ mContext.getOpPackageName());
+ if (s == null) {
+ throw new SipException(
+ "Failed to create SipSession; network unavailable?");
+ }
+ return new SipSession(s, listener);
+ } catch (RemoteException e) {
+ throw new SipException("createSipSession()", e);
+ }
+ }
+
+ /**
+ * Gets the list of profiles hosted by the SIP service. The user information
+ * (username, password and display name) are crossed out.
+ * @hide
+ */
+ public SipProfile[] getListOfProfiles() throws SipException {
+ try {
+ checkSipServiceConnection();
+ return mSipService.getListOfProfiles(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ return new SipProfile[0];
+ }
+ }
+
+ private static class ListenerRelay extends SipSessionAdapter {
+ private SipRegistrationListener mListener;
+ private String mUri;
+
+ // listener must not be null
+ public ListenerRelay(SipRegistrationListener listener, String uri) {
+ mListener = listener;
+ mUri = uri;
+ }
+
+ private String getUri(ISipSession session) {
+ try {
+ return ((session == null)
+ ? mUri
+ : session.getLocalProfile().getUriString());
+ } catch (Throwable e) {
+ // SipService died? SIP stack died?
+ Rlog.e(TAG, "getUri(): ", e);
+ return null;
+ }
+ }
+
+ @Override
+ public void onRegistering(ISipSession session) {
+ mListener.onRegistering(getUri(session));
+ }
+
+ @Override
+ public void onRegistrationDone(ISipSession session, int duration) {
+ long expiryTime = duration;
+ if (duration > 0) expiryTime += System.currentTimeMillis();
+ mListener.onRegistrationDone(getUri(session), expiryTime);
+ }
+
+ @Override
+ public void onRegistrationFailed(ISipSession session, int errorCode,
+ String message) {
+ mListener.onRegistrationFailed(getUri(session), errorCode, message);
+ }
+
+ @Override
+ public void onRegistrationTimeout(ISipSession session) {
+ mListener.onRegistrationFailed(getUri(session),
+ SipErrorCode.TIME_OUT, "registration timed out");
+ }
+ }
+}
diff --git a/android/net/sip/SipProfile.java b/android/net/sip/SipProfile.java
new file mode 100644
index 0000000..0ef754c
--- /dev/null
+++ b/android/net/sip/SipProfile.java
@@ -0,0 +1,502 @@
+/*
+ * Copyright (C) 2010 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.sip;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.text.ParseException;
+import javax.sip.InvalidArgumentException;
+import javax.sip.ListeningPoint;
+import javax.sip.PeerUnavailableException;
+import javax.sip.SipFactory;
+import javax.sip.address.Address;
+import javax.sip.address.AddressFactory;
+import javax.sip.address.SipURI;
+import javax.sip.address.URI;
+
+/**
+ * Defines a SIP profile, including a SIP account, domain and server information.
+ * <p>You can create a {@link SipProfile} using {@link
+ * SipProfile.Builder}. You can also retrieve one from a {@link SipSession}, using {@link
+ * SipSession#getLocalProfile} and {@link SipSession#getPeerProfile}.</p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using SIP, read the
+ * <a href="{@docRoot}guide/topics/network/sip.html">Session Initiation Protocol</a>
+ * developer guide.</p>
+ * </div>
+ */
+public class SipProfile implements Parcelable, Serializable, Cloneable {
+ private static final long serialVersionUID = 1L;
+ private static final int DEFAULT_PORT = 5060;
+ private static final String TCP = "TCP";
+ private static final String UDP = "UDP";
+ private Address mAddress;
+ private String mProxyAddress;
+ private String mPassword;
+ private String mDomain;
+ private String mProtocol = UDP;
+ private String mProfileName;
+ private String mAuthUserName;
+ private int mPort = DEFAULT_PORT;
+ private boolean mSendKeepAlive = false;
+ private boolean mAutoRegistration = true;
+ private transient int mCallingUid = 0;
+
+ public static final Parcelable.Creator<SipProfile> CREATOR =
+ new Parcelable.Creator<SipProfile>() {
+ public SipProfile createFromParcel(Parcel in) {
+ return new SipProfile(in);
+ }
+
+ public SipProfile[] newArray(int size) {
+ return new SipProfile[size];
+ }
+ };
+
+ /**
+ * Helper class for creating a {@link SipProfile}.
+ */
+ public static class Builder {
+ private AddressFactory mAddressFactory;
+ private SipProfile mProfile = new SipProfile();
+ private SipURI mUri;
+ private String mDisplayName;
+ private String mProxyAddress;
+
+ {
+ try {
+ mAddressFactory =
+ SipFactory.getInstance().createAddressFactory();
+ } catch (PeerUnavailableException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Creates a builder based on the given profile.
+ */
+ public Builder(SipProfile profile) {
+ if (profile == null) throw new NullPointerException();
+ try {
+ mProfile = (SipProfile) profile.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new RuntimeException("should not occur", e);
+ }
+ mProfile.mAddress = null;
+ mUri = profile.getUri();
+ mUri.setUserPassword(profile.getPassword());
+ mDisplayName = profile.getDisplayName();
+ mProxyAddress = profile.getProxyAddress();
+ mProfile.mPort = profile.getPort();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param uriString the URI string as "sip:<user_name>@<domain>"
+ * @throws ParseException if the string is not a valid URI
+ */
+ public Builder(String uriString) throws ParseException {
+ if (uriString == null) {
+ throw new NullPointerException("uriString cannot be null");
+ }
+ URI uri = mAddressFactory.createURI(fix(uriString));
+ if (uri instanceof SipURI) {
+ mUri = (SipURI) uri;
+ } else {
+ throw new ParseException(uriString + " is not a SIP URI", 0);
+ }
+ mProfile.mDomain = mUri.getHost();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param username username of the SIP account
+ * @param serverDomain the SIP server domain; if the network address
+ * is different from the domain, use {@link #setOutboundProxy} to
+ * set server address
+ * @throws ParseException if the parameters are not valid
+ */
+ public Builder(String username, String serverDomain)
+ throws ParseException {
+ if ((username == null) || (serverDomain == null)) {
+ throw new NullPointerException(
+ "username and serverDomain cannot be null");
+ }
+ mUri = mAddressFactory.createSipURI(username, serverDomain);
+ mProfile.mDomain = serverDomain;
+ }
+
+ private String fix(String uriString) {
+ return (uriString.trim().toLowerCase().startsWith("sip:")
+ ? uriString
+ : "sip:" + uriString);
+ }
+
+ /**
+ * Sets the username used for authentication.
+ *
+ * @param name authentication username of the profile
+ * @return this builder object
+ */
+ public Builder setAuthUserName(String name) {
+ mProfile.mAuthUserName = name;
+ return this;
+ }
+
+ /**
+ * Sets the name of the profile. This name is given by user.
+ *
+ * @param name name of the profile
+ * @return this builder object
+ */
+ public Builder setProfileName(String name) {
+ mProfile.mProfileName = name;
+ return this;
+ }
+
+ /**
+ * Sets the password of the SIP account
+ *
+ * @param password password of the SIP account
+ * @return this builder object
+ */
+ public Builder setPassword(String password) {
+ mUri.setUserPassword(password);
+ return this;
+ }
+
+ /**
+ * Sets the port number of the server. By default, it is 5060.
+ *
+ * @param port port number of the server
+ * @return this builder object
+ * @throws IllegalArgumentException if the port number is out of range
+ */
+ public Builder setPort(int port) throws IllegalArgumentException {
+ if ((port > 65535) || (port < 1000)) {
+ throw new IllegalArgumentException("incorrect port arugment: " + port);
+ }
+ mProfile.mPort = port;
+ return this;
+ }
+
+ /**
+ * Sets the protocol used to connect to the SIP server. Currently,
+ * only "UDP" and "TCP" are supported.
+ *
+ * @param protocol the protocol string
+ * @return this builder object
+ * @throws IllegalArgumentException if the protocol is not recognized
+ */
+ public Builder setProtocol(String protocol)
+ throws IllegalArgumentException {
+ if (protocol == null) {
+ throw new NullPointerException("protocol cannot be null");
+ }
+ protocol = protocol.toUpperCase();
+ if (!protocol.equals(UDP) && !protocol.equals(TCP)) {
+ throw new IllegalArgumentException(
+ "unsupported protocol: " + protocol);
+ }
+ mProfile.mProtocol = protocol;
+ return this;
+ }
+
+ /**
+ * Sets the outbound proxy of the SIP server.
+ *
+ * @param outboundProxy the network address of the outbound proxy
+ * @return this builder object
+ */
+ public Builder setOutboundProxy(String outboundProxy) {
+ mProxyAddress = outboundProxy;
+ return this;
+ }
+
+ /**
+ * Sets the display name of the user.
+ *
+ * @param displayName display name of the user
+ * @return this builder object
+ */
+ public Builder setDisplayName(String displayName) {
+ mDisplayName = displayName;
+ return this;
+ }
+
+ /**
+ * Sets the send keep-alive flag.
+ *
+ * @param flag true if sending keep-alive message is required,
+ * false otherwise
+ * @return this builder object
+ */
+ public Builder setSendKeepAlive(boolean flag) {
+ mProfile.mSendKeepAlive = flag;
+ return this;
+ }
+
+
+ /**
+ * Sets the auto. registration flag.
+ *
+ * @param flag true if the profile will be registered automatically,
+ * false otherwise
+ * @return this builder object
+ */
+ public Builder setAutoRegistration(boolean flag) {
+ mProfile.mAutoRegistration = flag;
+ return this;
+ }
+
+ /**
+ * Builds and returns the SIP profile object.
+ *
+ * @return the profile object created
+ */
+ public SipProfile build() {
+ // remove password from URI
+ mProfile.mPassword = mUri.getUserPassword();
+ mUri.setUserPassword(null);
+ try {
+ if (!TextUtils.isEmpty(mProxyAddress)) {
+ SipURI uri = (SipURI)
+ mAddressFactory.createURI(fix(mProxyAddress));
+ mProfile.mProxyAddress = uri.getHost();
+ } else {
+ if (!mProfile.mProtocol.equals(UDP)) {
+ mUri.setTransportParam(mProfile.mProtocol);
+ }
+ if (mProfile.mPort != DEFAULT_PORT) {
+ mUri.setPort(mProfile.mPort);
+ }
+ }
+ mProfile.mAddress = mAddressFactory.createAddress(
+ mDisplayName, mUri);
+ } catch (InvalidArgumentException e) {
+ throw new RuntimeException(e);
+ } catch (ParseException e) {
+ // must not occur
+ throw new RuntimeException(e);
+ }
+ return mProfile;
+ }
+ }
+
+ private SipProfile() {
+ }
+
+ private SipProfile(Parcel in) {
+ mAddress = (Address) in.readSerializable();
+ mProxyAddress = in.readString();
+ mPassword = in.readString();
+ mDomain = in.readString();
+ mProtocol = in.readString();
+ mProfileName = in.readString();
+ mSendKeepAlive = (in.readInt() == 0) ? false : true;
+ mAutoRegistration = (in.readInt() == 0) ? false : true;
+ mCallingUid = in.readInt();
+ mPort = in.readInt();
+ mAuthUserName = in.readString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeSerializable(mAddress);
+ out.writeString(mProxyAddress);
+ out.writeString(mPassword);
+ out.writeString(mDomain);
+ out.writeString(mProtocol);
+ out.writeString(mProfileName);
+ out.writeInt(mSendKeepAlive ? 1 : 0);
+ out.writeInt(mAutoRegistration ? 1 : 0);
+ out.writeInt(mCallingUid);
+ out.writeInt(mPort);
+ out.writeString(mAuthUserName);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Gets the SIP URI of this profile.
+ *
+ * @return the SIP URI of this profile
+ * @hide
+ */
+ public SipURI getUri() {
+ return (SipURI) mAddress.getURI();
+ }
+
+ /**
+ * Gets the SIP URI string of this profile.
+ *
+ * @return the SIP URI string of this profile
+ */
+ public String getUriString() {
+ // We need to return the sip uri domain instead of
+ // the SIP URI with transport, port information if
+ // the outbound proxy address exists.
+ if (!TextUtils.isEmpty(mProxyAddress)) {
+ return "sip:" + getUserName() + "@" + mDomain;
+ }
+ return getUri().toString();
+ }
+
+ /**
+ * Gets the SIP address of this profile.
+ *
+ * @return the SIP address of this profile
+ * @hide
+ */
+ public Address getSipAddress() {
+ return mAddress;
+ }
+
+ /**
+ * Gets the display name of the user.
+ *
+ * @return the display name of the user
+ */
+ public String getDisplayName() {
+ return mAddress.getDisplayName();
+ }
+
+ /**
+ * Gets the username.
+ *
+ * @return the username
+ */
+ public String getUserName() {
+ return getUri().getUser();
+ }
+
+ /**
+ * Gets the username for authentication. If it is null, then the username
+ * is used in authentication instead.
+ *
+ * @return the authentication username
+ * @see #getUserName
+ */
+ public String getAuthUserName() {
+ return mAuthUserName;
+ }
+
+ /**
+ * Gets the password.
+ *
+ * @return the password
+ */
+ public String getPassword() {
+ return mPassword;
+ }
+
+ /**
+ * Gets the SIP domain.
+ *
+ * @return the SIP domain
+ */
+ public String getSipDomain() {
+ return mDomain;
+ }
+
+ /**
+ * Gets the port number of the SIP server.
+ *
+ * @return the port number of the SIP server
+ */
+ public int getPort() {
+ return mPort;
+ }
+
+ /**
+ * Gets the protocol used to connect to the server.
+ *
+ * @return the protocol
+ */
+ public String getProtocol() {
+ return mProtocol;
+ }
+
+ /**
+ * Gets the network address of the server outbound proxy.
+ *
+ * @return the network address of the server outbound proxy
+ */
+ public String getProxyAddress() {
+ return mProxyAddress;
+ }
+
+ /**
+ * Gets the (user-defined) name of the profile.
+ *
+ * @return name of the profile
+ */
+ public String getProfileName() {
+ return mProfileName;
+ }
+
+ /**
+ * Gets the flag of 'Sending keep-alive'.
+ *
+ * @return the flag of sending SIP keep-alive messages.
+ */
+ public boolean getSendKeepAlive() {
+ return mSendKeepAlive;
+ }
+
+ /**
+ * Gets the flag of 'Auto Registration'.
+ *
+ * @return the flag of registering the profile automatically.
+ */
+ public boolean getAutoRegistration() {
+ return mAutoRegistration;
+ }
+
+ /**
+ * Sets the calling process's Uid in the sip service.
+ * @hide
+ */
+ public void setCallingUid(int uid) {
+ mCallingUid = uid;
+ }
+
+ /**
+ * Gets the calling process's Uid in the sip settings.
+ * @hide
+ */
+ public int getCallingUid() {
+ return mCallingUid;
+ }
+
+ private Object readResolve() throws ObjectStreamException {
+ // For compatibility.
+ if (mPort == 0) mPort = DEFAULT_PORT;
+ return this;
+ }
+}
diff --git a/android/net/sip/SipRegistrationListener.java b/android/net/sip/SipRegistrationListener.java
new file mode 100644
index 0000000..9968cc7
--- /dev/null
+++ b/android/net/sip/SipRegistrationListener.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 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.sip;
+
+/**
+ * Listener for SIP registration events.
+ */
+public interface SipRegistrationListener {
+ /**
+ * Called when a registration request is sent.
+ *
+ * @param localProfileUri the URI string of the SIP profile to register with
+ */
+ void onRegistering(String localProfileUri);
+
+ /**
+ * Called when the registration succeeded.
+ *
+ * @param localProfileUri the URI string of the SIP profile to register with
+ * @param expiryTime duration in seconds before the registration expires
+ */
+ void onRegistrationDone(String localProfileUri, long expiryTime);
+
+ /**
+ * Called when the registration failed.
+ *
+ * @param localProfileUri the URI string of the SIP profile to register with
+ * @param errorCode error code of this error
+ * @param errorMessage error message
+ * @see SipErrorCode
+ */
+ void onRegistrationFailed(String localProfileUri, int errorCode,
+ String errorMessage);
+}
diff --git a/android/net/sip/SipSession.java b/android/net/sip/SipSession.java
new file mode 100644
index 0000000..edbc66f
--- /dev/null
+++ b/android/net/sip/SipSession.java
@@ -0,0 +1,574 @@
+/*
+ * Copyright (C) 2010 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.sip;
+
+import android.os.RemoteException;
+import android.telephony.Rlog;
+
+/**
+ * Represents a SIP session that is associated with a SIP dialog or a standalone
+ * transaction not within a dialog.
+ * <p>You can get a {@link SipSession} from {@link SipManager} with {@link
+ * SipManager#createSipSession createSipSession()} (when initiating calls) or {@link
+ * SipManager#getSessionFor getSessionFor()} (when receiving calls).</p>
+ */
+public final class SipSession {
+ private static final String TAG = "SipSession";
+
+ /**
+ * Defines SIP session states, such as "registering", "outgoing call", and "in call".
+ */
+ public static class State {
+ /** When session is ready to initiate a call or transaction. */
+ public static final int READY_TO_CALL = 0;
+
+ /** When the registration request is sent out. */
+ public static final int REGISTERING = 1;
+
+ /** When the unregistration request is sent out. */
+ public static final int DEREGISTERING = 2;
+
+ /** When an INVITE request is received. */
+ public static final int INCOMING_CALL = 3;
+
+ /** When an OK response is sent for the INVITE request received. */
+ public static final int INCOMING_CALL_ANSWERING = 4;
+
+ /** When an INVITE request is sent. */
+ public static final int OUTGOING_CALL = 5;
+
+ /** When a RINGING response is received for the INVITE request sent. */
+ public static final int OUTGOING_CALL_RING_BACK = 6;
+
+ /** When a CANCEL request is sent for the INVITE request sent. */
+ public static final int OUTGOING_CALL_CANCELING = 7;
+
+ /** When a call is established. */
+ public static final int IN_CALL = 8;
+
+ /** When an OPTIONS request is sent. */
+ public static final int PINGING = 9;
+
+ /** When ending a call. @hide */
+ public static final int ENDING_CALL = 10;
+
+ /** Not defined. */
+ public static final int NOT_DEFINED = 101;
+
+ /**
+ * Converts the state to string.
+ */
+ public static String toString(int state) {
+ switch (state) {
+ case READY_TO_CALL:
+ return "READY_TO_CALL";
+ case REGISTERING:
+ return "REGISTERING";
+ case DEREGISTERING:
+ return "DEREGISTERING";
+ case INCOMING_CALL:
+ return "INCOMING_CALL";
+ case INCOMING_CALL_ANSWERING:
+ return "INCOMING_CALL_ANSWERING";
+ case OUTGOING_CALL:
+ return "OUTGOING_CALL";
+ case OUTGOING_CALL_RING_BACK:
+ return "OUTGOING_CALL_RING_BACK";
+ case OUTGOING_CALL_CANCELING:
+ return "OUTGOING_CALL_CANCELING";
+ case IN_CALL:
+ return "IN_CALL";
+ case PINGING:
+ return "PINGING";
+ default:
+ return "NOT_DEFINED";
+ }
+ }
+
+ private State() {
+ }
+ }
+
+ /**
+ * Listener for events relating to a SIP session, such as when a session is being registered
+ * ("on registering") or a call is outgoing ("on calling").
+ * <p>Many of these events are also received by {@link SipAudioCall.Listener}.</p>
+ */
+ public static class Listener {
+ /**
+ * Called when an INVITE request is sent to initiate a new call.
+ *
+ * @param session the session object that carries out the transaction
+ */
+ public void onCalling(SipSession session) {
+ }
+
+ /**
+ * Called when an INVITE request is received.
+ *
+ * @param session the session object that carries out the transaction
+ * @param caller the SIP profile of the caller
+ * @param sessionDescription the caller's session description
+ */
+ public void onRinging(SipSession session, SipProfile caller,
+ String sessionDescription) {
+ }
+
+ /**
+ * Called when a RINGING response is received for the INVITE request sent
+ *
+ * @param session the session object that carries out the transaction
+ */
+ public void onRingingBack(SipSession session) {
+ }
+
+ /**
+ * Called when the session is established.
+ *
+ * @param session the session object that is associated with the dialog
+ * @param sessionDescription the peer's session description
+ */
+ public void onCallEstablished(SipSession session,
+ String sessionDescription) {
+ }
+
+ /**
+ * Called when the session is terminated.
+ *
+ * @param session the session object that is associated with the dialog
+ */
+ public void onCallEnded(SipSession session) {
+ }
+
+ /**
+ * Called when the peer is busy during session initialization.
+ *
+ * @param session the session object that carries out the transaction
+ */
+ public void onCallBusy(SipSession session) {
+ }
+
+ /**
+ * Called when the call is being transferred to a new one.
+ *
+ * @hide
+ * @param newSession the new session that the call will be transferred to
+ * @param sessionDescription the new peer's session description
+ */
+ public void onCallTransferring(SipSession newSession,
+ String sessionDescription) {
+ }
+
+ /**
+ * Called when an error occurs during session initialization and
+ * termination.
+ *
+ * @param session the session object that carries out the transaction
+ * @param errorCode error code defined in {@link SipErrorCode}
+ * @param errorMessage error message
+ */
+ public void onError(SipSession session, int errorCode,
+ String errorMessage) {
+ }
+
+ /**
+ * Called when an error occurs during session modification negotiation.
+ *
+ * @param session the session object that carries out the transaction
+ * @param errorCode error code defined in {@link SipErrorCode}
+ * @param errorMessage error message
+ */
+ public void onCallChangeFailed(SipSession session, int errorCode,
+ String errorMessage) {
+ }
+
+ /**
+ * Called when a registration request is sent.
+ *
+ * @param session the session object that carries out the transaction
+ */
+ public void onRegistering(SipSession session) {
+ }
+
+ /**
+ * Called when registration is successfully done.
+ *
+ * @param session the session object that carries out the transaction
+ * @param duration duration in second before the registration expires
+ */
+ public void onRegistrationDone(SipSession session, int duration) {
+ }
+
+ /**
+ * Called when the registration fails.
+ *
+ * @param session the session object that carries out the transaction
+ * @param errorCode error code defined in {@link SipErrorCode}
+ * @param errorMessage error message
+ */
+ public void onRegistrationFailed(SipSession session, int errorCode,
+ String errorMessage) {
+ }
+
+ /**
+ * Called when the registration gets timed out.
+ *
+ * @param session the session object that carries out the transaction
+ */
+ public void onRegistrationTimeout(SipSession session) {
+ }
+ }
+
+ private final ISipSession mSession;
+ private Listener mListener;
+
+ SipSession(ISipSession realSession) {
+ mSession = realSession;
+ if (realSession != null) {
+ try {
+ realSession.setListener(createListener());
+ } catch (RemoteException e) {
+ loge("SipSession.setListener:", e);
+ }
+ }
+ }
+
+ SipSession(ISipSession realSession, Listener listener) {
+ this(realSession);
+ setListener(listener);
+ }
+
+ /**
+ * Gets the IP address of the local host on which this SIP session runs.
+ *
+ * @return the IP address of the local host
+ */
+ public String getLocalIp() {
+ try {
+ return mSession.getLocalIp();
+ } catch (RemoteException e) {
+ loge("getLocalIp:", e);
+ return "127.0.0.1";
+ }
+ }
+
+ /**
+ * Gets the SIP profile that this session is associated with.
+ *
+ * @return the SIP profile that this session is associated with
+ */
+ public SipProfile getLocalProfile() {
+ try {
+ return mSession.getLocalProfile();
+ } catch (RemoteException e) {
+ loge("getLocalProfile:", e);
+ return null;
+ }
+ }
+
+ /**
+ * Gets the SIP profile that this session is connected to. Only available
+ * when the session is associated with a SIP dialog.
+ *
+ * @return the SIP profile that this session is connected to
+ */
+ public SipProfile getPeerProfile() {
+ try {
+ return mSession.getPeerProfile();
+ } catch (RemoteException e) {
+ loge("getPeerProfile:", e);
+ return null;
+ }
+ }
+
+ /**
+ * Gets the session state. The value returned must be one of the states in
+ * {@link State}.
+ *
+ * @return the session state
+ */
+ public int getState() {
+ try {
+ return mSession.getState();
+ } catch (RemoteException e) {
+ loge("getState:", e);
+ return State.NOT_DEFINED;
+ }
+ }
+
+ /**
+ * Checks if the session is in a call.
+ *
+ * @return true if the session is in a call
+ */
+ public boolean isInCall() {
+ try {
+ return mSession.isInCall();
+ } catch (RemoteException e) {
+ loge("isInCall:", e);
+ return false;
+ }
+ }
+
+ /**
+ * Gets the call ID of the session.
+ *
+ * @return the call ID
+ */
+ public String getCallId() {
+ try {
+ return mSession.getCallId();
+ } catch (RemoteException e) {
+ loge("getCallId:", e);
+ return null;
+ }
+ }
+
+
+ /**
+ * Sets the listener to listen to the session events. A {@code SipSession}
+ * can only hold one listener at a time. Subsequent calls to this method
+ * override the previous listener.
+ *
+ * @param listener to listen to the session events of this object
+ */
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+
+ /**
+ * Performs registration to the server specified by the associated local
+ * profile. The session listener is called back upon success or failure of
+ * registration. The method is only valid to call when the session state is
+ * in {@link State#READY_TO_CALL}.
+ *
+ * @param duration duration in second before the registration expires
+ * @see Listener
+ */
+ public void register(int duration) {
+ try {
+ mSession.register(duration);
+ } catch (RemoteException e) {
+ loge("register:", e);
+ }
+ }
+
+ /**
+ * Performs unregistration to the server specified by the associated local
+ * profile. Unregistration is technically the same as registration with zero
+ * expiration duration. The session listener is called back upon success or
+ * failure of unregistration. The method is only valid to call when the
+ * session state is in {@link State#READY_TO_CALL}.
+ *
+ * @see Listener
+ */
+ public void unregister() {
+ try {
+ mSession.unregister();
+ } catch (RemoteException e) {
+ loge("unregister:", e);
+ }
+ }
+
+ /**
+ * Initiates a call to the specified profile. The session listener is called
+ * back upon defined session events. The method is only valid to call when
+ * the session state is in {@link State#READY_TO_CALL}.
+ *
+ * @param callee the SIP profile to make the call to
+ * @param sessionDescription the session description of this call
+ * @param timeout the session will be timed out if the call is not
+ * established within {@code timeout} seconds. Default value (defined
+ * by SIP protocol) is used if {@code timeout} is zero or negative.
+ * @see Listener
+ */
+ public void makeCall(SipProfile callee, String sessionDescription,
+ int timeout) {
+ try {
+ mSession.makeCall(callee, sessionDescription, timeout);
+ } catch (RemoteException e) {
+ loge("makeCall:", e);
+ }
+ }
+
+ /**
+ * Answers an incoming call with the specified session description. The
+ * method is only valid to call when the session state is in
+ * {@link State#INCOMING_CALL}.
+ *
+ * @param sessionDescription the session description to answer this call
+ * @param timeout the session will be timed out if the call is not
+ * established within {@code timeout} seconds. Default value (defined
+ * by SIP protocol) is used if {@code timeout} is zero or negative.
+ */
+ public void answerCall(String sessionDescription, int timeout) {
+ try {
+ mSession.answerCall(sessionDescription, timeout);
+ } catch (RemoteException e) {
+ loge("answerCall:", e);
+ }
+ }
+
+ /**
+ * Ends an established call, terminates an outgoing call or rejects an
+ * incoming call. The method is only valid to call when the session state is
+ * in {@link State#IN_CALL},
+ * {@link State#INCOMING_CALL},
+ * {@link State#OUTGOING_CALL} or
+ * {@link State#OUTGOING_CALL_RING_BACK}.
+ */
+ public void endCall() {
+ try {
+ mSession.endCall();
+ } catch (RemoteException e) {
+ loge("endCall:", e);
+ }
+ }
+
+ /**
+ * Changes the session description during a call. The method is only valid
+ * to call when the session state is in {@link State#IN_CALL}.
+ *
+ * @param sessionDescription the new session description
+ * @param timeout the session will be timed out if the call is not
+ * established within {@code timeout} seconds. Default value (defined
+ * by SIP protocol) is used if {@code timeout} is zero or negative.
+ */
+ public void changeCall(String sessionDescription, int timeout) {
+ try {
+ mSession.changeCall(sessionDescription, timeout);
+ } catch (RemoteException e) {
+ loge("changeCall:", e);
+ }
+ }
+
+ ISipSession getRealSession() {
+ return mSession;
+ }
+
+ private ISipSessionListener createListener() {
+ return new ISipSessionListener.Stub() {
+ @Override
+ public void onCalling(ISipSession session) {
+ if (mListener != null) {
+ mListener.onCalling(SipSession.this);
+ }
+ }
+
+ @Override
+ public void onRinging(ISipSession session, SipProfile caller,
+ String sessionDescription) {
+ if (mListener != null) {
+ mListener.onRinging(SipSession.this, caller,
+ sessionDescription);
+ }
+ }
+
+ @Override
+ public void onRingingBack(ISipSession session) {
+ if (mListener != null) {
+ mListener.onRingingBack(SipSession.this);
+ }
+ }
+
+ @Override
+ public void onCallEstablished(ISipSession session,
+ String sessionDescription) {
+ if (mListener != null) {
+ mListener.onCallEstablished(SipSession.this,
+ sessionDescription);
+ }
+ }
+
+ @Override
+ public void onCallEnded(ISipSession session) {
+ if (mListener != null) {
+ mListener.onCallEnded(SipSession.this);
+ }
+ }
+
+ @Override
+ public void onCallBusy(ISipSession session) {
+ if (mListener != null) {
+ mListener.onCallBusy(SipSession.this);
+ }
+ }
+
+ @Override
+ public void onCallTransferring(ISipSession session,
+ String sessionDescription) {
+ if (mListener != null) {
+ mListener.onCallTransferring(
+ new SipSession(session, SipSession.this.mListener),
+ sessionDescription);
+
+ }
+ }
+
+ @Override
+ public void onCallChangeFailed(ISipSession session, int errorCode,
+ String message) {
+ if (mListener != null) {
+ mListener.onCallChangeFailed(SipSession.this, errorCode,
+ message);
+ }
+ }
+
+ @Override
+ public void onError(ISipSession session, int errorCode, String message) {
+ if (mListener != null) {
+ mListener.onError(SipSession.this, errorCode, message);
+ }
+ }
+
+ @Override
+ public void onRegistering(ISipSession session) {
+ if (mListener != null) {
+ mListener.onRegistering(SipSession.this);
+ }
+ }
+
+ @Override
+ public void onRegistrationDone(ISipSession session, int duration) {
+ if (mListener != null) {
+ mListener.onRegistrationDone(SipSession.this, duration);
+ }
+ }
+
+ @Override
+ public void onRegistrationFailed(ISipSession session, int errorCode,
+ String message) {
+ if (mListener != null) {
+ mListener.onRegistrationFailed(SipSession.this, errorCode,
+ message);
+ }
+ }
+
+ @Override
+ public void onRegistrationTimeout(ISipSession session) {
+ if (mListener != null) {
+ mListener.onRegistrationTimeout(SipSession.this);
+ }
+ }
+ };
+ }
+
+ private void loge(String s, Throwable t) {
+ Rlog.e(TAG, s, t);
+ }
+}
diff --git a/android/net/sip/SipSessionAdapter.java b/android/net/sip/SipSessionAdapter.java
new file mode 100644
index 0000000..f538983
--- /dev/null
+++ b/android/net/sip/SipSessionAdapter.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2010 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.sip;
+
+/**
+ * Adapter class for {@link ISipSessionListener}. Default implementation of all
+ * callback methods is no-op.
+ * @hide
+ */
+public class SipSessionAdapter extends ISipSessionListener.Stub {
+ public void onCalling(ISipSession session) {
+ }
+
+ public void onRinging(ISipSession session, SipProfile caller,
+ String sessionDescription) {
+ }
+
+ public void onRingingBack(ISipSession session) {
+ }
+
+ public void onCallEstablished(ISipSession session,
+ String sessionDescription) {
+ }
+
+ public void onCallEnded(ISipSession session) {
+ }
+
+ public void onCallBusy(ISipSession session) {
+ }
+
+ public void onCallTransferring(ISipSession session,
+ String sessionDescription) {
+ }
+
+ public void onCallChangeFailed(ISipSession session, int errorCode,
+ String message) {
+ }
+
+ public void onError(ISipSession session, int errorCode, String message) {
+ }
+
+ public void onRegistering(ISipSession session) {
+ }
+
+ public void onRegistrationDone(ISipSession session, int duration) {
+ }
+
+ public void onRegistrationFailed(ISipSession session, int errorCode,
+ String message) {
+ }
+
+ public void onRegistrationTimeout(ISipSession session) {
+ }
+}
diff --git a/android/net/util/DnsUtils.java b/android/net/util/DnsUtils.java
new file mode 100644
index 0000000..e6abd50
--- /dev/null
+++ b/android/net/util/DnsUtils.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.util;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.InetAddresses;
+import android.net.Network;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import com.android.internal.util.BitUtils;
+
+import libcore.io.IoUtils;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * @hide
+ */
+public class DnsUtils {
+ private static final String TAG = "DnsUtils";
+ private static final int CHAR_BIT = 8;
+ public static final int IPV6_ADDR_SCOPE_NODELOCAL = 0x01;
+ public static final int IPV6_ADDR_SCOPE_LINKLOCAL = 0x02;
+ public static final int IPV6_ADDR_SCOPE_SITELOCAL = 0x05;
+ public static final int IPV6_ADDR_SCOPE_GLOBAL = 0x0e;
+ private static final Comparator<SortableAddress> sRfc6724Comparator = new Rfc6724Comparator();
+
+ /**
+ * Comparator to sort SortableAddress in Rfc6724 style.
+ */
+ public static class Rfc6724Comparator implements Comparator<SortableAddress> {
+ // This function matches the behaviour of _rfc6724_compare in the native resolver.
+ @Override
+ public int compare(SortableAddress span1, SortableAddress span2) {
+ // Rule 1: Avoid unusable destinations.
+ if (span1.hasSrcAddr != span2.hasSrcAddr) {
+ return span2.hasSrcAddr - span1.hasSrcAddr;
+ }
+
+ // Rule 2: Prefer matching scope.
+ if (span1.scopeMatch != span2.scopeMatch) {
+ return span2.scopeMatch - span1.scopeMatch;
+ }
+
+ // TODO: Implement rule 3: Avoid deprecated addresses.
+ // TODO: Implement rule 4: Prefer home addresses.
+
+ // Rule 5: Prefer matching label.
+ if (span1.labelMatch != span2.labelMatch) {
+ return span2.labelMatch - span1.labelMatch;
+ }
+
+ // Rule 6: Prefer higher precedence.
+ if (span1.precedence != span2.precedence) {
+ return span2.precedence - span1.precedence;
+ }
+
+ // TODO: Implement rule 7: Prefer native transport.
+
+ // Rule 8: Prefer smaller scope.
+ if (span1.scope != span2.scope) {
+ return span1.scope - span2.scope;
+ }
+
+ // Rule 9: Use longest matching prefix. IPv6 only.
+ if (span1.prefixMatchLen != span2.prefixMatchLen) {
+ return span2.prefixMatchLen - span1.prefixMatchLen;
+ }
+
+ // Rule 10: Leave the order unchanged. Collections.sort is a stable sort.
+ return 0;
+ }
+ }
+
+ /**
+ * Class used to sort with RFC 6724
+ */
+ public static class SortableAddress {
+ public final int label;
+ public final int labelMatch;
+ public final int scope;
+ public final int scopeMatch;
+ public final int precedence;
+ public final int prefixMatchLen;
+ public final int hasSrcAddr;
+ public final InetAddress address;
+
+ public SortableAddress(@NonNull InetAddress addr, @Nullable InetAddress srcAddr) {
+ address = addr;
+ hasSrcAddr = (srcAddr != null) ? 1 : 0;
+ label = findLabel(addr);
+ scope = findScope(addr);
+ precedence = findPrecedence(addr);
+ labelMatch = ((srcAddr != null) && (label == findLabel(srcAddr))) ? 1 : 0;
+ scopeMatch = ((srcAddr != null) && (scope == findScope(srcAddr))) ? 1 : 0;
+ if (isIpv6Address(addr) && isIpv6Address(srcAddr)) {
+ prefixMatchLen = compareIpv6PrefixMatchLen(srcAddr, addr);
+ } else {
+ prefixMatchLen = 0;
+ }
+ }
+ }
+
+ /**
+ * Sort the given address list in RFC6724 order.
+ * Will leave the list unchanged if an error occurs.
+ *
+ * This function matches the behaviour of _rfc6724_sort in the native resolver.
+ */
+ public static @NonNull List<InetAddress> rfc6724Sort(@Nullable Network network,
+ @NonNull List<InetAddress> answers) {
+ List<SortableAddress> sortableAnswerList = new ArrayList<>();
+ answers.forEach(addr -> sortableAnswerList.add(
+ new SortableAddress(addr, findSrcAddress(network, addr))));
+
+ Collections.sort(sortableAnswerList, sRfc6724Comparator);
+
+ final List<InetAddress> sortedAnswers = new ArrayList<>();
+ sortableAnswerList.forEach(ans -> sortedAnswers.add(ans.address));
+
+ return sortedAnswers;
+ }
+
+ private static @Nullable InetAddress findSrcAddress(@Nullable Network network,
+ @NonNull InetAddress addr) {
+ final int domain;
+ if (isIpv4Address(addr)) {
+ domain = AF_INET;
+ } else if (isIpv6Address(addr)) {
+ domain = AF_INET6;
+ } else {
+ return null;
+ }
+ final FileDescriptor socket;
+ try {
+ socket = Os.socket(domain, SOCK_DGRAM, IPPROTO_UDP);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "findSrcAddress:" + e.toString());
+ return null;
+ }
+ try {
+ if (network != null) network.bindSocket(socket);
+ Os.connect(socket, new InetSocketAddress(addr, 0));
+ return ((InetSocketAddress) Os.getsockname(socket)).getAddress();
+ } catch (IOException | ErrnoException e) {
+ return null;
+ } finally {
+ IoUtils.closeQuietly(socket);
+ }
+ }
+
+ /**
+ * Get the label for a given IPv4/IPv6 address.
+ * RFC 6724, section 2.1.
+ *
+ * Note that Java will return an IPv4-mapped address as an IPv4 address.
+ */
+ private static int findLabel(@NonNull InetAddress addr) {
+ if (isIpv4Address(addr)) {
+ return 4;
+ } else if (isIpv6Address(addr)) {
+ if (addr.isLoopbackAddress()) {
+ return 0;
+ } else if (isIpv6Address6To4(addr)) {
+ return 2;
+ } else if (isIpv6AddressTeredo(addr)) {
+ return 5;
+ } else if (isIpv6AddressULA(addr)) {
+ return 13;
+ } else if (((Inet6Address) addr).isIPv4CompatibleAddress()) {
+ return 3;
+ } else if (addr.isSiteLocalAddress()) {
+ return 11;
+ } else if (isIpv6Address6Bone(addr)) {
+ return 12;
+ } else {
+ // All other IPv6 addresses, including global unicast addresses.
+ return 1;
+ }
+ } else {
+ // This should never happen.
+ return 1;
+ }
+ }
+
+ private static boolean isIpv6Address(@Nullable InetAddress addr) {
+ return addr instanceof Inet6Address;
+ }
+
+ private static boolean isIpv4Address(@Nullable InetAddress addr) {
+ return addr instanceof Inet4Address;
+ }
+
+ private static boolean isIpv6Address6To4(@NonNull InetAddress addr) {
+ if (!isIpv6Address(addr)) return false;
+ final byte[] byteAddr = addr.getAddress();
+ return byteAddr[0] == 0x20 && byteAddr[1] == 0x02;
+ }
+
+ private static boolean isIpv6AddressTeredo(@NonNull InetAddress addr) {
+ if (!isIpv6Address(addr)) return false;
+ final byte[] byteAddr = addr.getAddress();
+ return byteAddr[0] == 0x20 && byteAddr[1] == 0x01 && byteAddr[2] == 0x00
+ && byteAddr[3] == 0x00;
+ }
+
+ private static boolean isIpv6AddressULA(@NonNull InetAddress addr) {
+ return isIpv6Address(addr) && (addr.getAddress()[0] & 0xfe) == 0xfc;
+ }
+
+ private static boolean isIpv6Address6Bone(@NonNull InetAddress addr) {
+ if (!isIpv6Address(addr)) return false;
+ final byte[] byteAddr = addr.getAddress();
+ return byteAddr[0] == 0x3f && byteAddr[1] == (byte) 0xfe;
+ }
+
+ private static int getIpv6MulticastScope(@NonNull InetAddress addr) {
+ return !isIpv6Address(addr) ? 0 : (addr.getAddress()[1] & 0x0f);
+ }
+
+ private static int findScope(@NonNull InetAddress addr) {
+ if (isIpv6Address(addr)) {
+ if (addr.isMulticastAddress()) {
+ return getIpv6MulticastScope(addr);
+ } else if (addr.isLoopbackAddress() || addr.isLinkLocalAddress()) {
+ /**
+ * RFC 4291 section 2.5.3 says loopback is to be treated as having
+ * link-local scope.
+ */
+ return IPV6_ADDR_SCOPE_LINKLOCAL;
+ } else if (addr.isSiteLocalAddress()) {
+ return IPV6_ADDR_SCOPE_SITELOCAL;
+ } else {
+ return IPV6_ADDR_SCOPE_GLOBAL;
+ }
+ } else if (isIpv4Address(addr)) {
+ if (addr.isLoopbackAddress() || addr.isLinkLocalAddress()) {
+ return IPV6_ADDR_SCOPE_LINKLOCAL;
+ } else {
+ /**
+ * RFC 6724 section 3.2. Other IPv4 addresses, including private addresses
+ * and shared addresses (100.64.0.0/10), are assigned global scope.
+ */
+ return IPV6_ADDR_SCOPE_GLOBAL;
+ }
+ } else {
+ /**
+ * This should never happen.
+ * Return a scope with low priority as a last resort.
+ */
+ return IPV6_ADDR_SCOPE_NODELOCAL;
+ }
+ }
+
+ /**
+ * Get the precedence for a given IPv4/IPv6 address.
+ * RFC 6724, section 2.1.
+ *
+ * Note that Java will return an IPv4-mapped address as an IPv4 address.
+ */
+ private static int findPrecedence(@NonNull InetAddress addr) {
+ if (isIpv4Address(addr)) {
+ return 35;
+ } else if (isIpv6Address(addr)) {
+ if (addr.isLoopbackAddress()) {
+ return 50;
+ } else if (isIpv6Address6To4(addr)) {
+ return 30;
+ } else if (isIpv6AddressTeredo(addr)) {
+ return 5;
+ } else if (isIpv6AddressULA(addr)) {
+ return 3;
+ } else if (((Inet6Address) addr).isIPv4CompatibleAddress() || addr.isSiteLocalAddress()
+ || isIpv6Address6Bone(addr)) {
+ return 1;
+ } else {
+ // All other IPv6 addresses, including global unicast addresses.
+ return 40;
+ }
+ } else {
+ return 1;
+ }
+ }
+
+ /**
+ * Find number of matching initial bits between the two addresses.
+ */
+ private static int compareIpv6PrefixMatchLen(@NonNull InetAddress srcAddr,
+ @NonNull InetAddress dstAddr) {
+ final byte[] srcByte = srcAddr.getAddress();
+ final byte[] dstByte = dstAddr.getAddress();
+
+ // This should never happen.
+ if (srcByte.length != dstByte.length) return 0;
+
+ for (int i = 0; i < dstByte.length; ++i) {
+ if (srcByte[i] == dstByte[i]) {
+ continue;
+ }
+ int x = BitUtils.uint8(srcByte[i]) ^ BitUtils.uint8(dstByte[i]);
+ return i * CHAR_BIT + (Integer.numberOfLeadingZeros(x) - 24); // Java ints are 32 bits
+ }
+ return dstByte.length * CHAR_BIT;
+ }
+
+ /**
+ * Check if given network has Ipv4 capability
+ * This function matches the behaviour of have_ipv4 in the native resolver.
+ */
+ public static boolean haveIpv4(@Nullable Network network) {
+ final SocketAddress addrIpv4 =
+ new InetSocketAddress(InetAddresses.parseNumericAddress("8.8.8.8"), 0);
+ return checkConnectivity(network, AF_INET, addrIpv4);
+ }
+
+ /**
+ * Check if given network has Ipv6 capability
+ * This function matches the behaviour of have_ipv6 in the native resolver.
+ */
+ public static boolean haveIpv6(@Nullable Network network) {
+ final SocketAddress addrIpv6 =
+ new InetSocketAddress(InetAddresses.parseNumericAddress("2000::"), 0);
+ return checkConnectivity(network, AF_INET6, addrIpv6);
+ }
+
+ private static boolean checkConnectivity(@Nullable Network network,
+ int domain, @NonNull SocketAddress addr) {
+ final FileDescriptor socket;
+ try {
+ socket = Os.socket(domain, SOCK_DGRAM, IPPROTO_UDP);
+ } catch (ErrnoException e) {
+ return false;
+ }
+ try {
+ if (network != null) network.bindSocket(socket);
+ Os.connect(socket, addr);
+ } catch (IOException | ErrnoException e) {
+ return false;
+ } finally {
+ IoUtils.closeQuietly(socket);
+ }
+ return true;
+ }
+}
diff --git a/android/net/util/InterfaceParams.java b/android/net/util/InterfaceParams.java
new file mode 100644
index 0000000..f6bb873
--- /dev/null
+++ b/android/net/util/InterfaceParams.java
@@ -0,0 +1,97 @@
+/*
+ * 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 android.net.util;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import android.net.MacAddress;
+import android.text.TextUtils;
+
+import java.net.NetworkInterface;
+import java.net.SocketException;
+
+
+/**
+ * Encapsulate the interface parameters common to IpClient/IpServer components.
+ *
+ * Basically all java.net.NetworkInterface methods throw Exceptions. IpClient
+ * and IpServer (sub)components need most or all of this information at some
+ * point during their lifecycles, so pass only this simplified object around
+ * which can be created once when IpClient/IpServer are told to start.
+ *
+ * @hide
+ */
+public class InterfaceParams {
+ public final String name;
+ public final int index;
+ public final MacAddress macAddr;
+ public final int defaultMtu;
+
+ // TODO: move the below to NetworkStackConstants when this class is moved to the NetworkStack.
+ private static final int ETHER_MTU = 1500;
+ private static final int IPV6_MIN_MTU = 1280;
+
+
+ public static InterfaceParams getByName(String name) {
+ final NetworkInterface netif = getNetworkInterfaceByName(name);
+ if (netif == null) return null;
+
+ // Not all interfaces have MAC addresses, e.g. rmnet_data0.
+ final MacAddress macAddr = getMacAddress(netif);
+
+ try {
+ return new InterfaceParams(name, netif.getIndex(), macAddr, netif.getMTU());
+ } catch (IllegalArgumentException|SocketException e) {
+ return null;
+ }
+ }
+
+ public InterfaceParams(String name, int index, MacAddress macAddr) {
+ this(name, index, macAddr, ETHER_MTU);
+ }
+
+ public InterfaceParams(String name, int index, MacAddress macAddr, int defaultMtu) {
+ checkArgument((!TextUtils.isEmpty(name)), "impossible interface name");
+ checkArgument((index > 0), "invalid interface index");
+ this.name = name;
+ this.index = index;
+ this.macAddr = (macAddr != null) ? macAddr : MacAddress.fromBytes(new byte[] {
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 });
+ this.defaultMtu = (defaultMtu > IPV6_MIN_MTU) ? defaultMtu : IPV6_MIN_MTU;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s/%d/%s/%d", name, index, macAddr, defaultMtu);
+ }
+
+ private static NetworkInterface getNetworkInterfaceByName(String name) {
+ try {
+ return NetworkInterface.getByName(name);
+ } catch (NullPointerException|SocketException e) {
+ return null;
+ }
+ }
+
+ private static MacAddress getMacAddress(NetworkInterface netif) {
+ try {
+ return MacAddress.fromBytes(netif.getHardwareAddress());
+ } catch (IllegalArgumentException|NullPointerException|SocketException e) {
+ return null;
+ }
+ }
+}
diff --git a/android/net/util/InterfaceSet.java b/android/net/util/InterfaceSet.java
new file mode 100644
index 0000000..9f26fa1
--- /dev/null
+++ b/android/net/util/InterfaceSet.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.util;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.StringJoiner;
+
+
+/**
+ * @hide
+ */
+public class InterfaceSet {
+ public final Set<String> ifnames;
+
+ public InterfaceSet(String... names) {
+ final Set<String> nameSet = new HashSet<>();
+ for (String name : names) {
+ if (name != null) nameSet.add(name);
+ }
+ ifnames = Collections.unmodifiableSet(nameSet);
+ }
+
+ @Override
+ public String toString() {
+ final StringJoiner sj = new StringJoiner(",", "[", "]");
+ for (String ifname : ifnames) sj.add(ifname);
+ return sj.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj != null
+ && obj instanceof InterfaceSet
+ && ifnames.equals(((InterfaceSet)obj).ifnames);
+ }
+}
diff --git a/android/net/util/IpUtils.java b/android/net/util/IpUtils.java
new file mode 100644
index 0000000..e037c40
--- /dev/null
+++ b/android/net/util/IpUtils.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2015 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.util;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.nio.BufferOverflowException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ShortBuffer;
+
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+/**
+ * @hide
+ */
+public class IpUtils {
+ /**
+ * Converts a signed short value to an unsigned int value. Needed
+ * because Java does not have unsigned types.
+ */
+ private static int intAbs(short v) {
+ return v & 0xFFFF;
+ }
+
+ /**
+ * Performs an IP checksum (used in IP header and across UDP
+ * payload) on the specified portion of a ByteBuffer. The seed
+ * allows the checksum to commence with a specified value.
+ */
+ private static int checksum(ByteBuffer buf, int seed, int start, int end) {
+ int sum = seed;
+ final int bufPosition = buf.position();
+
+ // set position of original ByteBuffer, so that the ShortBuffer
+ // will be correctly initialized
+ buf.position(start);
+ ShortBuffer shortBuf = buf.asShortBuffer();
+
+ // re-set ByteBuffer position
+ buf.position(bufPosition);
+
+ final int numShorts = (end - start) / 2;
+ for (int i = 0; i < numShorts; i++) {
+ sum += intAbs(shortBuf.get(i));
+ }
+ start += numShorts * 2;
+
+ // see if a singleton byte remains
+ if (end != start) {
+ short b = buf.get(start);
+
+ // make it unsigned
+ if (b < 0) {
+ b += 256;
+ }
+
+ sum += b * 256;
+ }
+
+ sum = ((sum >> 16) & 0xFFFF) + (sum & 0xFFFF);
+ sum = ((sum + ((sum >> 16) & 0xFFFF)) & 0xFFFF);
+ int negated = ~sum;
+ return intAbs((short) negated);
+ }
+
+ private static int pseudoChecksumIPv4(
+ ByteBuffer buf, int headerOffset, int protocol, int transportLen) {
+ int partial = protocol + transportLen;
+ partial += intAbs(buf.getShort(headerOffset + 12));
+ partial += intAbs(buf.getShort(headerOffset + 14));
+ partial += intAbs(buf.getShort(headerOffset + 16));
+ partial += intAbs(buf.getShort(headerOffset + 18));
+ return partial;
+ }
+
+ private static int pseudoChecksumIPv6(
+ ByteBuffer buf, int headerOffset, int protocol, int transportLen) {
+ int partial = protocol + transportLen;
+ for (int offset = 8; offset < 40; offset += 2) {
+ partial += intAbs(buf.getShort(headerOffset + offset));
+ }
+ return partial;
+ }
+
+ private static byte ipversion(ByteBuffer buf, int headerOffset) {
+ return (byte) ((buf.get(headerOffset) & (byte) 0xf0) >> 4);
+ }
+
+ public static short ipChecksum(ByteBuffer buf, int headerOffset) {
+ byte ihl = (byte) (buf.get(headerOffset) & 0x0f);
+ return (short) checksum(buf, 0, headerOffset, headerOffset + ihl * 4);
+ }
+
+ private static short transportChecksum(ByteBuffer buf, int protocol,
+ int ipOffset, int transportOffset, int transportLen) {
+ if (transportLen < 0) {
+ throw new IllegalArgumentException("Transport length < 0: " + transportLen);
+ }
+ int sum;
+ byte ver = ipversion(buf, ipOffset);
+ if (ver == 4) {
+ sum = pseudoChecksumIPv4(buf, ipOffset, protocol, transportLen);
+ } else if (ver == 6) {
+ sum = pseudoChecksumIPv6(buf, ipOffset, protocol, transportLen);
+ } else {
+ throw new UnsupportedOperationException("Checksum must be IPv4 or IPv6");
+ }
+
+ sum = checksum(buf, sum, transportOffset, transportOffset + transportLen);
+ if (protocol == IPPROTO_UDP && sum == 0) {
+ sum = (short) 0xffff;
+ }
+ return (short) sum;
+ }
+
+ public static short udpChecksum(ByteBuffer buf, int ipOffset, int transportOffset) {
+ int transportLen = intAbs(buf.getShort(transportOffset + 4));
+ return transportChecksum(buf, IPPROTO_UDP, ipOffset, transportOffset, transportLen);
+ }
+
+ public static short tcpChecksum(ByteBuffer buf, int ipOffset, int transportOffset,
+ int transportLen) {
+ return transportChecksum(buf, IPPROTO_TCP, ipOffset, transportOffset, transportLen);
+ }
+
+ public static String addressAndPortToString(InetAddress address, int port) {
+ return String.format(
+ (address instanceof Inet6Address) ? "[%s]:%d" : "%s:%d",
+ address.getHostAddress(), port);
+ }
+
+ public static boolean isValidUdpOrTcpPort(int port) {
+ return port > 0 && port < 65536;
+ }
+}
diff --git a/android/net/util/KeepalivePacketDataUtil.java b/android/net/util/KeepalivePacketDataUtil.java
new file mode 100644
index 0000000..9a51729
--- /dev/null
+++ b/android/net/util/KeepalivePacketDataUtil.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.util;
+
+import android.annotation.NonNull;
+import android.net.NattKeepalivePacketData;
+import android.net.NattKeepalivePacketDataParcelable;
+
+/** @hide */
+public final class KeepalivePacketDataUtil {
+ /**
+ * Convert this NattKeepalivePacketData to a NattKeepalivePacketDataParcelable.
+ */
+ @NonNull
+ public static NattKeepalivePacketDataParcelable toStableParcelable(
+ NattKeepalivePacketData pkt) {
+ final NattKeepalivePacketDataParcelable parcel = new NattKeepalivePacketDataParcelable();
+
+ parcel.srcAddress = pkt.srcAddress.getAddress();
+ parcel.srcPort = pkt.srcPort;
+ parcel.dstAddress = pkt.dstAddress.getAddress();
+ parcel.dstPort = pkt.dstPort;
+ return parcel;
+ }
+}
diff --git a/android/net/util/KeepaliveUtils.java b/android/net/util/KeepaliveUtils.java
new file mode 100644
index 0000000..bfc4563
--- /dev/null
+++ b/android/net/util/KeepaliveUtils.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.util;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.NetworkCapabilities;
+import android.text.TextUtils;
+import android.util.AndroidRuntimeException;
+
+import com.android.internal.R;
+
+/**
+ * Collection of utilities for socket keepalive offload.
+ *
+ * @hide
+ */
+public final class KeepaliveUtils {
+
+ public static final String TAG = "KeepaliveUtils";
+
+ public static class KeepaliveDeviceConfigurationException extends AndroidRuntimeException {
+ public KeepaliveDeviceConfigurationException(final String msg) {
+ super(msg);
+ }
+ }
+
+ /**
+ * Read supported keepalive count for each transport type from overlay resource. This should be
+ * used to create a local variable store of resource customization, and use it as the input for
+ * {@link getSupportedKeepalivesForNetworkCapabilities}.
+ *
+ * @param context The context to read resource from.
+ * @return An array of supported keepalive count for each transport type.
+ */
+ @NonNull
+ public static int[] getSupportedKeepalives(@NonNull Context context) {
+ String[] res = null;
+ try {
+ res = context.getResources().getStringArray(
+ R.array.config_networkSupportedKeepaliveCount);
+ } catch (Resources.NotFoundException unused) {
+ }
+ if (res == null) throw new KeepaliveDeviceConfigurationException("invalid resource");
+
+ final int[] ret = new int[NetworkCapabilities.MAX_TRANSPORT + 1];
+ for (final String row : res) {
+ if (TextUtils.isEmpty(row)) {
+ throw new KeepaliveDeviceConfigurationException("Empty string");
+ }
+ final String[] arr = row.split(",");
+ if (arr.length != 2) {
+ throw new KeepaliveDeviceConfigurationException("Invalid parameter length");
+ }
+
+ int transport;
+ int supported;
+ try {
+ transport = Integer.parseInt(arr[0]);
+ supported = Integer.parseInt(arr[1]);
+ } catch (NumberFormatException e) {
+ throw new KeepaliveDeviceConfigurationException("Invalid number format");
+ }
+
+ if (!NetworkCapabilities.isValidTransport(transport)) {
+ throw new KeepaliveDeviceConfigurationException("Invalid transport " + transport);
+ }
+
+ if (supported < 0) {
+ throw new KeepaliveDeviceConfigurationException(
+ "Invalid supported count " + supported + " for "
+ + NetworkCapabilities.transportNameOf(transport));
+ }
+ ret[transport] = supported;
+ }
+ return ret;
+ }
+
+ /**
+ * Get supported keepalive count for the given {@link NetworkCapabilities}.
+ *
+ * @param supportedKeepalives An array of supported keepalive count for each transport type.
+ * @param nc The {@link NetworkCapabilities} of the network the socket keepalive is on.
+ *
+ * @return Supported keepalive count for the given {@link NetworkCapabilities}.
+ */
+ public static int getSupportedKeepalivesForNetworkCapabilities(
+ @NonNull int[] supportedKeepalives, @NonNull NetworkCapabilities nc) {
+ final int[] transports = nc.getTransportTypes();
+ if (transports.length == 0) return 0;
+ int supportedCount = supportedKeepalives[transports[0]];
+ // Iterate through transports and return minimum supported value.
+ for (final int transport : transports) {
+ if (supportedCount > supportedKeepalives[transport]) {
+ supportedCount = supportedKeepalives[transport];
+ }
+ }
+ return supportedCount;
+ }
+}
diff --git a/android/net/util/MultinetworkPolicyTracker.java b/android/net/util/MultinetworkPolicyTracker.java
new file mode 100644
index 0000000..30c5cd9
--- /dev/null
+++ b/android/net/util/MultinetworkPolicyTracker.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2016 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.util;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.ConnectivityManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Slog;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.R;
+
+import static android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI;
+import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
+
+/**
+ * A class to encapsulate management of the "Smart Networking" capability of
+ * avoiding bad Wi-Fi when, for example upstream connectivity is lost or
+ * certain critical link failures occur.
+ *
+ * This enables the device to switch to another form of connectivity, like
+ * mobile, if it's available and working.
+ *
+ * The Runnable |avoidBadWifiCallback|, if given, is posted to the supplied
+ * Handler' whenever the computed "avoid bad wifi" value changes.
+ *
+ * Disabling this reverts the device to a level of networking sophistication
+ * circa 2012-13 by disabling disparate code paths each of which contribute to
+ * maintaining continuous, working Internet connectivity.
+ *
+ * @hide
+ */
+public class MultinetworkPolicyTracker {
+ private static String TAG = MultinetworkPolicyTracker.class.getSimpleName();
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final Runnable mReevaluateRunnable;
+ private final List<Uri> mSettingsUris;
+ private final ContentResolver mResolver;
+ private final SettingObserver mSettingObserver;
+ private final BroadcastReceiver mBroadcastReceiver;
+
+ private volatile boolean mAvoidBadWifi = true;
+ private volatile int mMeteredMultipathPreference;
+
+ public MultinetworkPolicyTracker(Context ctx, Handler handler) {
+ this(ctx, handler, null);
+ }
+
+ public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback) {
+ mContext = ctx;
+ mHandler = handler;
+ mReevaluateRunnable = () -> {
+ if (updateAvoidBadWifi() && avoidBadWifiCallback != null) {
+ avoidBadWifiCallback.run();
+ }
+ updateMeteredMultipathPreference();
+ };
+ mSettingsUris = Arrays.asList(
+ Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI),
+ Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE));
+ mResolver = mContext.getContentResolver();
+ mSettingObserver = new SettingObserver();
+ mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ reevaluate();
+ }
+ };
+
+ updateAvoidBadWifi();
+ updateMeteredMultipathPreference();
+ }
+
+ public void start() {
+ for (Uri uri : mSettingsUris) {
+ mResolver.registerContentObserver(uri, false, mSettingObserver);
+ }
+
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+ mContext.registerReceiverAsUser(
+ mBroadcastReceiver, UserHandle.ALL, intentFilter, null, null);
+
+ reevaluate();
+ }
+
+ public void shutdown() {
+ mResolver.unregisterContentObserver(mSettingObserver);
+
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ }
+
+ public boolean getAvoidBadWifi() {
+ return mAvoidBadWifi;
+ }
+
+ // TODO: move this to MultipathPolicyTracker.
+ public int getMeteredMultipathPreference() {
+ return mMeteredMultipathPreference;
+ }
+
+ /**
+ * Whether the device or carrier configuration disables avoiding bad wifi by default.
+ */
+ public boolean configRestrictsAvoidBadWifi() {
+ return (mContext.getResources().getInteger(R.integer.config_networkAvoidBadWifi) == 0);
+ }
+
+ /**
+ * Whether we should display a notification when wifi becomes unvalidated.
+ */
+ public boolean shouldNotifyWifiUnvalidated() {
+ return configRestrictsAvoidBadWifi() && getAvoidBadWifiSetting() == null;
+ }
+
+ public String getAvoidBadWifiSetting() {
+ return Settings.Global.getString(mResolver, NETWORK_AVOID_BAD_WIFI);
+ }
+
+ @VisibleForTesting
+ public void reevaluate() {
+ mHandler.post(mReevaluateRunnable);
+ }
+
+ public boolean updateAvoidBadWifi() {
+ final boolean settingAvoidBadWifi = "1".equals(getAvoidBadWifiSetting());
+ final boolean prev = mAvoidBadWifi;
+ mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi();
+ return mAvoidBadWifi != prev;
+ }
+
+ /**
+ * The default (device and carrier-dependent) value for metered multipath preference.
+ */
+ public int configMeteredMultipathPreference() {
+ return mContext.getResources().getInteger(
+ R.integer.config_networkMeteredMultipathPreference);
+ }
+
+ public void updateMeteredMultipathPreference() {
+ String setting = Settings.Global.getString(mResolver, NETWORK_METERED_MULTIPATH_PREFERENCE);
+ try {
+ mMeteredMultipathPreference = Integer.parseInt(setting);
+ } catch (NumberFormatException e) {
+ mMeteredMultipathPreference = configMeteredMultipathPreference();
+ }
+ }
+
+ private class SettingObserver extends ContentObserver {
+ public SettingObserver() {
+ super(null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ Slog.wtf(TAG, "Should never be reached.");
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (!mSettingsUris.contains(uri)) {
+ Slog.wtf(TAG, "Unexpected settings observation: " + uri);
+ }
+ reevaluate();
+ }
+ }
+}
diff --git a/android/net/util/NetdService.java b/android/net/util/NetdService.java
new file mode 100644
index 0000000..d4cd5bd
--- /dev/null
+++ b/android/net/util/NetdService.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2016 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.util;
+
+import android.content.Context;
+import android.net.INetd;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.util.Log;
+
+
+/**
+ * @hide
+ */
+public class NetdService {
+ private static final String TAG = NetdService.class.getSimpleName();
+ private static final long BASE_TIMEOUT_MS = 100;
+ private static final long MAX_TIMEOUT_MS = 1000;
+
+
+ /**
+ * Return an INetd instance, or null if not available.
+ *
+ * It is the caller's responsibility to check for a null return value
+ * and to handle RemoteException errors from invocations on the returned
+ * interface if, for example, netd dies and is restarted.
+ *
+ * Returned instances of INetd should not be cached.
+ *
+ * @return an INetd instance or null.
+ */
+ public static INetd getInstance() {
+ // NOTE: ServiceManager does no caching for the netd service,
+ // because netd is not one of the defined common services.
+ final INetd netdInstance = INetd.Stub.asInterface(
+ ServiceManager.getService(Context.NETD_SERVICE));
+ if (netdInstance == null) {
+ Log.w(TAG, "WARNING: returning null INetd instance.");
+ }
+ return netdInstance;
+ }
+
+ /**
+ * Blocks for a specified time until an INetd instance is available.
+ *
+ * It is the caller's responsibility to handle RemoteException errors
+ * from invocations on the returned interface if, for example, netd
+ * dies after this interface was returned.
+ *
+ * Returned instances of INetd should not be cached.
+ *
+ * Special values of maxTimeoutMs include: 0, meaning try to obtain an
+ * INetd instance only once, and -1 (or any value less than 0), meaning
+ * try to obtain an INetd instance indefinitely.
+ *
+ * @param maxTimeoutMs the maximum time to spend getting an INetd instance
+ * @return an INetd instance or null if no instance is available
+ * within |maxTimeoutMs| milliseconds.
+ */
+ public static INetd get(long maxTimeoutMs) {
+ if (maxTimeoutMs == 0) return getInstance();
+
+ final long stop = (maxTimeoutMs > 0)
+ ? SystemClock.elapsedRealtime() + maxTimeoutMs
+ : Long.MAX_VALUE;
+
+ long timeoutMs = 0;
+ while (true) {
+ final INetd netdInstance = getInstance();
+ if (netdInstance != null) {
+ return netdInstance;
+ }
+
+ final long remaining = stop - SystemClock.elapsedRealtime();
+ if (remaining <= 0) break;
+
+ // No netdInstance was received; sleep and retry.
+ timeoutMs = Math.min(timeoutMs + BASE_TIMEOUT_MS, MAX_TIMEOUT_MS);
+ timeoutMs = Math.min(timeoutMs, remaining);
+ try {
+ Thread.sleep(timeoutMs);
+ } catch (InterruptedException e) {}
+ }
+ return null;
+ }
+
+ /**
+ * Blocks until an INetd instance is available.
+ *
+ * It is the caller's responsibility to handle RemoteException errors
+ * from invocations on the returned interface if, for example, netd
+ * dies after this interface was returned.
+ *
+ * Returned instances of INetd should not be cached.
+ *
+ * @return an INetd instance.
+ */
+ public static INetd get() {
+ return get(-1);
+ }
+
+ public static interface NetdCommand {
+ void run(INetd netd) throws RemoteException;
+ }
+
+ /**
+ * Blocks until an INetd instance is availabe, and retries until either
+ * the command succeeds or a runtime exception is thrown.
+ */
+ public static void run(NetdCommand cmd) {
+ while (true) {
+ try {
+ cmd.run(get());
+ return;
+ } catch (RemoteException re) {
+ Log.e(TAG, "error communicating with netd: " + re);
+ }
+ }
+ }
+}
diff --git a/android/net/util/NetworkConstants.java b/android/net/util/NetworkConstants.java
new file mode 100644
index 0000000..ea5ce65
--- /dev/null
+++ b/android/net/util/NetworkConstants.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2016 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.util;
+
+/**
+ * Networking protocol constants.
+ *
+ * Includes:
+ * - constants that describe packet layout
+ * - various helper functions
+ *
+ * @hide
+ */
+public final class NetworkConstants {
+ private NetworkConstants() { throw new RuntimeException("no instance permitted"); }
+
+ public static final byte FF = asByte(0xff);
+ public static final byte[] ETHER_ADDR_BROADCAST = {
+ FF, FF, FF, FF, FF, FF
+ };
+
+ public static final int ETHER_MTU = 1500;
+
+ /**
+ * IPv4 constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc791
+ */
+ public static final int IPV4_ADDR_BITS = 32;
+
+ /**
+ * IPv6 constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc2460
+ */
+ public static final int IPV6_ADDR_BITS = 128;
+ public static final int IPV6_ADDR_LEN = 16;
+ public static final int IPV6_MIN_MTU = 1280;
+ public static final int RFC7421_PREFIX_LENGTH = 64;
+
+ /**
+ * ICMP common (v4/v6) constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc792
+ * - https://tools.ietf.org/html/rfc4443
+ */
+ public static final int ICMP_HEADER_TYPE_OFFSET = 0;
+ public static final int ICMP_HEADER_CODE_OFFSET = 1;
+ public static final int ICMP_HEADER_CHECKSUM_OFFSET = 2;
+ public static final int ICMP_ECHO_IDENTIFIER_OFFSET = 4;
+ public static final int ICMP_ECHO_SEQUENCE_NUMBER_OFFSET = 6;
+ public static final int ICMP_ECHO_DATA_OFFSET = 8;
+
+ /**
+ * ICMPv4 constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc792
+ */
+ public static final int ICMPV4_ECHO_REQUEST_TYPE = 8;
+ public static final int ICMPV6_ECHO_REQUEST_TYPE = 128;
+
+ /**
+ * DNS constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc1035
+ */
+ public static final int DNS_SERVER_PORT = 53;
+
+ /**
+ * Utility functions.
+ */
+ public static byte asByte(int i) { return (byte) i; }
+}
diff --git a/android/net/util/PrefixUtils.java b/android/net/util/PrefixUtils.java
new file mode 100644
index 0000000..f60694a
--- /dev/null
+++ b/android/net/util/PrefixUtils.java
@@ -0,0 +1,83 @@
+/*
+ * 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 android.net.util;
+
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+
+/**
+ * @hide
+ */
+public class PrefixUtils {
+ private static final IpPrefix[] MIN_NON_FORWARDABLE_PREFIXES = {
+ pfx("127.0.0.0/8"), // IPv4 loopback
+ pfx("169.254.0.0/16"), // IPv4 link-local, RFC3927#section-8
+ pfx("::/3"),
+ pfx("fe80::/64"), // IPv6 link-local
+ pfx("fc00::/7"), // IPv6 ULA
+ pfx("ff02::/8"), // IPv6 link-local multicast
+ };
+
+ public static final IpPrefix DEFAULT_WIFI_P2P_PREFIX = pfx("192.168.49.0/24");
+
+ public static Set<IpPrefix> getNonForwardablePrefixes() {
+ final HashSet<IpPrefix> prefixes = new HashSet<>();
+ addNonForwardablePrefixes(prefixes);
+ return prefixes;
+ }
+
+ public static void addNonForwardablePrefixes(Set<IpPrefix> prefixes) {
+ Collections.addAll(prefixes, MIN_NON_FORWARDABLE_PREFIXES);
+ }
+
+ public static Set<IpPrefix> localPrefixesFrom(LinkProperties lp) {
+ final HashSet<IpPrefix> localPrefixes = new HashSet<>();
+ if (lp == null) return localPrefixes;
+
+ for (LinkAddress addr : lp.getAllLinkAddresses()) {
+ if (addr.getAddress().isLinkLocalAddress()) continue;
+ localPrefixes.add(asIpPrefix(addr));
+ }
+ // TODO: Add directly-connected routes as well (ones from which we did
+ // not also form a LinkAddress)?
+
+ return localPrefixes;
+ }
+
+ public static IpPrefix asIpPrefix(LinkAddress addr) {
+ return new IpPrefix(addr.getAddress(), addr.getPrefixLength());
+ }
+
+ public static IpPrefix ipAddressAsPrefix(InetAddress ip) {
+ final int bitLength = (ip instanceof Inet4Address)
+ ? NetworkConstants.IPV4_ADDR_BITS
+ : NetworkConstants.IPV6_ADDR_BITS;
+ return new IpPrefix(ip, bitLength);
+ }
+
+ private static IpPrefix pfx(String prefixStr) {
+ return new IpPrefix(prefixStr);
+ }
+}
diff --git a/android/net/util/SharedLog.java b/android/net/util/SharedLog.java
new file mode 100644
index 0000000..2cdb2b0
--- /dev/null
+++ b/android/net/util/SharedLog.java
@@ -0,0 +1,202 @@
+/*
+ * 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 android.net.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.StringJoiner;
+
+
+/**
+ * Class to centralize logging functionality for tethering.
+ *
+ * All access to class methods other than dump() must be on the same thread.
+ *
+ * TODO: this is a copy of SharedLog in the NetworkStack. Remove after Tethering is migrated.
+ * @hide
+ */
+public class SharedLog {
+ private static final int DEFAULT_MAX_RECORDS = 500;
+ private static final String COMPONENT_DELIMITER = ".";
+
+ private enum Category {
+ NONE,
+ ERROR,
+ MARK,
+ WARN,
+ };
+
+ private final LocalLog mLocalLog;
+ // The tag to use for output to the system log. This is not output to the
+ // LocalLog because that would be redundant.
+ private final String mTag;
+ // The component (or subcomponent) of a system that is sharing this log.
+ // This can grow in depth if components call forSubComponent() to obtain
+ // their SharedLog instance. The tag is not included in the component for
+ // brevity.
+ private final String mComponent;
+
+ public SharedLog(String tag) {
+ this(DEFAULT_MAX_RECORDS, tag);
+ }
+
+ public SharedLog(int maxRecords, String tag) {
+ this(new LocalLog(maxRecords), tag, tag);
+ }
+
+ private SharedLog(LocalLog localLog, String tag, String component) {
+ mLocalLog = localLog;
+ mTag = tag;
+ mComponent = component;
+ }
+
+ public String getTag() {
+ return mTag;
+ }
+
+ /**
+ * Create a SharedLog based on this log with an additional component prefix on each logged line.
+ */
+ public SharedLog forSubComponent(String component) {
+ if (!isRootLogInstance()) {
+ component = mComponent + COMPONENT_DELIMITER + component;
+ }
+ return new SharedLog(mLocalLog, mTag, component);
+ }
+
+ /**
+ * Dump the contents of this log.
+ *
+ * <p>This method may be called on any thread.
+ */
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ mLocalLog.readOnlyLocalLog().dump(fd, writer, args);
+ }
+
+ //////
+ // Methods that both log an entry and emit it to the system log.
+ //////
+
+ /**
+ * Log an error due to an exception. This does not include the exception stacktrace.
+ *
+ * <p>The log entry will be also added to the system log.
+ * @see #e(String, Throwable)
+ */
+ public void e(Exception e) {
+ Log.e(mTag, record(Category.ERROR, e.toString()));
+ }
+
+ /**
+ * Log an error message.
+ *
+ * <p>The log entry will be also added to the system log.
+ */
+ public void e(String msg) {
+ Log.e(mTag, record(Category.ERROR, msg));
+ }
+
+ /**
+ * Log an error due to an exception, with the exception stacktrace if provided.
+ *
+ * <p>The error and exception message appear in the shared log, but the stacktrace is only
+ * logged in general log output (logcat). The log entry will be also added to the system log.
+ */
+ public void e(@NonNull String msg, @Nullable Throwable exception) {
+ if (exception == null) {
+ e(msg);
+ return;
+ }
+ Log.e(mTag, record(Category.ERROR, msg + ": " + exception.getMessage()), exception);
+ }
+
+ /**
+ * Log an informational message.
+ *
+ * <p>The log entry will be also added to the system log.
+ */
+ public void i(String msg) {
+ Log.i(mTag, record(Category.NONE, msg));
+ }
+
+ /**
+ * Log a warning message.
+ *
+ * <p>The log entry will be also added to the system log.
+ */
+ public void w(String msg) {
+ Log.w(mTag, record(Category.WARN, msg));
+ }
+
+ //////
+ // Methods that only log an entry (and do NOT emit to the system log).
+ //////
+
+ /**
+ * Log a general message to be only included in the in-memory log.
+ *
+ * <p>The log entry will *not* be added to the system log.
+ */
+ public void log(String msg) {
+ record(Category.NONE, msg);
+ }
+
+ /**
+ * Log a general, formatted message to be only included in the in-memory log.
+ *
+ * <p>The log entry will *not* be added to the system log.
+ * @see String#format(String, Object...)
+ */
+ public void logf(String fmt, Object... args) {
+ log(String.format(fmt, args));
+ }
+
+ /**
+ * Log a message with MARK level.
+ *
+ * <p>The log entry will *not* be added to the system log.
+ */
+ public void mark(String msg) {
+ record(Category.MARK, msg);
+ }
+
+ private String record(Category category, String msg) {
+ final String entry = logLine(category, msg);
+ mLocalLog.log(entry);
+ return entry;
+ }
+
+ private String logLine(Category category, String msg) {
+ final StringJoiner sj = new StringJoiner(" ");
+ if (!isRootLogInstance()) sj.add("[" + mComponent + "]");
+ if (category != Category.NONE) sj.add(category.toString());
+ return sj.add(msg).toString();
+ }
+
+ // Check whether this SharedLog instance is nominally the top level in
+ // a potential hierarchy of shared logs (the root of a tree),
+ // or is a subcomponent within the hierarchy.
+ private boolean isRootLogInstance() {
+ return TextUtils.isEmpty(mComponent) || mComponent.equals(mTag);
+ }
+}
diff --git a/android/net/util/SocketUtils.java b/android/net/util/SocketUtils.java
new file mode 100644
index 0000000..1364d8c
--- /dev/null
+++ b/android/net/util/SocketUtils.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2015 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.util;
+
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_BINDTODEVICE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.net.NetworkUtils;
+import android.system.ErrnoException;
+import android.system.NetlinkSocketAddress;
+import android.system.Os;
+import android.system.PacketSocketAddress;
+
+import libcore.io.IoBridge;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.SocketAddress;
+
+/**
+ * Collection of utilities to interact with raw sockets.
+ * @hide
+ */
+@SystemApi
+@TestApi
+public final class SocketUtils {
+ /**
+ * Create a raw datagram socket that is bound to an interface.
+ *
+ * <p>Data sent through the socket will go directly to the underlying network, ignoring VPNs.
+ */
+ public static void bindSocketToInterface(@NonNull FileDescriptor socket, @NonNull String iface)
+ throws ErrnoException {
+ // SO_BINDTODEVICE actually takes a string. This works because the first member
+ // of struct ifreq is a NULL-terminated interface name.
+ // TODO: add a setsockoptString()
+ Os.setsockoptIfreq(socket, SOL_SOCKET, SO_BINDTODEVICE, iface);
+ NetworkUtils.protectFromVpn(socket);
+ }
+
+ /**
+ * Make a socket address to communicate with netlink.
+ */
+ @NonNull
+ public static SocketAddress makeNetlinkSocketAddress(int portId, int groupsMask) {
+ return new NetlinkSocketAddress(portId, groupsMask);
+ }
+
+ /**
+ * Make socket address that packet sockets can bind to.
+ */
+ @NonNull
+ public static SocketAddress makePacketSocketAddress(int protocol, int ifIndex) {
+ return new PacketSocketAddress((short) protocol, ifIndex);
+ }
+
+ /**
+ * Make a socket address that packet socket can send packets to.
+ */
+ @NonNull
+ public static SocketAddress makePacketSocketAddress(int ifIndex, @NonNull byte[] hwAddr) {
+ return new PacketSocketAddress(ifIndex, hwAddr);
+ }
+
+ /**
+ * @see IoBridge#closeAndSignalBlockedThreads(FileDescriptor)
+ */
+ public static void closeSocket(@Nullable FileDescriptor fd) throws IOException {
+ IoBridge.closeAndSignalBlockedThreads(fd);
+ }
+
+ private SocketUtils() {}
+}
diff --git a/android/net/util/VersionedBroadcastListener.java b/android/net/util/VersionedBroadcastListener.java
new file mode 100644
index 0000000..107c404
--- /dev/null
+++ b/android/net/util/VersionedBroadcastListener.java
@@ -0,0 +1,109 @@
+/*
+ * 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 android.net.util;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.util.Log;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+
+/**
+ * A utility class that runs the provided callback on the provided handler when
+ * intents matching the provided filter arrive. Intents received by a stale
+ * receiver are safely ignored.
+ *
+ * Calls to startListening() and stopListening() must happen on the same thread.
+ *
+ * @hide
+ */
+public class VersionedBroadcastListener {
+ private static final boolean DBG = false;
+
+ public interface IntentCallback {
+ public void run(Intent intent);
+ }
+
+ private final String mTag;
+ private final Context mContext;
+ private final Handler mHandler;
+ private final IntentFilter mFilter;
+ private final Consumer<Intent> mCallback;
+ private final AtomicInteger mGenerationNumber;
+ private BroadcastReceiver mReceiver;
+
+ public VersionedBroadcastListener(String tag, Context ctx, Handler handler,
+ IntentFilter filter, Consumer<Intent> callback) {
+ mTag = tag;
+ mContext = ctx;
+ mHandler = handler;
+ mFilter = filter;
+ mCallback = callback;
+ mGenerationNumber = new AtomicInteger(0);
+ }
+
+ public void startListening() {
+ if (DBG) Log.d(mTag, "startListening");
+ if (mReceiver != null) return;
+
+ mReceiver = new Receiver(mTag, mGenerationNumber, mCallback);
+ mContext.registerReceiver(mReceiver, mFilter, null, mHandler);
+ }
+
+ public void stopListening() {
+ if (DBG) Log.d(mTag, "stopListening");
+ if (mReceiver == null) return;
+
+ mGenerationNumber.incrementAndGet();
+ mContext.unregisterReceiver(mReceiver);
+ mReceiver = null;
+ }
+
+ private static class Receiver extends BroadcastReceiver {
+ public final String tag;
+ public final AtomicInteger atomicGenerationNumber;
+ public final Consumer<Intent> callback;
+ // Used to verify this receiver is still current.
+ public final int generationNumber;
+
+ public Receiver(
+ String tag, AtomicInteger atomicGenerationNumber, Consumer<Intent> callback) {
+ this.tag = tag;
+ this.atomicGenerationNumber = atomicGenerationNumber;
+ this.callback = callback;
+ generationNumber = atomicGenerationNumber.incrementAndGet();
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int currentGenerationNumber = atomicGenerationNumber.get();
+
+ if (DBG) {
+ Log.d(tag, "receiver generationNumber=" + generationNumber +
+ ", current generationNumber=" + currentGenerationNumber);
+ }
+ if (generationNumber != currentGenerationNumber) return;
+
+ callback.accept(intent);
+ }
+ }
+}
diff --git a/android/net/wifi/AnqpInformationElement.java b/android/net/wifi/AnqpInformationElement.java
new file mode 100644
index 0000000..47b7129
--- /dev/null
+++ b/android/net/wifi/AnqpInformationElement.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 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;
+
+/**
+ * This object contains the payload of an ANQP element.
+ * Vendor id is the vendor ID for the element, or 0 if it is an 802.11(u) element.
+ * Hotspot 2.0 uses the WFA Vendor ID which is 0x506f9a
+ * The payload contains the bytes of the payload, starting after the length octet(s).
+ * @hide
+ */
+public class AnqpInformationElement {
+ public static final int HOTSPOT20_VENDOR_ID = 0x506f9a;
+
+ public static final int ANQP_QUERY_LIST = 256;
+ public static final int ANQP_CAPABILITY_LIST = 257;
+ public static final int ANQP_VENUE_NAME = 258;
+ public static final int ANQP_EMERGENCY_NUMBER = 259;
+ public static final int ANQP_NWK_AUTH_TYPE = 260;
+ public static final int ANQP_ROAMING_CONSORTIUM = 261;
+ public static final int ANQP_IP_ADDR_AVAILABILITY = 262;
+ public static final int ANQP_NAI_REALM = 263;
+ public static final int ANQP_3GPP_NETWORK = 264;
+ public static final int ANQP_GEO_LOC = 265;
+ public static final int ANQP_CIVIC_LOC = 266;
+ public static final int ANQP_LOC_URI = 267;
+ public static final int ANQP_DOM_NAME = 268;
+ public static final int ANQP_EMERGENCY_ALERT = 269;
+ public static final int ANQP_TDLS_CAP = 270;
+ public static final int ANQP_EMERGENCY_NAI = 271;
+ public static final int ANQP_NEIGHBOR_REPORT = 272;
+ public static final int ANQP_VENDOR_SPEC = 56797;
+
+ public static final int HS_QUERY_LIST = 1;
+ public static final int HS_CAPABILITY_LIST = 2;
+ public static final int HS_FRIENDLY_NAME = 3;
+ public static final int HS_WAN_METRICS = 4;
+ public static final int HS_CONN_CAPABILITY = 5;
+ public static final int HS_NAI_HOME_REALM_QUERY = 6;
+ public static final int HS_OPERATING_CLASS = 7;
+ public static final int HS_OSU_PROVIDERS = 8;
+ public static final int HS_ICON_REQUEST = 10;
+ public static final int HS_ICON_FILE = 11;
+
+ private final int mVendorId;
+ private final int mElementId;
+ private final byte[] mPayload;
+
+ public AnqpInformationElement(int vendorId, int elementId, byte[] payload) {
+ mVendorId = vendorId;
+ mElementId = elementId;
+ mPayload = payload;
+ }
+
+ public int getVendorId() {
+ return mVendorId;
+ }
+
+ public int getElementId() {
+ return mElementId;
+ }
+
+ public byte[] getPayload() {
+ return mPayload;
+ }
+}
diff --git a/android/net/wifi/BatchedScanResult.java b/android/net/wifi/BatchedScanResult.java
new file mode 100644
index 0000000..ed8845d
--- /dev/null
+++ b/android/net/wifi/BatchedScanResult.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.os.Parcelable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Describes the Results of a batched set of wifi scans where the firmware performs many
+ * scans and stores the timestamped results without waking the main processor each time.
+ * @hide
+ * @removed
+ */
+@Deprecated
+@SystemApi
+public class BatchedScanResult implements Parcelable {
+ private static final String TAG = "BatchedScanResult";
+
+ /** Inidcates this scan was interrupted and may only have partial results. */
+ public boolean truncated;
+
+ /** The result of this particular scan. */
+ public final List<ScanResult> scanResults = new ArrayList<ScanResult>();
+
+
+ public BatchedScanResult() {
+ }
+
+ public BatchedScanResult(BatchedScanResult source) {
+ truncated = source.truncated;
+ for (ScanResult s : source.scanResults) scanResults.add(new ScanResult(s));
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append("BatchedScanResult: ").
+ append("truncated: ").append(String.valueOf(truncated)).
+ append("scanResults: [");
+ for (ScanResult s : scanResults) {
+ sb.append(" <").append(s.toString()).append("> ");
+ }
+ sb.append(" ]");
+ return sb.toString();
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(truncated ? 1 : 0);
+ dest.writeInt(scanResults.size());
+ for (ScanResult s : scanResults) {
+ s.writeToParcel(dest, flags);
+ }
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final @android.annotation.NonNull Creator<BatchedScanResult> CREATOR =
+ new Creator<BatchedScanResult>() {
+ public BatchedScanResult createFromParcel(Parcel in) {
+ BatchedScanResult result = new BatchedScanResult();
+ result.truncated = (in.readInt() == 1);
+ int count = in.readInt();
+ while (count-- > 0) {
+ result.scanResults.add(ScanResult.CREATOR.createFromParcel(in));
+ }
+ return result;
+ }
+
+ public BatchedScanResult[] newArray(int size) {
+ return new BatchedScanResult[size];
+ }
+ };
+}
diff --git a/android/net/wifi/EAPConstants.java b/android/net/wifi/EAPConstants.java
new file mode 100644
index 0000000..b5f7c94
--- /dev/null
+++ b/android/net/wifi/EAPConstants.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2016, 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;
+
+/**
+ * Utility class containing EAP (Extensible Authentication Protocol) Related constants.
+ *
+ * @hide
+ */
+public final class EAPConstants {
+ // Constant definition for EAP types. Refer to
+ // http://www.iana.org/assignments/eap-numbers/eap-numbers.xhtml for more info.
+ public static final int EAP_MD5 = 4;
+ public static final int EAP_OTP = 5;
+ public static final int EAP_RSA = 9;
+ public static final int EAP_KEA = 11;
+ public static final int EAP_KEA_VALIDATE = 12;
+ public static final int EAP_TLS = 13;
+ public static final int EAP_LEAP = 17;
+ public static final int EAP_SIM = 18;
+ public static final int EAP_TTLS = 21;
+ public static final int EAP_AKA = 23;
+ public static final int EAP_3Com = 24;
+ public static final int EAP_MSCHAPv2 = 26;
+ public static final int EAP_PEAP = 29;
+ public static final int EAP_POTP = 32;
+ public static final int EAP_ActiontecWireless = 35;
+ public static final int EAP_HTTPDigest = 38;
+ public static final int EAP_SPEKE = 41;
+ public static final int EAP_MOBAC = 42;
+ public static final int EAP_FAST = 43;
+ public static final int EAP_ZLXEAP = 44;
+ public static final int EAP_Link = 45;
+ public static final int EAP_PAX = 46;
+ public static final int EAP_PSK = 47;
+ public static final int EAP_SAKE = 48;
+ public static final int EAP_IKEv2 = 49;
+ public static final int EAP_AKA_PRIME = 50;
+ public static final int EAP_GPSK = 51;
+ public static final int EAP_PWD = 52;
+ public static final int EAP_EKE = 53;
+ public static final int EAP_TEAP = 55;
+}
diff --git a/android/net/wifi/EasyConnectStatusCallback.java b/android/net/wifi/EasyConnectStatusCallback.java
new file mode 100644
index 0000000..b8c82fd
--- /dev/null
+++ b/android/net/wifi/EasyConnectStatusCallback.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.os.Handler;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Easy Connect (DPP) Status Callback. Use this callback to get status updates (success, failure,
+ * progress) from the Easy Connect operation started with
+ * {@link WifiManager#startEasyConnectAsConfiguratorInitiator(String,
+ * int, int, Handler, EasyConnectStatusCallback)} or
+ * {@link WifiManager#startEasyConnectAsEnrolleeInitiator(String,
+ * Handler, EasyConnectStatusCallback)}
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class EasyConnectStatusCallback {
+ /**
+ * Easy Connect Success event: Configuration sent (Configurator mode).
+ */
+ public static final int EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT = 0;
+
+ /** @hide */
+ @IntDef(prefix = {"EASY_CONNECT_EVENT_SUCCESS_"}, value = {
+ EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EasyConnectSuccessStatusCode {
+ }
+
+ /**
+ * Easy Connect Progress event: Initial authentication with peer succeeded.
+ */
+ public static final int EASY_CONNECT_EVENT_PROGRESS_AUTHENTICATION_SUCCESS = 0;
+
+ /**
+ * Easy Connect Progress event: Peer requires more time to process bootstrapping.
+ */
+ public static final int EASY_CONNECT_EVENT_PROGRESS_RESPONSE_PENDING = 1;
+
+ /** @hide */
+ @IntDef(prefix = {"EASY_CONNECT_EVENT_PROGRESS_"}, value = {
+ EASY_CONNECT_EVENT_PROGRESS_AUTHENTICATION_SUCCESS,
+ EASY_CONNECT_EVENT_PROGRESS_RESPONSE_PENDING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EasyConnectProgressStatusCode {
+ }
+
+ /**
+ * Easy Connect Failure event: Scanned QR code is either not a Easy Connect URI, or the Easy
+ * Connect URI has errors.
+ */
+ public static final int EASY_CONNECT_EVENT_FAILURE_INVALID_URI = -1;
+
+ /**
+ * Easy Connect Failure event: Bootstrapping/Authentication initialization process failure.
+ */
+ public static final int EASY_CONNECT_EVENT_FAILURE_AUTHENTICATION = -2;
+
+ /**
+ * Easy Connect Failure event: Both devices are implementing the same role and are incompatible.
+ */
+ public static final int EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE = -3;
+
+ /**
+ * Easy Connect Failure event: Configuration process has failed due to malformed message.
+ */
+ public static final int EASY_CONNECT_EVENT_FAILURE_CONFIGURATION = -4;
+
+ /**
+ * Easy Connect Failure event: Easy Connect request while in another Easy Connect exchange.
+ */
+ public static final int EASY_CONNECT_EVENT_FAILURE_BUSY = -5;
+
+ /**
+ * Easy Connect Failure event: No response from the peer.
+ */
+ public static final int EASY_CONNECT_EVENT_FAILURE_TIMEOUT = -6;
+
+ /**
+ * Easy Connect Failure event: General protocol failure.
+ */
+ public static final int EASY_CONNECT_EVENT_FAILURE_GENERIC = -7;
+
+ /**
+ * Easy Connect Failure event: Feature or option is not supported.
+ */
+ public static final int EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED = -8;
+
+ /**
+ * Easy Connect Failure event: Invalid network provided to Easy Connect configurator.
+ * Network must either be WPA3-Personal (SAE) or WPA2-Personal (PSK).
+ */
+ public static final int EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK = -9;
+
+
+ /** @hide */
+ @IntDef(prefix = {"EASY_CONNECT_EVENT_FAILURE_"}, value = {
+ EASY_CONNECT_EVENT_FAILURE_INVALID_URI,
+ EASY_CONNECT_EVENT_FAILURE_AUTHENTICATION,
+ EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE,
+ EASY_CONNECT_EVENT_FAILURE_CONFIGURATION,
+ EASY_CONNECT_EVENT_FAILURE_BUSY,
+ EASY_CONNECT_EVENT_FAILURE_TIMEOUT,
+ EASY_CONNECT_EVENT_FAILURE_GENERIC,
+ EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED,
+ EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EasyConnectFailureStatusCode {
+ }
+
+ /**
+ * Called when local Easy Connect Enrollee successfully receives a new Wi-Fi configuration from
+ * the
+ * peer Easy Connect configurator. This callback marks the successful end of the Easy Connect
+ * current Easy Connect
+ * session, and no further callbacks will be called. This callback is the successful outcome
+ * of a Easy Connect flow starting with
+ * {@link WifiManager#startEasyConnectAsEnrolleeInitiator(String,
+ * Handler,
+ * EasyConnectStatusCallback)}.
+ *
+ * @param newNetworkId New Wi-Fi configuration with a network ID received from the configurator
+ */
+ public abstract void onEnrolleeSuccess(int newNetworkId);
+
+ /**
+ * Called when a Easy Connect success event takes place, except for when configuration is
+ * received from
+ * an external Configurator. The callback onSuccessConfigReceived will be used in this case.
+ * This callback marks the successful end of the current Easy Connect session, and no further
+ * callbacks will be called. This callback is the successful outcome of a Easy Connect flow
+ * starting with
+ * {@link WifiManager#startEasyConnectAsConfiguratorInitiator(String, int, int, Handler,
+ * EasyConnectStatusCallback)}.
+ *
+ * @param code Easy Connect success status code.
+ */
+ public abstract void onConfiguratorSuccess(@EasyConnectSuccessStatusCode int code);
+
+ /**
+ * Called when a Easy Connect Failure event takes place. This callback marks the unsuccessful
+ * end of the
+ * current Easy Connect session, and no further callbacks will be called.
+ *
+ * @param code Easy Connect failure status code.
+ */
+ public abstract void onFailure(@EasyConnectFailureStatusCode int code);
+
+ /**
+ * Called when Easy Connect events that indicate progress take place. Can be used by UI elements
+ * to show progress.
+ *
+ * @param code Easy Connect progress status code.
+ */
+ public abstract void onProgress(@EasyConnectProgressStatusCode int code);
+}
diff --git a/android/net/wifi/ParcelUtil.java b/android/net/wifi/ParcelUtil.java
new file mode 100644
index 0000000..a26877d
--- /dev/null
+++ b/android/net/wifi/ParcelUtil.java
@@ -0,0 +1,165 @@
+/**
+ * Copyright (c) 2016, 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.os.Parcel;
+
+import java.io.ByteArrayInputStream;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+
+/**
+ * Provides utilities for writing/reading a non-Parcelable objects to/from
+ * a Parcel object.
+ *
+ * @hide
+ */
+public class ParcelUtil {
+ /**
+ * Write a PrivateKey object |key| to the specified Parcel |dest|.
+ *
+ * Below is the data format:
+ * |algorithm| -> String of algorithm name
+ * |endcodedKey| -> byte[] of key data
+ *
+ * For a null PrivateKey object, a null string will be written to |algorithm| and
+ * |encodedKey| will be skipped. Since a PrivateKey can only be constructed with
+ * a valid algorithm String.
+ *
+ * @param dest Parcel object to write to
+ * @param key PrivateKey object to read from.
+ */
+ public static void writePrivateKey(Parcel dest, PrivateKey key) {
+ if (key == null) {
+ dest.writeString(null);
+ return;
+ }
+
+ dest.writeString(key.getAlgorithm());
+ dest.writeByteArray(key.getEncoded());
+ }
+
+ /**
+ * Read/create a PrivateKey object from a specified Parcel object |in|.
+ *
+ * Refer to the function above for the expected data format.
+ *
+ * @param in Parcel object to read from
+ * @return a PrivateKey object or null
+ */
+ public static PrivateKey readPrivateKey(Parcel in) {
+ String algorithm = in.readString();
+ if (algorithm == null) {
+ return null;
+ }
+
+ byte[] userKeyBytes = in.createByteArray();
+ try {
+ KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
+ return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(userKeyBytes));
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Write a X509Certificate object |cert| to a Parcel object |dest|.
+ * The data being written to the Parcel is just a byte[] of the encoded certificate data.
+ *
+ * @param dest Parcel object to write to
+ * @param cert X509Certificate object to read from
+ */
+ public static void writeCertificate(Parcel dest, X509Certificate cert) {
+ byte[] certBytes = null;
+ if (cert != null) {
+ try {
+ certBytes = cert.getEncoded();
+ } catch (CertificateEncodingException e) {
+ /* empty, write null. */
+ }
+ }
+ dest.writeByteArray(certBytes);
+ }
+
+ /**
+ * Read/create a X509Certificate object from a specified Parcel object |in|.
+ *
+ * @param in Parcel object to read from
+ * @return a X509Certficate object or null
+ */
+ public static X509Certificate readCertificate(Parcel in) {
+ byte[] certBytes = in.createByteArray();
+ if (certBytes == null) {
+ return null;
+ }
+
+ try {
+ CertificateFactory cFactory = CertificateFactory.getInstance("X.509");
+ return (X509Certificate) cFactory
+ .generateCertificate(new ByteArrayInputStream(certBytes));
+ } catch (CertificateException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Write an array of X509Certificate objects |certs| to a Parcel object |dest|.
+ * The data being written to the Parcel are consist of an integer indicating
+ * the size of the array and the certificates data. Certificates data will be
+ * skipped for a null array or size of 0 array.
+ *
+ * @param dest Parcel object to write to
+ * @param certs array of X509Certificate objects to read from
+ */
+ public static void writeCertificates(Parcel dest, X509Certificate[] certs) {
+ if (certs == null || certs.length == 0) {
+ dest.writeInt(0);
+ return;
+ }
+
+ dest.writeInt(certs.length);
+ for (int i = 0; i < certs.length; i++) {
+ writeCertificate(dest, certs[i]);
+ }
+ }
+
+ /**
+ * Read/create an array of X509Certificate objects from a specified Parcel object |in|.
+ *
+ * @param in Parcel object to read from
+ * @return X509Certficate[] or null
+ */
+ public static X509Certificate[] readCertificates(Parcel in) {
+ int length = in.readInt();
+ if (length == 0) {
+ return null;
+ }
+
+ X509Certificate[] certs = new X509Certificate[length];
+ for (int i = 0; i < length; i++) {
+ certs[i] = readCertificate(in);
+ }
+ return certs;
+ }
+}
diff --git a/android/net/wifi/PasspointManagementObjectDefinition.java b/android/net/wifi/PasspointManagementObjectDefinition.java
new file mode 100644
index 0000000..70577b9
--- /dev/null
+++ b/android/net/wifi/PasspointManagementObjectDefinition.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2016 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This object describes a partial tree structure in the Hotspot 2.0 release 2 management object.
+ * The object is used during subscription remediation to modify parts of an existing PPS MO
+ * tree (Hotspot 2.0 specification section 9.1).
+ * @hide
+ */
+public class PasspointManagementObjectDefinition implements Parcelable {
+ private final String mBaseUri;
+ private final String mUrn;
+ private final String mMoTree;
+
+ public PasspointManagementObjectDefinition(String baseUri, String urn, String moTree) {
+ mBaseUri = baseUri;
+ mUrn = urn;
+ mMoTree = moTree;
+ }
+
+ public String getBaseUri() {
+ return mBaseUri;
+ }
+
+ public String getUrn() {
+ return mUrn;
+ }
+
+ public String getMoTree() {
+ return mMoTree;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mBaseUri);
+ dest.writeString(mUrn);
+ dest.writeString(mMoTree);
+ }
+
+ /**
+ * Implement the Parcelable interface {@hide}
+ */
+ public static final @android.annotation.NonNull Creator<PasspointManagementObjectDefinition> CREATOR =
+ new Creator<PasspointManagementObjectDefinition>() {
+ public PasspointManagementObjectDefinition createFromParcel(Parcel in) {
+ return new PasspointManagementObjectDefinition(
+ in.readString(), /* base URI */
+ in.readString(), /* URN */
+ in.readString() /* Tree XML */
+ );
+ }
+
+ public PasspointManagementObjectDefinition[] newArray(int size) {
+ return new PasspointManagementObjectDefinition[size];
+ }
+ };
+}
+
diff --git a/android/net/wifi/RssiPacketCountInfo.java b/android/net/wifi/RssiPacketCountInfo.java
new file mode 100644
index 0000000..4301165
--- /dev/null
+++ b/android/net/wifi/RssiPacketCountInfo.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2012 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Bundle of RSSI and packet count information, for WiFi watchdog
+ *
+ * @see WifiWatchdogStateMachine
+ *
+ * @hide
+ */
+public class RssiPacketCountInfo implements Parcelable {
+
+ public int rssi;
+
+ public int txgood;
+
+ public int txbad;
+
+ public int rxgood;
+
+ public RssiPacketCountInfo() {
+ rssi = txgood = txbad = rxgood = 0;
+ }
+
+ private RssiPacketCountInfo(Parcel in) {
+ rssi = in.readInt();
+ txgood = in.readInt();
+ txbad = in.readInt();
+ rxgood = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(rssi);
+ out.writeInt(txgood);
+ out.writeInt(txbad);
+ out.writeInt(rxgood);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<RssiPacketCountInfo> CREATOR =
+ new Parcelable.Creator<RssiPacketCountInfo>() {
+ @Override
+ public RssiPacketCountInfo createFromParcel(Parcel in) {
+ return new RssiPacketCountInfo(in);
+ }
+
+ @Override
+ public RssiPacketCountInfo[] newArray(int size) {
+ return new RssiPacketCountInfo[size];
+ }
+ };
+}
diff --git a/android/net/wifi/RttManager.java b/android/net/wifi/RttManager.java
new file mode 100644
index 0000000..6ce2121
--- /dev/null
+++ b/android/net/wifi/RttManager.java
@@ -0,0 +1,1231 @@
+package android.net.wifi;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.wifi.rtt.RangingRequest;
+import android.net.wifi.rtt.RangingResult;
+import android.net.wifi.rtt.RangingResultCallback;
+import android.net.wifi.rtt.WifiRttManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Protocol;
+
+import java.util.List;
+
+/** @hide */
+@SystemApi
+@Deprecated
+@SystemService(Context.WIFI_RTT_SERVICE)
+public class RttManager {
+
+ private static final boolean DBG = false;
+ private static final String TAG = "RttManager";
+
+ /** @deprecated It is Not supported anymore. */
+ @Deprecated
+ public static final int RTT_TYPE_UNSPECIFIED = 0;
+
+ public static final int RTT_TYPE_ONE_SIDED = 1;
+ public static final int RTT_TYPE_TWO_SIDED = 2;
+
+ /** @deprecated It is not supported anymore. */
+ @Deprecated
+ public static final int RTT_TYPE_11_V = 2;
+
+ /** @deprecated It is not supported anymore. */
+ @Deprecated
+ public static final int RTT_TYPE_11_MC = 4;
+
+ /** @deprecated It is not supported anymore. */
+ @Deprecated
+ public static final int RTT_PEER_TYPE_UNSPECIFIED = 0;
+
+ public static final int RTT_PEER_TYPE_AP = 1;
+ public static final int RTT_PEER_TYPE_STA = 2; /* requires NAN */
+ public static final int RTT_PEER_P2P_GO = 3;
+ public static final int RTT_PEER_P2P_CLIENT = 4;
+ public static final int RTT_PEER_NAN = 5;
+
+ /**
+ * @deprecated It is not supported anymore.
+ * Use {@link android.net.wifi.RttManager#RTT_BW_20_SUPPORT} API.
+ */
+ @Deprecated
+ public static final int RTT_CHANNEL_WIDTH_20 = 0;
+
+ /**
+ * @deprecated It is not supported anymore.
+ * Use {@link android.net.wifi.RttManager#RTT_BW_40_SUPPORT} API.
+ */
+ @Deprecated
+ public static final int RTT_CHANNEL_WIDTH_40 = 1;
+
+ /**
+ * @deprecated It is not supported anymore.
+ * Use {@link android.net.wifi.RttManager#RTT_BW_80_SUPPORT} API.
+ */
+ @Deprecated
+ public static final int RTT_CHANNEL_WIDTH_80 = 2;
+
+ /**@deprecated It is not supported anymore.
+ * Use {@link android.net.wifi.RttManager#RTT_BW_160_SUPPORT} API.
+ */
+ @Deprecated
+ public static final int RTT_CHANNEL_WIDTH_160 = 3;
+
+ /**@deprecated not supported anymore*/
+ @Deprecated
+ public static final int RTT_CHANNEL_WIDTH_80P80 = 4;
+
+ /**@deprecated It is not supported anymore.
+ * Use {@link android.net.wifi.RttManager#RTT_BW_5_SUPPORT} API.
+ */
+ @Deprecated
+ public static final int RTT_CHANNEL_WIDTH_5 = 5;
+
+ /**@deprecated It is not supported anymore.
+ * Use {@link android.net.wifi.RttManager#RTT_BW_10_SUPPORT} API.
+ */
+ @Deprecated
+ public static final int RTT_CHANNEL_WIDTH_10 = 6;
+
+ /** @deprecated channel info must be specified. */
+ @Deprecated
+ public static final int RTT_CHANNEL_WIDTH_UNSPECIFIED = -1;
+
+ public static final int RTT_STATUS_SUCCESS = 0;
+ /** General failure*/
+ public static final int RTT_STATUS_FAILURE = 1;
+ /** Destination does not respond to RTT request*/
+ public static final int RTT_STATUS_FAIL_NO_RSP = 2;
+ /** RTT request is rejected by the destination. Double side RTT only*/
+ public static final int RTT_STATUS_FAIL_REJECTED = 3;
+ /** */
+ public static final int RTT_STATUS_FAIL_NOT_SCHEDULED_YET = 4;
+ /** Timing measurement timeout*/
+ public static final int RTT_STATUS_FAIL_TM_TIMEOUT = 5;
+ /** Destination is on a different channel from the RTT Request*/
+ public static final int RTT_STATUS_FAIL_AP_ON_DIFF_CHANNEL = 6;
+ /** This type of Ranging is not support by Hardware*/
+ public static final int RTT_STATUS_FAIL_NO_CAPABILITY = 7;
+ /** Request abort fro uncertain reason*/
+ public static final int RTT_STATUS_ABORTED = 8;
+ /** The T1-T4 or TOD/TOA Timestamp is illegal*/
+ public static final int RTT_STATUS_FAIL_INVALID_TS = 9;
+ /** 11mc protocol level failed, eg, unrecognized FTMR/FTM frame*/
+ public static final int RTT_STATUS_FAIL_PROTOCOL = 10;
+ /** Request can not be scheduled by hardware*/
+ public static final int RTT_STATUS_FAIL_SCHEDULE = 11;
+ /** destination is busy now, you can try after a specified time from destination*/
+ public static final int RTT_STATUS_FAIL_BUSY_TRY_LATER = 12;
+ /** Bad Request argument*/
+ public static final int RTT_STATUS_INVALID_REQ = 13;
+ /** Wifi is not enabled*/
+ public static final int RTT_STATUS_NO_WIFI = 14;
+ /** Responder overrides param info, cannot range with new params 2-side RTT only*/
+ public static final int RTT_STATUS_FAIL_FTM_PARAM_OVERRIDE = 15;
+
+ public static final int REASON_UNSPECIFIED = -1;
+ public static final int REASON_NOT_AVAILABLE = -2;
+ public static final int REASON_INVALID_LISTENER = -3;
+ public static final int REASON_INVALID_REQUEST = -4;
+ /** Do not have required permission */
+ public static final int REASON_PERMISSION_DENIED = -5;
+ /** Ranging failed because responder role is enabled in STA mode.*/
+ public static final int
+ REASON_INITIATOR_NOT_ALLOWED_WHEN_RESPONDER_ON = -6;
+
+ public static final String DESCRIPTION_KEY = "android.net.wifi.RttManager.Description";
+
+ /**
+ * RTT BW supported bit mask, used as RTT param bandWidth too
+ */
+ public static final int RTT_BW_5_SUPPORT = 0x01;
+ public static final int RTT_BW_10_SUPPORT = 0x02;
+ public static final int RTT_BW_20_SUPPORT = 0x04;
+ public static final int RTT_BW_40_SUPPORT = 0x08;
+ public static final int RTT_BW_80_SUPPORT = 0x10;
+ public static final int RTT_BW_160_SUPPORT = 0x20;
+
+ /**
+ * RTT Preamble Support bit mask
+ */
+ public static final int PREAMBLE_LEGACY = 0x01;
+ public static final int PREAMBLE_HT = 0x02;
+ public static final int PREAMBLE_VHT = 0x04;
+
+ /** @deprecated Use the new {@link android.net.wifi.RttManager.RttCapabilities} API */
+ @Deprecated
+ public class Capabilities {
+ public int supportedType;
+ public int supportedPeerType;
+ }
+
+ /** @deprecated Use the new {@link android.net.wifi.RttManager#getRttCapabilities()} API.*/
+ @Deprecated
+ @SuppressLint("Doclava125")
+ public Capabilities getCapabilities() {
+ throw new UnsupportedOperationException(
+ "getCapabilities is not supported in the adaptation layer");
+ }
+
+ /**
+ * This class describe the RTT capability of the Hardware
+ */
+ @Deprecated
+ public static class RttCapabilities implements Parcelable {
+ /** @deprecated It is not supported*/
+ @Deprecated
+ public boolean supportedType;
+ /** @deprecated It is not supported*/
+ @Deprecated
+ public boolean supportedPeerType;
+ //1-sided rtt measurement is supported
+ public boolean oneSidedRttSupported;
+ //11mc 2-sided rtt measurement is supported
+ public boolean twoSided11McRttSupported;
+ //location configuration information supported
+ public boolean lciSupported;
+ //location civic records supported
+ public boolean lcrSupported;
+ //preamble supported, see bit mask definition above
+ public int preambleSupported;
+ //RTT bandwidth supported
+ public int bwSupported;
+ // Whether STA responder role is supported.
+ public boolean responderSupported;
+
+ /** Whether the secure RTT protocol is supported. */
+ public boolean secureRttSupported;
+
+ /** Draft 11mc version supported, including major and minor version. e.g, draft 4.3 is 43 */
+ public int mcVersion;
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("oneSidedRtt ").
+ append(oneSidedRttSupported ? "is Supported. " : "is not supported. ").
+ append("twoSided11McRtt ").
+ append(twoSided11McRttSupported ? "is Supported. " : "is not supported. ").
+ append("lci ").
+ append(lciSupported ? "is Supported. " : "is not supported. ").
+ append("lcr ").
+ append(lcrSupported ? "is Supported. " : "is not supported. ");
+
+ if ((preambleSupported & PREAMBLE_LEGACY) != 0) {
+ sb.append("Legacy ");
+ }
+
+ if ((preambleSupported & PREAMBLE_HT) != 0) {
+ sb.append("HT ");
+ }
+
+ if ((preambleSupported & PREAMBLE_VHT) != 0) {
+ sb.append("VHT ");
+ }
+
+ sb.append("is supported. ");
+
+ if ((bwSupported & RTT_BW_5_SUPPORT) != 0) {
+ sb.append("5 MHz ");
+ }
+
+ if ((bwSupported & RTT_BW_10_SUPPORT) != 0) {
+ sb.append("10 MHz ");
+ }
+
+ if ((bwSupported & RTT_BW_20_SUPPORT) != 0) {
+ sb.append("20 MHz ");
+ }
+
+ if ((bwSupported & RTT_BW_40_SUPPORT) != 0) {
+ sb.append("40 MHz ");
+ }
+
+ if ((bwSupported & RTT_BW_80_SUPPORT) != 0) {
+ sb.append("80 MHz ");
+ }
+
+ if ((bwSupported & RTT_BW_160_SUPPORT) != 0) {
+ sb.append("160 MHz ");
+ }
+
+ sb.append("is supported.");
+
+ sb.append(" STA responder role is ")
+ .append(responderSupported ? "supported" : "not supported");
+ sb.append(" Secure RTT protocol is ")
+ .append(secureRttSupported ? "supported" : "not supported");
+ sb.append(" 11mc version is " + mcVersion);
+
+ return sb.toString();
+ }
+ /** Implement the Parcelable interface {@hide} */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(oneSidedRttSupported ? 1 : 0);
+ dest.writeInt(twoSided11McRttSupported ? 1 : 0);
+ dest.writeInt(lciSupported ? 1 : 0);
+ dest.writeInt(lcrSupported ? 1 : 0);
+ dest.writeInt(preambleSupported);
+ dest.writeInt(bwSupported);
+ dest.writeInt(responderSupported ? 1 : 0);
+ dest.writeInt(secureRttSupported ? 1 : 0);
+ dest.writeInt(mcVersion);
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final @android.annotation.NonNull Creator<RttCapabilities> CREATOR =
+ new Creator<RttCapabilities>() {
+ @Override
+ public RttCapabilities createFromParcel(Parcel in) {
+ RttCapabilities capabilities = new RttCapabilities();
+ capabilities.oneSidedRttSupported = (in.readInt() == 1);
+ capabilities.twoSided11McRttSupported = (in.readInt() == 1);
+ capabilities.lciSupported = (in.readInt() == 1);
+ capabilities.lcrSupported = (in.readInt() == 1);
+ capabilities.preambleSupported = in.readInt();
+ capabilities.bwSupported = in.readInt();
+ capabilities.responderSupported = (in.readInt() == 1);
+ capabilities.secureRttSupported = (in.readInt() == 1);
+ capabilities.mcVersion = in.readInt();
+ return capabilities;
+ }
+ /** Implement the Parcelable interface {@hide} */
+ @Override
+ public RttCapabilities[] newArray(int size) {
+ return new RttCapabilities[size];
+ }
+ };
+ }
+
+ /**
+ * This method is deprecated. Please use the {@link WifiRttManager} API.
+ */
+ @RequiresPermission(Manifest.permission.LOCATION_HARDWARE)
+ public RttCapabilities getRttCapabilities() {
+ return mRttCapabilities;
+ }
+
+ /** specifies parameters for RTT request */
+ @Deprecated
+ public static class RttParams {
+ /**
+ * type of destination device being ranged
+ * currently only support RTT_PEER_TYPE_AP
+ * Range:RTT_PEER_TYPE_xxxx Default value:RTT_PEER_TYPE_AP
+ */
+ public int deviceType;
+
+ /**
+ * type of RTT measurement method. Need check scan result and RttCapabilities first
+ * Range: RTT_TYPE_ONE_SIDED or RTT_TYPE_TWO_SIDED
+ * Default value: RTT_TYPE_ONE_SIDED
+ */
+ public int requestType;
+
+ /**
+ * Whether the secure RTT protocol needs to be used for ranging this peer device.
+ */
+ public boolean secure;
+
+ /**
+ * mac address of the device being ranged
+ * Default value: null
+ */
+ public String bssid;
+
+ /**
+ * The primary control channel over which the client is
+ * communicating with the AP.Same as ScanResult.frequency
+ * Default value: 0
+ */
+ public int frequency;
+
+ /**
+ * channel width of the destination AP. Same as ScanResult.channelWidth
+ * Default value: 0
+ */
+ public int channelWidth;
+
+ /**
+ * Not used if the AP bandwidth is 20 MHz
+ * If the AP use 40, 80 or 160 MHz, this is the center frequency
+ * if the AP use 80 + 80 MHz, this is the center frequency of the first segment
+ * same as ScanResult.centerFreq0
+ * Default value: 0
+ */
+ public int centerFreq0;
+
+ /**
+ * Only used if the AP bandwidth is 80 + 80 MHz
+ * if the AP use 80 + 80 MHz, this is the center frequency of the second segment
+ * same as ScanResult.centerFreq1
+ * Default value: 0
+ */
+ public int centerFreq1;
+
+ /**
+ * number of samples to be taken
+ * @deprecated Use the new {@link android.net.wifi.RttManager.RttParams#numSamplesPerBurst}
+ */
+ @Deprecated
+ public int num_samples;
+
+ /**
+ * number of retries if a sample fails
+ * @deprecated
+ * Use {@link android.net.wifi.RttManager.RttParams#numRetriesPerMeasurementFrame} API.
+ */
+ @Deprecated
+ public int num_retries;
+
+ /** Number of burst in exp , 2^x. 0 means single shot measurement, range 0-15
+ * Currently only single shot is supported
+ * Default value: 0
+ */
+ public int numberBurst;
+
+ /**
+ * valid only if numberBurst > 1, interval between burst(100ms).
+ * Range : 0-31, 0--means no specific
+ * Default value: 0
+ */
+ public int interval;
+
+ /**
+ * number of samples to be taken in one burst
+ * Range: 1-31
+ * Default value: 8
+ */
+ public int numSamplesPerBurst;
+
+ /** number of retries for each measurement frame if a sample fails
+ * Only used by single side RTT,
+ * Range 0 - 3 Default value: 0
+ */
+ public int numRetriesPerMeasurementFrame;
+
+ /**
+ * number of retries for FTMR frame (control frame) if it fails.
+ * Only used by 80211MC double side RTT
+ * Range: 0-3 Default Value : 0
+ */
+ public int numRetriesPerFTMR;
+
+ /**
+ * Request LCI information, only available when choose double side RTT measurement
+ * need check RttCapabilties first.
+ * Default value: false
+ * */
+ public boolean LCIRequest;
+
+ /**
+ * Request LCR information, only available when choose double side RTT measurement
+ * need check RttCapabilties first.
+ * Default value: false
+ * */
+ public boolean LCRRequest;
+
+ /**
+ * Timeout for each burst, (250 * 2^x) us,
+ * Range 1-11 and 15. 15 means no control Default value: 15
+ * */
+ public int burstTimeout;
+
+ /** preamble used for RTT measurement
+ * Range: PREAMBLE_LEGACY, PREAMBLE_HT, PREAMBLE_VHT
+ * Default value: PREAMBLE_HT
+ */
+ public int preamble;
+
+ /** bandWidth used for RTT measurement.User need verify the highest BW the destination
+ * support (from scan result etc) before set this value. Wider channels result usually give
+ * better accuracy. However, the frame loss can increase too.
+ * should be one of RTT_BW_5_SUPPORT to RTT_BW_160_SUPPORT. However, need check
+ * RttCapabilities firstto verify HW support this bandwidth.
+ * Default value:RTT_BW_20_SUPPORT
+ */
+ public int bandwidth;
+
+ public RttParams() {
+ //provide initial value for RttParams
+ deviceType = RTT_PEER_TYPE_AP;
+ requestType = RTT_TYPE_ONE_SIDED;
+ numberBurst = 0;
+ numSamplesPerBurst = 8;
+ numRetriesPerMeasurementFrame = 0;
+ numRetriesPerFTMR = 0;
+ burstTimeout = 15;
+ preamble = PREAMBLE_HT;
+ bandwidth = RTT_BW_20_SUPPORT;
+ }
+
+ /**
+ * {@hide}
+ */
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("deviceType=" + deviceType);
+ sb.append(", requestType=" + requestType);
+ sb.append(", secure=" + secure);
+ sb.append(", bssid=" + bssid);
+ sb.append(", frequency=" + frequency);
+ sb.append(", channelWidth=" + channelWidth);
+ sb.append(", centerFreq0=" + centerFreq0);
+ sb.append(", centerFreq1=" + centerFreq1);
+ sb.append(", num_samples=" + num_samples);
+ sb.append(", num_retries=" + num_retries);
+ sb.append(", numberBurst=" + numberBurst);
+ sb.append(", interval=" + interval);
+ sb.append(", numSamplesPerBurst=" + numSamplesPerBurst);
+ sb.append(", numRetriesPerMeasurementFrame=" + numRetriesPerMeasurementFrame);
+ sb.append(", numRetriesPerFTMR=" + numRetriesPerFTMR);
+ sb.append(", LCIRequest=" + LCIRequest);
+ sb.append(", LCRRequest=" + LCRRequest);
+ sb.append(", burstTimeout=" + burstTimeout);
+ sb.append(", preamble=" + preamble);
+ sb.append(", bandwidth=" + bandwidth);
+ return sb.toString();
+ }
+ }
+
+ /** pseudo-private class used to parcel arguments */
+ @Deprecated
+ public static class ParcelableRttParams implements Parcelable {
+
+ @NonNull
+ public RttParams mParams[];
+
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public ParcelableRttParams(RttParams[] params) {
+ mParams = (params == null ? new RttParams[0] : params);
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mParams.length);
+
+ for (RttParams params : mParams) {
+ dest.writeInt(params.deviceType);
+ dest.writeInt(params.requestType);
+ dest.writeByte(params.secure ? (byte) 1 : 0);
+ dest.writeString(params.bssid);
+ dest.writeInt(params.channelWidth);
+ dest.writeInt(params.frequency);
+ dest.writeInt(params.centerFreq0);
+ dest.writeInt(params.centerFreq1);
+ dest.writeInt(params.numberBurst);
+ dest.writeInt(params.interval);
+ dest.writeInt(params.numSamplesPerBurst);
+ dest.writeInt(params.numRetriesPerMeasurementFrame);
+ dest.writeInt(params.numRetriesPerFTMR);
+ dest.writeInt(params.LCIRequest ? 1 : 0);
+ dest.writeInt(params.LCRRequest ? 1 : 0);
+ dest.writeInt(params.burstTimeout);
+ dest.writeInt(params.preamble);
+ dest.writeInt(params.bandwidth);
+ }
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final @android.annotation.NonNull Creator<ParcelableRttParams> CREATOR =
+ new Creator<ParcelableRttParams>() {
+ @Override
+ public ParcelableRttParams createFromParcel(Parcel in) {
+
+ int num = in.readInt();
+ RttParams params[] = new RttParams[num];
+ for (int i = 0; i < num; i++) {
+ params[i] = new RttParams();
+ params[i].deviceType = in.readInt();
+ params[i].requestType = in.readInt();
+ params[i].secure = (in.readByte() != 0);
+ params[i].bssid = in.readString();
+ params[i].channelWidth = in.readInt();
+ params[i].frequency = in.readInt();
+ params[i].centerFreq0 = in.readInt();
+ params[i].centerFreq1 = in.readInt();
+ params[i].numberBurst = in.readInt();
+ params[i].interval = in.readInt();
+ params[i].numSamplesPerBurst = in.readInt();
+ params[i].numRetriesPerMeasurementFrame = in.readInt();
+ params[i].numRetriesPerFTMR = in.readInt();
+ params[i].LCIRequest = (in.readInt() == 1);
+ params[i].LCRRequest = (in.readInt() == 1);
+ params[i].burstTimeout = in.readInt();
+ params[i].preamble = in.readInt();
+ params[i].bandwidth = in.readInt();
+ }
+
+ ParcelableRttParams parcelableParams = new ParcelableRttParams(params);
+ return parcelableParams;
+ }
+
+ @Override
+ public ParcelableRttParams[] newArray(int size) {
+ return new ParcelableRttParams[size];
+ }
+ };
+ }
+
+ @Deprecated
+ public static class WifiInformationElement {
+ /** Information Element ID 0xFF means element is invalid. */
+ public byte id;
+ public byte[] data;
+ }
+ /** specifies RTT results */
+ @Deprecated
+ public static class RttResult {
+ /** mac address of the device being ranged. */
+ public String bssid;
+
+ /** # of burst for this measurement. */
+ public int burstNumber;
+
+ /** total number of measurement frames attempted in this measurement. */
+ public int measurementFrameNumber;
+
+ /** total successful number of measurement frames in this measurement. */
+ public int successMeasurementFrameNumber;
+
+ /**
+ * Maximum number of frames per burst supported by peer. Two side RTT only
+ * Valid only if less than request
+ */
+ public int frameNumberPerBurstPeer;
+
+ /** status of the request */
+ public int status;
+
+ /**
+ * type of the request used
+ * @deprecated Use {@link android.net.wifi.RttManager.RttResult#measurementType}
+ */
+ @Deprecated
+ public int requestType;
+
+ /** RTT measurement method type used, should be one of RTT_TYPE_ONE_SIDED or
+ * RTT_TYPE_TWO_SIDED.
+ */
+ public int measurementType;
+
+ /**
+ * only valid when status == RTT_STATUS_FAIL_BUSY_TRY_LATER
+ * please retry RTT measurement after this duration since peer indicate busy at ths moment
+ * Unit S Range:1-31
+ */
+ public int retryAfterDuration;
+
+ /** timestamp of completion, in microsecond since boot. */
+ public long ts;
+
+ /** average RSSI observed, unit of 0.5 dB. */
+ public int rssi;
+
+ /**
+ * RSSI spread (i.e. max - min)
+ * @deprecated Use {@link android.net.wifi.RttManager.RttResult#rssiSpread} API.
+ */
+ @Deprecated
+ public int rssi_spread;
+
+ /**RSSI spread (i.e. max - min), unit of 0.5 dB. */
+ public int rssiSpread;
+
+ /**
+ * average transmit rate
+ * @deprecated Use {@link android.net.wifi.RttManager.RttResult#txRate} API.
+ */
+ @Deprecated
+ public int tx_rate;
+
+ /** average transmit rate. Unit (kbps). */
+ public int txRate;
+
+ /** average receiving rate Unit (kbps). */
+ public int rxRate;
+
+ /**
+ * average round trip time in nano second
+ * @deprecated Use {@link android.net.wifi.RttManager.RttResult#rtt} API.
+ */
+ @Deprecated
+ public long rtt_ns;
+
+ /** average round trip time in picoseconds. */
+ public long rtt;
+
+ /**
+ * standard deviation observed in round trip time
+ * @deprecated Use {@link android.net.wifi.RttManager.RttResult#rttStandardDeviation} API.
+ */
+ @Deprecated
+ public long rtt_sd_ns;
+
+ /** standard deviation of RTT in picoseconds. */
+ public long rttStandardDeviation;
+
+ /**
+ * spread (i.e. max - min) round trip time
+ * @deprecated Use {@link android.net.wifi.RttManager.RttResult#rttSpread} API.
+ */
+ @Deprecated
+ public long rtt_spread_ns;
+
+ /** spread (i.e. max - min) RTT in picoseconds. */
+ public long rttSpread;
+
+ /**
+ * average distance in centimeter, computed based on rtt_ns
+ * @deprecated use {@link android.net.wifi.RttManager.RttResult#distance} API.
+ */
+ @Deprecated
+ public int distance_cm;
+
+ /** average distance in cm, computed based on rtt. */
+ public int distance;
+
+ /**
+ * standard deviation observed in distance
+ * @deprecated
+ * Use {@link .android.net.wifi.RttManager.RttResult#distanceStandardDeviation} API.
+ */
+ @Deprecated
+ public int distance_sd_cm;
+
+ /** standard deviation observed in distance in cm. */
+ public int distanceStandardDeviation;
+
+ /**
+ * spread (i.e. max - min) distance
+ * @deprecated Use {@link android.net.wifi.RttManager.RttResult#distanceSpread} API.
+ */
+ @Deprecated
+ public int distance_spread_cm;
+
+ /** spread (i.e. max - min) distance in cm. */
+ public int distanceSpread;
+
+ /** the duration of this measurement burst, unit ms. */
+ public int burstDuration;
+
+ /** Burst number supported by peer after negotiation, 2side RTT only*/
+ public int negotiatedBurstNum;
+
+ /** LCI information Element, only available for double side RTT. */
+ public WifiInformationElement LCI;
+
+ /** LCR information Element, only available to double side RTT. */
+ public WifiInformationElement LCR;
+
+ /**
+ * Whether the secure RTT protocol was used for ranging.
+ */
+ public boolean secure;
+ }
+
+
+ /** pseudo-private class used to parcel results. */
+ @Deprecated
+ public static class ParcelableRttResults implements Parcelable {
+
+ public RttResult mResults[];
+
+ public ParcelableRttResults(RttResult[] results) {
+ mResults = results;
+ }
+
+ /**
+ * {@hide}
+ */
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < mResults.length; ++i) {
+ sb.append("[" + i + "]: ");
+ sb.append("bssid=" + mResults[i].bssid);
+ sb.append(", burstNumber=" + mResults[i].burstNumber);
+ sb.append(", measurementFrameNumber=" + mResults[i].measurementFrameNumber);
+ sb.append(", successMeasurementFrameNumber="
+ + mResults[i].successMeasurementFrameNumber);
+ sb.append(", frameNumberPerBurstPeer=" + mResults[i].frameNumberPerBurstPeer);
+ sb.append(", status=" + mResults[i].status);
+ sb.append(", requestType=" + mResults[i].requestType);
+ sb.append(", measurementType=" + mResults[i].measurementType);
+ sb.append(", retryAfterDuration=" + mResults[i].retryAfterDuration);
+ sb.append(", ts=" + mResults[i].ts);
+ sb.append(", rssi=" + mResults[i].rssi);
+ sb.append(", rssi_spread=" + mResults[i].rssi_spread);
+ sb.append(", rssiSpread=" + mResults[i].rssiSpread);
+ sb.append(", tx_rate=" + mResults[i].tx_rate);
+ sb.append(", txRate=" + mResults[i].txRate);
+ sb.append(", rxRate=" + mResults[i].rxRate);
+ sb.append(", rtt_ns=" + mResults[i].rtt_ns);
+ sb.append(", rtt=" + mResults[i].rtt);
+ sb.append(", rtt_sd_ns=" + mResults[i].rtt_sd_ns);
+ sb.append(", rttStandardDeviation=" + mResults[i].rttStandardDeviation);
+ sb.append(", rtt_spread_ns=" + mResults[i].rtt_spread_ns);
+ sb.append(", rttSpread=" + mResults[i].rttSpread);
+ sb.append(", distance_cm=" + mResults[i].distance_cm);
+ sb.append(", distance=" + mResults[i].distance);
+ sb.append(", distance_sd_cm=" + mResults[i].distance_sd_cm);
+ sb.append(", distanceStandardDeviation=" + mResults[i].distanceStandardDeviation);
+ sb.append(", distance_spread_cm=" + mResults[i].distance_spread_cm);
+ sb.append(", distanceSpread=" + mResults[i].distanceSpread);
+ sb.append(", burstDuration=" + mResults[i].burstDuration);
+ sb.append(", negotiatedBurstNum=" + mResults[i].negotiatedBurstNum);
+ sb.append(", LCI=" + mResults[i].LCI);
+ sb.append(", LCR=" + mResults[i].LCR);
+ sb.append(", secure=" + mResults[i].secure);
+ }
+ return sb.toString();
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (mResults != null) {
+ dest.writeInt(mResults.length);
+ for (RttResult result : mResults) {
+ dest.writeString(result.bssid);
+ dest.writeInt(result.burstNumber);
+ dest.writeInt(result.measurementFrameNumber);
+ dest.writeInt(result.successMeasurementFrameNumber);
+ dest.writeInt(result.frameNumberPerBurstPeer);
+ dest.writeInt(result.status);
+ dest.writeInt(result.measurementType);
+ dest.writeInt(result.retryAfterDuration);
+ dest.writeLong(result.ts);
+ dest.writeInt(result.rssi);
+ dest.writeInt(result.rssiSpread);
+ dest.writeInt(result.txRate);
+ dest.writeLong(result.rtt);
+ dest.writeLong(result.rttStandardDeviation);
+ dest.writeLong(result.rttSpread);
+ dest.writeInt(result.distance);
+ dest.writeInt(result.distanceStandardDeviation);
+ dest.writeInt(result.distanceSpread);
+ dest.writeInt(result.burstDuration);
+ dest.writeInt(result.negotiatedBurstNum);
+ dest.writeByte(result.LCI.id);
+ if (result.LCI.id != (byte) 0xFF) {
+ dest.writeByte((byte)result.LCI.data.length);
+ dest.writeByteArray(result.LCI.data);
+ }
+ dest.writeByte(result.LCR.id);
+ if (result.LCR.id != (byte) 0xFF) {
+ dest.writeByte((byte) result.LCR.data.length);
+ dest.writeByteArray(result.LCR.data);
+ }
+ dest.writeByte(result.secure ? (byte) 1 : 0);
+ }
+ } else {
+ dest.writeInt(0);
+ }
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final @android.annotation.NonNull Creator<ParcelableRttResults> CREATOR =
+ new Creator<ParcelableRttResults>() {
+ @Override
+ public ParcelableRttResults createFromParcel(Parcel in) {
+
+ int num = in.readInt();
+
+ if (num == 0) {
+ return new ParcelableRttResults(null);
+ }
+
+ RttResult results[] = new RttResult[num];
+ for (int i = 0; i < num; i++) {
+ results[i] = new RttResult();
+ results[i].bssid = in.readString();
+ results[i].burstNumber = in.readInt();
+ results[i].measurementFrameNumber = in.readInt();
+ results[i].successMeasurementFrameNumber = in.readInt();
+ results[i].frameNumberPerBurstPeer = in.readInt();
+ results[i].status = in.readInt();
+ results[i].measurementType = in.readInt();
+ results[i].retryAfterDuration = in.readInt();
+ results[i].ts = in.readLong();
+ results[i].rssi = in.readInt();
+ results[i].rssiSpread = in.readInt();
+ results[i].txRate = in.readInt();
+ results[i].rtt = in.readLong();
+ results[i].rttStandardDeviation = in.readLong();
+ results[i].rttSpread = in.readLong();
+ results[i].distance = in.readInt();
+ results[i].distanceStandardDeviation = in.readInt();
+ results[i].distanceSpread = in.readInt();
+ results[i].burstDuration = in.readInt();
+ results[i].negotiatedBurstNum = in.readInt();
+ results[i].LCI = new WifiInformationElement();
+ results[i].LCI.id = in.readByte();
+ if (results[i].LCI.id != (byte) 0xFF) {
+ byte length = in.readByte();
+ results[i].LCI.data = new byte[length];
+ in.readByteArray(results[i].LCI.data);
+ }
+ results[i].LCR = new WifiInformationElement();
+ results[i].LCR.id = in.readByte();
+ if (results[i].LCR.id != (byte) 0xFF) {
+ byte length = in.readByte();
+ results[i].LCR.data = new byte[length];
+ in.readByteArray(results[i].LCR.data);
+ }
+ results[i].secure = (in.readByte() != 0);
+ }
+
+ ParcelableRttResults parcelableResults = new ParcelableRttResults(results);
+ return parcelableResults;
+ }
+
+ @Override
+ public ParcelableRttResults[] newArray(int size) {
+ return new ParcelableRttResults[size];
+ }
+ };
+ }
+
+ @Deprecated
+ public static interface RttListener {
+ public void onSuccess(RttResult[] results);
+ public void onFailure(int reason, String description);
+ public void onAborted();
+ }
+
+ /**
+ * Request to start an RTT ranging
+ * <p>
+ * This method is deprecated. Please use the
+ * {@link WifiRttManager#startRanging(RangingRequest, java.util.concurrent.Executor, RangingResultCallback)}
+ * API.
+ *
+ * @param params -- RTT request Parameters
+ * @param listener -- Call back to inform RTT result
+ * @exception throw IllegalArgumentException when params are illegal
+ * throw IllegalStateException when RttCapabilities do not exist
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public void startRanging(RttParams[] params, RttListener listener) {
+ Log.i(TAG, "Send RTT request to RTT Service");
+
+ if (!mNewService.isAvailable()) {
+ listener.onFailure(REASON_NOT_AVAILABLE, "");
+ return;
+ }
+
+ RangingRequest.Builder builder = new RangingRequest.Builder();
+ for (RttParams rttParams : params) {
+ if (rttParams.deviceType != RTT_PEER_TYPE_AP) {
+ listener.onFailure(REASON_INVALID_REQUEST, "Only AP peers are supported");
+ return;
+ }
+
+ ScanResult reconstructed = new ScanResult();
+ reconstructed.BSSID = rttParams.bssid;
+ if (rttParams.requestType == RTT_TYPE_TWO_SIDED) {
+ reconstructed.setFlag(ScanResult.FLAG_80211mc_RESPONDER);
+ }
+ reconstructed.channelWidth = rttParams.channelWidth;
+ reconstructed.frequency = rttParams.frequency;
+ reconstructed.centerFreq0 = rttParams.centerFreq0;
+ reconstructed.centerFreq1 = rttParams.centerFreq1;
+ builder.addResponder(
+ android.net.wifi.rtt.ResponderConfig.fromScanResult(reconstructed));
+ }
+ try {
+ mNewService.startRanging(builder.build(),
+ mContext.getMainExecutor(),
+ new RangingResultCallback() {
+ @Override
+ public void onRangingFailure(int code) {
+ int localCode = REASON_UNSPECIFIED;
+ if (code == STATUS_CODE_FAIL_RTT_NOT_AVAILABLE) {
+ localCode = REASON_NOT_AVAILABLE;
+ }
+ listener.onFailure(localCode, "");
+ }
+
+ @Override
+ public void onRangingResults(List<RangingResult> results) {
+ RttResult[] legacyResults = new RttResult[results.size()];
+ int i = 0;
+ for (RangingResult result : results) {
+ legacyResults[i] = new RttResult();
+ legacyResults[i].status = result.getStatus();
+ legacyResults[i].bssid = result.getMacAddress().toString();
+ if (result.getStatus() == RangingResult.STATUS_SUCCESS) {
+ legacyResults[i].distance = result.getDistanceMm() / 10;
+ legacyResults[i].distanceStandardDeviation =
+ result.getDistanceStdDevMm() / 10;
+ legacyResults[i].rssi = result.getRssi() * -2;
+ legacyResults[i].ts = result.getRangingTimestampMillis() * 1000;
+ legacyResults[i].measurementFrameNumber =
+ result.getNumAttemptedMeasurements();
+ legacyResults[i].successMeasurementFrameNumber =
+ result.getNumSuccessfulMeasurements();
+ } else {
+ // just in case legacy API needed some relatively real timestamp
+ legacyResults[i].ts = SystemClock.elapsedRealtime() * 1000;
+ }
+ i++;
+ }
+ listener.onSuccess(legacyResults);
+ }
+ });
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "startRanging: invalid arguments - " + e);
+ listener.onFailure(REASON_INVALID_REQUEST, e.getMessage());
+ } catch (SecurityException e) {
+ Log.e(TAG, "startRanging: security exception - " + e);
+ listener.onFailure(REASON_PERMISSION_DENIED, e.getMessage());
+ }
+ }
+
+ /**
+ * This method is deprecated and performs no function. Please use the {@link WifiRttManager}
+ * API.
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public void stopRanging(RttListener listener) {
+ Log.e(TAG, "stopRanging: unsupported operation - nop");
+ }
+
+ /**
+ * Callbacks for responder operations.
+ * <p>
+ * A {@link ResponderCallback} is the handle to the calling client. {@link RttManager} will keep
+ * a reference to the callback for the entire period when responder is enabled. The same
+ * callback as used in enabling responder needs to be passed for disabling responder.
+ * The client can freely destroy or reuse the callback after {@link RttManager#disableResponder}
+ * is called.
+ */
+ @Deprecated
+ public abstract static class ResponderCallback {
+ /** Callback when responder is enabled. */
+ public abstract void onResponderEnabled(ResponderConfig config);
+ /** Callback when enabling responder failed. */
+ public abstract void onResponderEnableFailure(int reason);
+ // TODO: consider adding onResponderAborted once it's supported.
+ }
+
+ /**
+ * Enable Wi-Fi RTT responder mode on the device. The enabling result will be delivered via
+ * {@code callback}.
+ * <p>
+ * Note calling this method with the same callback when the responder is already enabled won't
+ * change the responder state, a cached {@link ResponderConfig} from the last enabling will be
+ * returned through the callback.
+ * <p>
+ * This method is deprecated and will throw an {@link UnsupportedOperationException}
+ * exception. Please use the {@link WifiRttManager} API to perform a Wi-Fi Aware peer-to-peer
+ * ranging.
+ *
+ * @param callback Callback for responder enabling/disabling result.
+ * @throws IllegalArgumentException If {@code callback} is null.
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public void enableResponder(ResponderCallback callback) {
+ throw new UnsupportedOperationException(
+ "enableResponder is not supported in the adaptation layer");
+ }
+
+ /**
+ * Disable Wi-Fi RTT responder mode on the device. The {@code callback} needs to be the
+ * same one used in {@link #enableResponder(ResponderCallback)}.
+ * <p>
+ * Calling this method when responder isn't enabled won't have any effect. The callback can be
+ * reused for enabling responder after this method is called.
+ * <p>
+ * This method is deprecated and will throw an {@link UnsupportedOperationException}
+ * exception. Please use the {@link WifiRttManager} API to perform a Wi-Fi Aware peer-to-peer
+ * ranging.
+ *
+ * @param callback The same callback used for enabling responder.
+ * @throws IllegalArgumentException If {@code callback} is null.
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public void disableResponder(ResponderCallback callback) {
+ throw new UnsupportedOperationException(
+ "disableResponder is not supported in the adaptation layer");
+ }
+
+ /**
+ * Configuration used for RTT responder mode. The configuration information can be used by a
+ * peer device to range the responder.
+ *
+ * @see ScanResult
+ */
+ @Deprecated
+ public static class ResponderConfig implements Parcelable {
+
+ // TODO: make all fields final once we can get mac address from responder HAL APIs.
+ /**
+ * Wi-Fi mac address used for responder mode.
+ */
+ public String macAddress = "";
+
+ /**
+ * The primary 20 MHz frequency (in MHz) of the channel where responder is enabled.
+ * @see ScanResult#frequency
+ */
+ public int frequency;
+
+ /**
+ * Center frequency of the channel where responder is enabled on. Only in use when channel
+ * width is at least 40MHz.
+ * @see ScanResult#centerFreq0
+ */
+ public int centerFreq0;
+
+ /**
+ * Center frequency of the second segment when channel width is 80 + 80 MHz.
+ * @see ScanResult#centerFreq1
+ */
+ public int centerFreq1;
+
+ /**
+ * Width of the channel where responder is enabled on.
+ * @see ScanResult#channelWidth
+ */
+ public int channelWidth;
+
+ /**
+ * Preamble supported by responder.
+ */
+ public int preamble;
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("macAddress = ").append(macAddress)
+ .append(" frequency = ").append(frequency)
+ .append(" centerFreq0 = ").append(centerFreq0)
+ .append(" centerFreq1 = ").append(centerFreq1)
+ .append(" channelWidth = ").append(channelWidth)
+ .append(" preamble = ").append(preamble);
+ return builder.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(macAddress);
+ dest.writeInt(frequency);
+ dest.writeInt(centerFreq0);
+ dest.writeInt(centerFreq1);
+ dest.writeInt(channelWidth);
+ dest.writeInt(preamble);
+ }
+
+ /** Implement {@link Parcelable} interface */
+ public static final @android.annotation.NonNull Parcelable.Creator<ResponderConfig> CREATOR =
+ new Parcelable.Creator<ResponderConfig>() {
+ @Override
+ public ResponderConfig createFromParcel(Parcel in) {
+ ResponderConfig config = new ResponderConfig();
+ config.macAddress = in.readString();
+ config.frequency = in.readInt();
+ config.centerFreq0 = in.readInt();
+ config.centerFreq1 = in.readInt();
+ config.channelWidth = in.readInt();
+ config.preamble = in.readInt();
+ return config;
+ }
+
+ @Override
+ public ResponderConfig[] newArray(int size) {
+ return new ResponderConfig[size];
+ }
+ };
+
+ }
+
+ /* private methods */
+ public static final int BASE = Protocol.BASE_WIFI_RTT_MANAGER;
+
+ public static final int CMD_OP_START_RANGING = BASE + 0;
+ public static final int CMD_OP_STOP_RANGING = BASE + 1;
+ public static final int CMD_OP_FAILED = BASE + 2;
+ public static final int CMD_OP_SUCCEEDED = BASE + 3;
+ public static final int CMD_OP_ABORTED = BASE + 4;
+ public static final int CMD_OP_ENABLE_RESPONDER = BASE + 5;
+ public static final int CMD_OP_DISABLE_RESPONDER = BASE + 6;
+ public static final int
+ CMD_OP_ENALBE_RESPONDER_SUCCEEDED = BASE + 7;
+ public static final int
+ CMD_OP_ENALBE_RESPONDER_FAILED = BASE + 8;
+ /** @hide */
+ public static final int CMD_OP_REG_BINDER = BASE + 9;
+
+ private final WifiRttManager mNewService;
+ private final Context mContext;
+ private RttCapabilities mRttCapabilities;
+
+ /**
+ * Create a new WifiScanner instance.
+ * Applications will almost always want to use
+ * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
+ * the standard {@link android.content.Context#WIFI_RTT_SERVICE Context.WIFI_RTT_SERVICE}.
+ * @param service the new WifiRttManager service
+ *
+ * @hide
+ */
+ public RttManager(Context context, WifiRttManager service) {
+ mNewService = service;
+ mContext = context;
+
+ boolean rttSupported = context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WIFI_RTT);
+
+ mRttCapabilities = new RttCapabilities();
+ mRttCapabilities.oneSidedRttSupported = rttSupported;
+ mRttCapabilities.twoSided11McRttSupported = rttSupported;
+ mRttCapabilities.lciSupported = false;
+ mRttCapabilities.lcrSupported = false;
+ mRttCapabilities.preambleSupported = PREAMBLE_HT | PREAMBLE_VHT;
+ mRttCapabilities.bwSupported = RTT_BW_40_SUPPORT | RTT_BW_80_SUPPORT;
+ mRttCapabilities.responderSupported = false;
+ mRttCapabilities.secureRttSupported = false;
+ }
+}
+
diff --git a/android/net/wifi/ScanResult.java b/android/net/wifi/ScanResult.java
new file mode 100644
index 0000000..c0c0361
--- /dev/null
+++ b/android/net/wifi/ScanResult.java
@@ -0,0 +1,847 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.annotation.SystemApi;
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Describes information about a detected access point. In addition
+ * to the attributes described here, the supplicant keeps track of
+ * {@code quality}, {@code noise}, and {@code maxbitrate} attributes,
+ * but does not currently report them to external clients.
+ */
+public class ScanResult implements Parcelable {
+ /**
+ * The network name.
+ */
+ public String SSID;
+
+ /**
+ * Ascii encoded SSID. This will replace SSID when we deprecate it. @hide
+ */
+ @UnsupportedAppUsage
+ public WifiSsid wifiSsid;
+
+ /**
+ * The address of the access point.
+ */
+ public String BSSID;
+
+ /**
+ * The HESSID from the beacon.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public long hessid;
+
+ /**
+ * The ANQP Domain ID from the Hotspot 2.0 Indication element, if present.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public int anqpDomainId;
+
+ /*
+ * This field is equivalent to the |flags|, rather than the |capabilities| field
+ * of the per-BSS scan results returned by WPA supplicant. See the definition of
+ * |struct wpa_bss| in wpa_supplicant/bss.h for more details.
+ */
+ /**
+ * Describes the authentication, key management, and encryption schemes
+ * supported by the access point.
+ */
+ public String capabilities;
+
+ /**
+ * @hide
+ * No security protocol.
+ */
+ public static final int PROTOCOL_NONE = 0;
+ /**
+ * @hide
+ * Security protocol type: WPA version 1.
+ */
+ public static final int PROTOCOL_WPA = 1;
+ /**
+ * @hide
+ * Security protocol type: RSN, for WPA version 2, and version 3.
+ */
+ public static final int PROTOCOL_RSN = 2;
+ /**
+ * @hide
+ * Security protocol type:
+ * OSU Server-only authenticated layer 2 Encryption Network.
+ * Used for Hotspot 2.0.
+ */
+ public static final int PROTOCOL_OSEN = 3;
+
+ /**
+ * @hide
+ * No security key management scheme.
+ */
+ public static final int KEY_MGMT_NONE = 0;
+ /**
+ * @hide
+ * Security key management scheme: PSK.
+ */
+ public static final int KEY_MGMT_PSK = 1;
+ /**
+ * @hide
+ * Security key management scheme: EAP.
+ */
+ public static final int KEY_MGMT_EAP = 2;
+ /**
+ * @hide
+ * Security key management scheme: FT_PSK.
+ */
+ public static final int KEY_MGMT_FT_PSK = 3;
+ /**
+ * @hide
+ * Security key management scheme: FT_EAP.
+ */
+ public static final int KEY_MGMT_FT_EAP = 4;
+ /**
+ * @hide
+ * Security key management scheme: PSK_SHA256
+ */
+ public static final int KEY_MGMT_PSK_SHA256 = 5;
+ /**
+ * @hide
+ * Security key management scheme: EAP_SHA256.
+ */
+ public static final int KEY_MGMT_EAP_SHA256 = 6;
+ /**
+ * @hide
+ * Security key management scheme: OSEN.
+ * Used for Hotspot 2.0.
+ */
+ public static final int KEY_MGMT_OSEN = 7;
+ /**
+ * @hide
+ * Security key management scheme: SAE.
+ */
+ public static final int KEY_MGMT_SAE = 8;
+ /**
+ * @hide
+ * Security key management scheme: OWE.
+ */
+ public static final int KEY_MGMT_OWE = 9;
+ /**
+ * @hide
+ * Security key management scheme: SUITE_B_192.
+ */
+ public static final int KEY_MGMT_EAP_SUITE_B_192 = 10;
+ /**
+ * @hide
+ * Security key management scheme: FT_SAE.
+ */
+ public static final int KEY_MGMT_FT_SAE = 11;
+ /**
+ * @hide
+ * Security key management scheme: OWE in transition mode.
+ */
+ public static final int KEY_MGMT_OWE_TRANSITION = 12;
+ /**
+ * @hide
+ * No cipher suite.
+ */
+ public static final int CIPHER_NONE = 0;
+ /**
+ * @hide
+ * No group addressed, only used for group data cipher.
+ */
+ public static final int CIPHER_NO_GROUP_ADDRESSED = 1;
+ /**
+ * @hide
+ * Cipher suite: TKIP
+ */
+ public static final int CIPHER_TKIP = 2;
+ /**
+ * @hide
+ * Cipher suite: CCMP
+ */
+ public static final int CIPHER_CCMP = 3;
+ /**
+ * @hide
+ * Cipher suite: GCMP
+ */
+ public static final int CIPHER_GCMP_256 = 4;
+
+ /**
+ * The detected signal level in dBm, also known as the RSSI.
+ *
+ * <p>Use {@link android.net.wifi.WifiManager#calculateSignalLevel} to convert this number into
+ * an absolute signal level which can be displayed to a user.
+ */
+ public int level;
+ /**
+ * The primary 20 MHz frequency (in MHz) of the channel over which the client is communicating
+ * with the access point.
+ */
+ public int frequency;
+
+ /**
+ * AP Channel bandwidth is 20 MHZ
+ */
+ public static final int CHANNEL_WIDTH_20MHZ = 0;
+ /**
+ * AP Channel bandwidth is 40 MHZ
+ */
+ public static final int CHANNEL_WIDTH_40MHZ = 1;
+ /**
+ * AP Channel bandwidth is 80 MHZ
+ */
+ public static final int CHANNEL_WIDTH_80MHZ = 2;
+ /**
+ * AP Channel bandwidth is 160 MHZ
+ */
+ public static final int CHANNEL_WIDTH_160MHZ = 3;
+ /**
+ * AP Channel bandwidth is 160 MHZ, but 80MHZ + 80MHZ
+ */
+ public static final int CHANNEL_WIDTH_80MHZ_PLUS_MHZ = 4;
+
+ /**
+ * AP Channel bandwidth; one of {@link #CHANNEL_WIDTH_20MHZ}, {@link #CHANNEL_WIDTH_40MHZ},
+ * {@link #CHANNEL_WIDTH_80MHZ}, {@link #CHANNEL_WIDTH_160MHZ}
+ * or {@link #CHANNEL_WIDTH_80MHZ_PLUS_MHZ}.
+ */
+ public int channelWidth;
+
+ /**
+ * Not used if the AP bandwidth is 20 MHz
+ * If the AP use 40, 80 or 160 MHz, this is the center frequency (in MHz)
+ * if the AP use 80 + 80 MHz, this is the center frequency of the first segment (in MHz)
+ */
+ public int centerFreq0;
+
+ /**
+ * Only used if the AP bandwidth is 80 + 80 MHz
+ * if the AP use 80 + 80 MHz, this is the center frequency of the second segment (in MHz)
+ */
+ public int centerFreq1;
+
+ /**
+ * @deprecated use is80211mcResponder() instead
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public boolean is80211McRTTResponder;
+
+ /**
+ * timestamp in microseconds (since boot) when
+ * this result was last seen.
+ */
+ public long timestamp;
+
+ /**
+ * Timestamp representing date when this result was last seen, in milliseconds from 1970
+ * {@hide}
+ */
+ @UnsupportedAppUsage
+ public long seen;
+
+ /**
+ * On devices with multiple hardware radio chains, this class provides metadata about
+ * each radio chain that was used to receive this scan result (probe response or beacon).
+ * {@hide}
+ */
+ public static class RadioChainInfo {
+ /** Vendor defined id for a radio chain. */
+ public int id;
+ /** Detected signal level in dBm (also known as the RSSI) on this radio chain. */
+ public int level;
+
+ @Override
+ public String toString() {
+ return "RadioChainInfo: id=" + id + ", level=" + level;
+ }
+
+ @Override
+ public boolean equals(Object otherObj) {
+ if (this == otherObj) {
+ return true;
+ }
+ if (!(otherObj instanceof RadioChainInfo)) {
+ return false;
+ }
+ RadioChainInfo other = (RadioChainInfo) otherObj;
+ return id == other.id && level == other.level;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, level);
+ }
+ };
+
+ /**
+ * Information about the list of the radio chains used to receive this scan result
+ * (probe response or beacon).
+ *
+ * For Example: On devices with 2 hardware radio chains, this list could hold 1 or 2
+ * entries based on whether this scan result was received using one or both the chains.
+ * {@hide}
+ */
+ public RadioChainInfo[] radioChainInfos;
+
+ /**
+ * Status indicating the scan result does not correspond to a user's saved configuration
+ * @hide
+ * @removed
+ */
+ @SystemApi
+ public boolean untrusted;
+
+ /**
+ * Number of time autojoin used it
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public int numUsage;
+
+ /**
+ * The approximate distance to the AP in centimeter, if available. Else
+ * {@link UNSPECIFIED}.
+ * {@hide}
+ */
+ @UnsupportedAppUsage
+ public int distanceCm;
+
+ /**
+ * The standard deviation of the distance to the access point, if available.
+ * Else {@link UNSPECIFIED}.
+ * {@hide}
+ */
+ @UnsupportedAppUsage
+ public int distanceSdCm;
+
+ /** {@hide} */
+ public static final long FLAG_PASSPOINT_NETWORK = 0x0000000000000001;
+
+ /** {@hide} */
+ public static final long FLAG_80211mc_RESPONDER = 0x0000000000000002;
+
+ /*
+ * These flags are specific to the ScanResult class, and are not related to the |flags|
+ * field of the per-BSS scan results from WPA supplicant.
+ */
+ /**
+ * Defines flags; such as {@link #FLAG_PASSPOINT_NETWORK}.
+ * {@hide}
+ */
+ @UnsupportedAppUsage
+ public long flags;
+
+ /**
+ * sets a flag in {@link #flags} field
+ * @param flag flag to set
+ * @hide
+ */
+ public void setFlag(long flag) {
+ flags |= flag;
+ }
+
+ /**
+ * clears a flag in {@link #flags} field
+ * @param flag flag to set
+ * @hide
+ */
+ public void clearFlag(long flag) {
+ flags &= ~flag;
+ }
+
+ public boolean is80211mcResponder() {
+ return (flags & FLAG_80211mc_RESPONDER) != 0;
+ }
+
+ public boolean isPasspointNetwork() {
+ return (flags & FLAG_PASSPOINT_NETWORK) != 0;
+ }
+
+ /**
+ * Indicates venue name (such as 'San Francisco Airport') published by access point; only
+ * available on Passpoint network and if published by access point.
+ */
+ public CharSequence venueName;
+
+ /**
+ * Indicates Passpoint operator name published by access point.
+ */
+ public CharSequence operatorFriendlyName;
+
+ /**
+ * {@hide}
+ */
+ public final static int UNSPECIFIED = -1;
+ /**
+ * @hide
+ */
+ public boolean is24GHz() {
+ return ScanResult.is24GHz(frequency);
+ }
+
+ /**
+ * @hide
+ * TODO: makes real freq boundaries
+ */
+ public static boolean is24GHz(int freq) {
+ return freq > 2400 && freq < 2500;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean is5GHz() {
+ return ScanResult.is5GHz(frequency);
+ }
+
+ /**
+ * @hide
+ * TODO: makes real freq boundaries
+ */
+ public static boolean is5GHz(int freq) {
+ return freq > 4900 && freq < 5900;
+ }
+
+ /**
+ * @hide
+ * anqp lines from supplicant BSS response
+ */
+ @UnsupportedAppUsage
+ public List<String> anqpLines;
+
+ /** information elements from beacon
+ * @hide
+ */
+ public static class InformationElement {
+ @UnsupportedAppUsage
+ public static final int EID_SSID = 0;
+ @UnsupportedAppUsage
+ public static final int EID_SUPPORTED_RATES = 1;
+ @UnsupportedAppUsage
+ public static final int EID_TIM = 5;
+ @UnsupportedAppUsage
+ public static final int EID_BSS_LOAD = 11;
+ @UnsupportedAppUsage
+ public static final int EID_ERP = 42;
+ public static final int EID_HT_CAPABILITIES = 45;
+ @UnsupportedAppUsage
+ public static final int EID_RSN = 48;
+ @UnsupportedAppUsage
+ public static final int EID_EXTENDED_SUPPORTED_RATES = 50;
+ @UnsupportedAppUsage
+ public static final int EID_HT_OPERATION = 61;
+ @UnsupportedAppUsage
+ public static final int EID_INTERWORKING = 107;
+ @UnsupportedAppUsage
+ public static final int EID_ROAMING_CONSORTIUM = 111;
+ @UnsupportedAppUsage
+ public static final int EID_EXTENDED_CAPS = 127;
+ public static final int EID_VHT_CAPABILITIES = 191;
+ @UnsupportedAppUsage
+ public static final int EID_VHT_OPERATION = 192;
+ @UnsupportedAppUsage
+ public static final int EID_VSA = 221;
+
+ @UnsupportedAppUsage
+ public int id;
+ @UnsupportedAppUsage
+ public byte[] bytes;
+
+ public InformationElement() {
+ }
+
+ public InformationElement(InformationElement rhs) {
+ this.id = rhs.id;
+ this.bytes = rhs.bytes.clone();
+ }
+ }
+
+ /** information elements found in the beacon
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public InformationElement[] informationElements;
+
+ /** ANQP response elements.
+ * @hide
+ */
+ public AnqpInformationElement[] anqpElements;
+
+ /**
+ * Flag indicating if this AP is a carrier AP. The determination is based
+ * on the AP's SSID and if AP is using EAP security.
+ *
+ * @hide
+ */
+ public boolean isCarrierAp;
+
+ /**
+ * The EAP type {@link WifiEnterpriseConfig.Eap} associated with this AP if it is a carrier AP.
+ *
+ * @hide
+ */
+ public int carrierApEapType;
+
+ /**
+ * The name of the carrier that's associated with this AP if it is a carrier AP.
+ *
+ * @hide
+ */
+ public String carrierName;
+
+ /** {@hide} */
+ public ScanResult(WifiSsid wifiSsid, String BSSID, long hessid, int anqpDomainId,
+ byte[] osuProviders, String caps, int level, int frequency, long tsf) {
+ this.wifiSsid = wifiSsid;
+ this.SSID = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;
+ this.BSSID = BSSID;
+ this.hessid = hessid;
+ this.anqpDomainId = anqpDomainId;
+ if (osuProviders != null) {
+ this.anqpElements = new AnqpInformationElement[1];
+ this.anqpElements[0] =
+ new AnqpInformationElement(AnqpInformationElement.HOTSPOT20_VENDOR_ID,
+ AnqpInformationElement.HS_OSU_PROVIDERS, osuProviders);
+ }
+ this.capabilities = caps;
+ this.level = level;
+ this.frequency = frequency;
+ this.timestamp = tsf;
+ this.distanceCm = UNSPECIFIED;
+ this.distanceSdCm = UNSPECIFIED;
+ this.channelWidth = UNSPECIFIED;
+ this.centerFreq0 = UNSPECIFIED;
+ this.centerFreq1 = UNSPECIFIED;
+ this.flags = 0;
+ this.isCarrierAp = false;
+ this.carrierApEapType = UNSPECIFIED;
+ this.carrierName = null;
+ this.radioChainInfos = null;
+ }
+
+ /** {@hide} */
+ public ScanResult(WifiSsid wifiSsid, String BSSID, String caps, int level, int frequency,
+ long tsf, int distCm, int distSdCm) {
+ this.wifiSsid = wifiSsid;
+ this.SSID = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;
+ this.BSSID = BSSID;
+ this.capabilities = caps;
+ this.level = level;
+ this.frequency = frequency;
+ this.timestamp = tsf;
+ this.distanceCm = distCm;
+ this.distanceSdCm = distSdCm;
+ this.channelWidth = UNSPECIFIED;
+ this.centerFreq0 = UNSPECIFIED;
+ this.centerFreq1 = UNSPECIFIED;
+ this.flags = 0;
+ this.isCarrierAp = false;
+ this.carrierApEapType = UNSPECIFIED;
+ this.carrierName = null;
+ this.radioChainInfos = null;
+ }
+
+ /** {@hide} */
+ public ScanResult(String Ssid, String BSSID, long hessid, int anqpDomainId, String caps,
+ int level, int frequency,
+ long tsf, int distCm, int distSdCm, int channelWidth, int centerFreq0, int centerFreq1,
+ boolean is80211McRTTResponder) {
+ this.SSID = Ssid;
+ this.BSSID = BSSID;
+ this.hessid = hessid;
+ this.anqpDomainId = anqpDomainId;
+ this.capabilities = caps;
+ this.level = level;
+ this.frequency = frequency;
+ this.timestamp = tsf;
+ this.distanceCm = distCm;
+ this.distanceSdCm = distSdCm;
+ this.channelWidth = channelWidth;
+ this.centerFreq0 = centerFreq0;
+ this.centerFreq1 = centerFreq1;
+ if (is80211McRTTResponder) {
+ this.flags = FLAG_80211mc_RESPONDER;
+ } else {
+ this.flags = 0;
+ }
+ this.isCarrierAp = false;
+ this.carrierApEapType = UNSPECIFIED;
+ this.carrierName = null;
+ this.radioChainInfos = null;
+ }
+
+ /** {@hide} */
+ public ScanResult(WifiSsid wifiSsid, String Ssid, String BSSID, long hessid, int anqpDomainId,
+ String caps, int level,
+ int frequency, long tsf, int distCm, int distSdCm, int channelWidth,
+ int centerFreq0, int centerFreq1, boolean is80211McRTTResponder) {
+ this(Ssid, BSSID, hessid, anqpDomainId, caps, level, frequency, tsf, distCm,
+ distSdCm, channelWidth, centerFreq0, centerFreq1, is80211McRTTResponder);
+ this.wifiSsid = wifiSsid;
+ }
+
+ /** copy constructor {@hide} */
+ public ScanResult(ScanResult source) {
+ if (source != null) {
+ wifiSsid = source.wifiSsid;
+ SSID = source.SSID;
+ BSSID = source.BSSID;
+ hessid = source.hessid;
+ anqpDomainId = source.anqpDomainId;
+ informationElements = source.informationElements;
+ anqpElements = source.anqpElements;
+ capabilities = source.capabilities;
+ level = source.level;
+ frequency = source.frequency;
+ channelWidth = source.channelWidth;
+ centerFreq0 = source.centerFreq0;
+ centerFreq1 = source.centerFreq1;
+ timestamp = source.timestamp;
+ distanceCm = source.distanceCm;
+ distanceSdCm = source.distanceSdCm;
+ seen = source.seen;
+ untrusted = source.untrusted;
+ numUsage = source.numUsage;
+ venueName = source.venueName;
+ operatorFriendlyName = source.operatorFriendlyName;
+ flags = source.flags;
+ isCarrierAp = source.isCarrierAp;
+ carrierApEapType = source.carrierApEapType;
+ carrierName = source.carrierName;
+ radioChainInfos = source.radioChainInfos;
+ }
+ }
+
+ /** empty scan result
+ *
+ * {@hide}
+ * */
+ public ScanResult() {
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ String none = "<none>";
+
+ sb.append("SSID: ").
+ append(wifiSsid == null ? WifiSsid.NONE : wifiSsid).
+ append(", BSSID: ").
+ append(BSSID == null ? none : BSSID).
+ append(", capabilities: ").
+ append(capabilities == null ? none : capabilities).
+ append(", level: ").
+ append(level).
+ append(", frequency: ").
+ append(frequency).
+ append(", timestamp: ").
+ append(timestamp);
+
+ sb.append(", distance: ").append((distanceCm != UNSPECIFIED ? distanceCm : "?")).
+ append("(cm)");
+ sb.append(", distanceSd: ").append((distanceSdCm != UNSPECIFIED ? distanceSdCm : "?")).
+ append("(cm)");
+
+ sb.append(", passpoint: ");
+ sb.append(((flags & FLAG_PASSPOINT_NETWORK) != 0) ? "yes" : "no");
+ sb.append(", ChannelBandwidth: ").append(channelWidth);
+ sb.append(", centerFreq0: ").append(centerFreq0);
+ sb.append(", centerFreq1: ").append(centerFreq1);
+ sb.append(", 80211mcResponder: ");
+ sb.append(((flags & FLAG_80211mc_RESPONDER) != 0) ? "is supported" : "is not supported");
+ sb.append(", Carrier AP: ").append(isCarrierAp ? "yes" : "no");
+ sb.append(", Carrier AP EAP Type: ").append(carrierApEapType);
+ sb.append(", Carrier name: ").append(carrierName);
+ sb.append(", Radio Chain Infos: ").append(Arrays.toString(radioChainInfos));
+ return sb.toString();
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ if (wifiSsid != null) {
+ dest.writeInt(1);
+ wifiSsid.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeString(SSID);
+ dest.writeString(BSSID);
+ dest.writeLong(hessid);
+ dest.writeInt(anqpDomainId);
+ dest.writeString(capabilities);
+ dest.writeInt(level);
+ dest.writeInt(frequency);
+ dest.writeLong(timestamp);
+ dest.writeInt(distanceCm);
+ dest.writeInt(distanceSdCm);
+ dest.writeInt(channelWidth);
+ dest.writeInt(centerFreq0);
+ dest.writeInt(centerFreq1);
+ dest.writeLong(seen);
+ dest.writeInt(untrusted ? 1 : 0);
+ dest.writeInt(numUsage);
+ dest.writeString((venueName != null) ? venueName.toString() : "");
+ dest.writeString((operatorFriendlyName != null) ? operatorFriendlyName.toString() : "");
+ dest.writeLong(this.flags);
+
+ if (informationElements != null) {
+ dest.writeInt(informationElements.length);
+ for (int i = 0; i < informationElements.length; i++) {
+ dest.writeInt(informationElements[i].id);
+ dest.writeInt(informationElements[i].bytes.length);
+ dest.writeByteArray(informationElements[i].bytes);
+ }
+ } else {
+ dest.writeInt(0);
+ }
+
+ if (anqpLines != null) {
+ dest.writeInt(anqpLines.size());
+ for (int i = 0; i < anqpLines.size(); i++) {
+ dest.writeString(anqpLines.get(i));
+ }
+ }
+ else {
+ dest.writeInt(0);
+ }
+ if (anqpElements != null) {
+ dest.writeInt(anqpElements.length);
+ for (AnqpInformationElement element : anqpElements) {
+ dest.writeInt(element.getVendorId());
+ dest.writeInt(element.getElementId());
+ dest.writeInt(element.getPayload().length);
+ dest.writeByteArray(element.getPayload());
+ }
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(isCarrierAp ? 1 : 0);
+ dest.writeInt(carrierApEapType);
+ dest.writeString(carrierName);
+
+ if (radioChainInfos != null) {
+ dest.writeInt(radioChainInfos.length);
+ for (int i = 0; i < radioChainInfos.length; i++) {
+ dest.writeInt(radioChainInfos[i].id);
+ dest.writeInt(radioChainInfos[i].level);
+ }
+ } else {
+ dest.writeInt(0);
+ }
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ @UnsupportedAppUsage
+ public static final @android.annotation.NonNull Creator<ScanResult> CREATOR =
+ new Creator<ScanResult>() {
+ public ScanResult createFromParcel(Parcel in) {
+ WifiSsid wifiSsid = null;
+ if (in.readInt() == 1) {
+ wifiSsid = WifiSsid.CREATOR.createFromParcel(in);
+ }
+ ScanResult sr = new ScanResult(
+ wifiSsid,
+ in.readString(), /* SSID */
+ in.readString(), /* BSSID */
+ in.readLong(), /* HESSID */
+ in.readInt(), /* ANQP Domain ID */
+ in.readString(), /* capabilities */
+ in.readInt(), /* level */
+ in.readInt(), /* frequency */
+ in.readLong(), /* timestamp */
+ in.readInt(), /* distanceCm */
+ in.readInt(), /* distanceSdCm */
+ in.readInt(), /* channelWidth */
+ in.readInt(), /* centerFreq0 */
+ in.readInt(), /* centerFreq1 */
+ false /* rtt responder,
+ fixed with flags below */
+ );
+
+ sr.seen = in.readLong();
+ sr.untrusted = in.readInt() != 0;
+ sr.numUsage = in.readInt();
+ sr.venueName = in.readString();
+ sr.operatorFriendlyName = in.readString();
+ sr.flags = in.readLong();
+ int n = in.readInt();
+ if (n != 0) {
+ sr.informationElements = new InformationElement[n];
+ for (int i = 0; i < n; i++) {
+ sr.informationElements[i] = new InformationElement();
+ sr.informationElements[i].id = in.readInt();
+ int len = in.readInt();
+ sr.informationElements[i].bytes = new byte[len];
+ in.readByteArray(sr.informationElements[i].bytes);
+ }
+ }
+
+ n = in.readInt();
+ if (n != 0) {
+ sr.anqpLines = new ArrayList<String>();
+ for (int i = 0; i < n; i++) {
+ sr.anqpLines.add(in.readString());
+ }
+ }
+ n = in.readInt();
+ if (n != 0) {
+ sr.anqpElements = new AnqpInformationElement[n];
+ for (int i = 0; i < n; i++) {
+ int vendorId = in.readInt();
+ int elementId = in.readInt();
+ int len = in.readInt();
+ byte[] payload = new byte[len];
+ in.readByteArray(payload);
+ sr.anqpElements[i] =
+ new AnqpInformationElement(vendorId, elementId, payload);
+ }
+ }
+ sr.isCarrierAp = in.readInt() != 0;
+ sr.carrierApEapType = in.readInt();
+ sr.carrierName = in.readString();
+ n = in.readInt();
+ if (n != 0) {
+ sr.radioChainInfos = new RadioChainInfo[n];
+ for (int i = 0; i < n; i++) {
+ sr.radioChainInfos[i] = new RadioChainInfo();
+ sr.radioChainInfos[i].id = in.readInt();
+ sr.radioChainInfos[i].level = in.readInt();
+ }
+ }
+ return sr;
+ }
+
+ public ScanResult[] newArray(int size) {
+ return new ScanResult[size];
+ }
+ };
+}
diff --git a/android/net/wifi/SupplicantState.java b/android/net/wifi/SupplicantState.java
new file mode 100644
index 0000000..de7e2b5
--- /dev/null
+++ b/android/net/wifi/SupplicantState.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * From <code>defs.h</code> in <code>wpa_supplicant</code>.
+ * <p/>
+ * These enumeration values are used to indicate the current wpa_supplicant
+ * state. This is more fine-grained than most users will be interested in.
+ * In general, it is better to use
+ * {@link android.net.NetworkInfo.State NetworkInfo.State}.
+ * <p/>
+ * Note, the order of these enum constants must match the numerical values of the
+ * state constants in <code>defs.h</code> in <code>wpa_supplicant</code>.
+ */
+public enum SupplicantState implements Parcelable {
+ /**
+ * This state indicates that client is not associated, but is likely to
+ * start looking for an access point. This state is entered when a
+ * connection is lost.
+ */
+ DISCONNECTED,
+
+ /**
+ * Interface is disabled
+ * <p/>
+ * This state is entered if the network interface is disabled.
+ * wpa_supplicant refuses any new operations that would
+ * use the radio until the interface has been enabled.
+ */
+ INTERFACE_DISABLED,
+
+ /**
+ * Inactive state (wpa_supplicant disabled).
+ * <p/>
+ * This state is entered if there are no enabled networks in the
+ * configuration. wpa_supplicant is not trying to associate with a new
+ * network and external interaction (e.g., ctrl_iface call to add or
+ * enable a network) is needed to start association.
+ */
+ INACTIVE,
+
+ /**
+ * Scanning for a network.
+ * <p/>
+ * This state is entered when wpa_supplicant starts scanning for a
+ * network.
+ */
+ SCANNING,
+
+ /**
+ * Trying to authenticate with a BSS/SSID
+ * <p/>
+ * This state is entered when wpa_supplicant has found a suitable BSS
+ * to authenticate with and the driver is configured to try to
+ * authenticate with this BSS.
+ */
+ AUTHENTICATING,
+
+ /**
+ * Trying to associate with a BSS/SSID.
+ * <p/>
+ * This state is entered when wpa_supplicant has found a suitable BSS
+ * to associate with and the driver is configured to try to associate
+ * with this BSS in ap_scan=1 mode. When using ap_scan=2 mode, this
+ * state is entered when the driver is configured to try to associate
+ * with a network using the configured SSID and security policy.
+ */
+ ASSOCIATING,
+
+ /**
+ * Association completed.
+ * <p/>
+ * This state is entered when the driver reports that association has
+ * been successfully completed with an AP. If IEEE 802.1X is used
+ * (with or without WPA/WPA2), wpa_supplicant remains in this state
+ * until the IEEE 802.1X/EAPOL authentication has been completed.
+ */
+ ASSOCIATED,
+
+ /**
+ * WPA 4-Way Key Handshake in progress.
+ * <p/>
+ * This state is entered when WPA/WPA2 4-Way Handshake is started. In
+ * case of WPA-PSK, this happens when receiving the first EAPOL-Key
+ * frame after association. In case of WPA-EAP, this state is entered
+ * when the IEEE 802.1X/EAPOL authentication has been completed.
+ */
+ FOUR_WAY_HANDSHAKE,
+
+ /**
+ * WPA Group Key Handshake in progress.
+ * <p/>
+ * This state is entered when 4-Way Key Handshake has been completed
+ * (i.e., when the supplicant sends out message 4/4) and when Group
+ * Key rekeying is started by the AP (i.e., when supplicant receives
+ * message 1/2).
+ */
+ GROUP_HANDSHAKE,
+
+ /**
+ * All authentication completed.
+ * <p/>
+ * This state is entered when the full authentication process is
+ * completed. In case of WPA2, this happens when the 4-Way Handshake is
+ * successfully completed. With WPA, this state is entered after the
+ * Group Key Handshake; with IEEE 802.1X (non-WPA) connection is
+ * completed after dynamic keys are received (or if not used, after
+ * the EAP authentication has been completed). With static WEP keys and
+ * plaintext connections, this state is entered when an association
+ * has been completed.
+ * <p/>
+ * This state indicates that the supplicant has completed its
+ * processing for the association phase and that data connection is
+ * fully configured. Note, however, that there may not be any IP
+ * address associated with the connection yet. Typically, a DHCP
+ * request needs to be sent at this point to obtain an address.
+ */
+ COMPLETED,
+
+ /**
+ * An Android-added state that is reported when a client issues an
+ * explicit DISCONNECT command. In such a case, the supplicant is
+ * not only dissociated from the current access point (as for the
+ * DISCONNECTED state above), but it also does not attempt to connect
+ * to any access point until a RECONNECT or REASSOCIATE command
+ * is issued by the client.
+ */
+ DORMANT,
+
+ /**
+ * No connection to wpa_supplicant.
+ * <p/>
+ * This is an additional pseudo-state to handle the case where
+ * wpa_supplicant is not running and/or we have not been able
+ * to establish a connection to it.
+ */
+ UNINITIALIZED,
+
+ /**
+ * A pseudo-state that should normally never be seen.
+ */
+ INVALID;
+
+ /**
+ * Returns {@code true} if the supplicant state is valid and {@code false}
+ * otherwise.
+ * @param state The supplicant state
+ * @return {@code true} if the supplicant state is valid and {@code false}
+ * otherwise.
+ */
+ public static boolean isValidState(SupplicantState state) {
+ return state != UNINITIALIZED && state != INVALID;
+ }
+
+
+ /** Supplicant associating or authenticating is considered a handshake state {@hide} */
+ public static boolean isHandshakeState(SupplicantState state) {
+ switch(state) {
+ case AUTHENTICATING:
+ case ASSOCIATING:
+ case ASSOCIATED:
+ case FOUR_WAY_HANDSHAKE:
+ case GROUP_HANDSHAKE:
+ return true;
+ case COMPLETED:
+ case DISCONNECTED:
+ case INTERFACE_DISABLED:
+ case INACTIVE:
+ case SCANNING:
+ case DORMANT:
+ case UNINITIALIZED:
+ case INVALID:
+ return false;
+ default:
+ throw new IllegalArgumentException("Unknown supplicant state");
+ }
+ }
+
+ /** @hide */
+ public static boolean isConnecting(SupplicantState state) {
+ switch(state) {
+ case AUTHENTICATING:
+ case ASSOCIATING:
+ case ASSOCIATED:
+ case FOUR_WAY_HANDSHAKE:
+ case GROUP_HANDSHAKE:
+ case COMPLETED:
+ return true;
+ case DISCONNECTED:
+ case INTERFACE_DISABLED:
+ case INACTIVE:
+ case SCANNING:
+ case DORMANT:
+ case UNINITIALIZED:
+ case INVALID:
+ return false;
+ default:
+ throw new IllegalArgumentException("Unknown supplicant state");
+ }
+ }
+
+ /** @hide */
+ public static boolean isDriverActive(SupplicantState state) {
+ switch(state) {
+ case DISCONNECTED:
+ case DORMANT:
+ case INACTIVE:
+ case AUTHENTICATING:
+ case ASSOCIATING:
+ case ASSOCIATED:
+ case SCANNING:
+ case FOUR_WAY_HANDSHAKE:
+ case GROUP_HANDSHAKE:
+ case COMPLETED:
+ return true;
+ case INTERFACE_DISABLED:
+ case UNINITIALIZED:
+ case INVALID:
+ return false;
+ default:
+ throw new IllegalArgumentException("Unknown supplicant state");
+ }
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(name());
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final @android.annotation.NonNull Creator<SupplicantState> CREATOR =
+ new Creator<SupplicantState>() {
+ public SupplicantState createFromParcel(Parcel in) {
+ return SupplicantState.valueOf(in.readString());
+ }
+
+ public SupplicantState[] newArray(int size) {
+ return new SupplicantState[size];
+ }
+ };
+
+}
diff --git a/android/net/wifi/WifiActivityEnergyInfo.java b/android/net/wifi/WifiActivityEnergyInfo.java
new file mode 100644
index 0000000..0f7fc2d
--- /dev/null
+++ b/android/net/wifi/WifiActivityEnergyInfo.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2014 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.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * Record of energy and activity information from controller and
+ * underlying wifi stack state. Timestamp the record with elapsed
+ * real-time.
+ * @hide
+ */
+public final class WifiActivityEnergyInfo implements Parcelable {
+ /**
+ * @hide
+ */
+ public long mTimestamp;
+
+ /**
+ * @hide
+ */
+ public int mStackState;
+
+ /**
+ * @hide
+ */
+ public long mControllerTxTimeMs;
+
+ /**
+ * @hide
+ */
+ public long[] mControllerTxTimePerLevelMs;
+
+ /**
+ * @hide
+ */
+ public long mControllerRxTimeMs;
+
+ /**
+ * @hide
+ */
+ public long mControllerScanTimeMs;
+
+ /**
+ * @hide
+ */
+ public long mControllerIdleTimeMs;
+
+ /**
+ * @hide
+ */
+ public long mControllerEnergyUsed;
+
+ public static final int STACK_STATE_INVALID = 0;
+ public static final int STACK_STATE_STATE_ACTIVE = 1;
+ public static final int STACK_STATE_STATE_SCANNING = 2;
+ public static final int STACK_STATE_STATE_IDLE = 3;
+
+ public WifiActivityEnergyInfo(long timestamp, int stackState,
+ long txTime, long[] txTimePerLevel, long rxTime, long scanTime,
+ long idleTime, long energyUsed) {
+ mTimestamp = timestamp;
+ mStackState = stackState;
+ mControllerTxTimeMs = txTime;
+ mControllerTxTimePerLevelMs = txTimePerLevel;
+ mControllerRxTimeMs = rxTime;
+ mControllerScanTimeMs = scanTime;
+ mControllerIdleTimeMs = idleTime;
+ mControllerEnergyUsed = energyUsed;
+ }
+
+ @Override
+ public String toString() {
+ return "WifiActivityEnergyInfo{"
+ + " timestamp=" + mTimestamp
+ + " mStackState=" + mStackState
+ + " mControllerTxTimeMs=" + mControllerTxTimeMs
+ + " mControllerTxTimePerLevelMs=" + Arrays.toString(mControllerTxTimePerLevelMs)
+ + " mControllerRxTimeMs=" + mControllerRxTimeMs
+ + " mControllerScanTimeMs=" + mControllerScanTimeMs
+ + " mControllerIdleTimeMs=" + mControllerIdleTimeMs
+ + " mControllerEnergyUsed=" + mControllerEnergyUsed
+ + " }";
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<WifiActivityEnergyInfo> CREATOR =
+ new Parcelable.Creator<WifiActivityEnergyInfo>() {
+ public WifiActivityEnergyInfo createFromParcel(Parcel in) {
+ long timestamp = in.readLong();
+ int stackState = in.readInt();
+ long txTime = in.readLong();
+ long[] txTimePerLevel = in.createLongArray();
+ long rxTime = in.readLong();
+ long scanTime = in.readLong();
+ long idleTime = in.readLong();
+ long energyUsed = in.readLong();
+ return new WifiActivityEnergyInfo(timestamp, stackState,
+ txTime, txTimePerLevel, rxTime, scanTime, idleTime, energyUsed);
+ }
+ public WifiActivityEnergyInfo[] newArray(int size) {
+ return new WifiActivityEnergyInfo[size];
+ }
+ };
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(mTimestamp);
+ out.writeInt(mStackState);
+ out.writeLong(mControllerTxTimeMs);
+ out.writeLongArray(mControllerTxTimePerLevelMs);
+ out.writeLong(mControllerRxTimeMs);
+ out.writeLong(mControllerScanTimeMs);
+ out.writeLong(mControllerIdleTimeMs);
+ out.writeLong(mControllerEnergyUsed);
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * @return bt stack reported state
+ */
+ public int getStackState() {
+ return mStackState;
+ }
+
+ /**
+ * @return tx time in ms
+ */
+ public long getControllerTxTimeMillis() {
+ return mControllerTxTimeMs;
+ }
+
+ /**
+ * @return tx time at power level provided in ms
+ */
+ public long getControllerTxTimeMillisAtLevel(int level) {
+ if (level < mControllerTxTimePerLevelMs.length) {
+ return mControllerTxTimePerLevelMs[level];
+ }
+ return 0;
+ }
+
+ /**
+ * @return rx time in ms
+ */
+ public long getControllerRxTimeMillis() {
+ return mControllerRxTimeMs;
+ }
+
+ /**
+ * @return scan time in ms
+ */
+ public long getControllerScanTimeMillis() {
+ return mControllerScanTimeMs;
+ }
+
+ /**
+ * @return idle time in ms
+ */
+ public long getControllerIdleTimeMillis() {
+ return mControllerIdleTimeMs;
+ }
+
+ /**
+ * product of current(mA), voltage(V) and time(ms)
+ * @return energy used
+ */
+ public long getControllerEnergyUsed() {
+ return mControllerEnergyUsed;
+ }
+ /**
+ * @return timestamp(wall clock) of record creation
+ */
+ public long getTimeStamp() {
+ return mTimestamp;
+ }
+
+ /**
+ * @return if the record is valid
+ */
+ public boolean isValid() {
+ return ((mControllerTxTimeMs >=0) &&
+ (mControllerRxTimeMs >=0) &&
+ (mControllerScanTimeMs >=0) &&
+ (mControllerIdleTimeMs >=0));
+ }
+}
\ No newline at end of file
diff --git a/android/net/wifi/WifiConfiguration.java b/android/net/wifi/WifiConfiguration.java
new file mode 100644
index 0000000..ed41642
--- /dev/null
+++ b/android/net/wifi/WifiConfiguration.java
@@ -0,0 +1,2628 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.UnsupportedAppUsage;
+import android.content.pm.PackageManager;
+import android.net.IpConfiguration;
+import android.net.IpConfiguration.ProxySettings;
+import android.net.MacAddress;
+import android.net.NetworkSpecifier;
+import android.net.ProxyInfo;
+import android.net.StaticIpConfiguration;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.BackupUtils;
+import android.util.Log;
+import android.util.TimeUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.HashMap;
+
+/**
+ * A class representing a configured Wi-Fi network, including the
+ * security configuration.
+ *
+ * @deprecated Use {@link WifiNetworkSpecifier.Builder} to create {@link NetworkSpecifier} and
+ * {@link WifiNetworkSuggestion.Builder} to create {@link WifiNetworkSuggestion}. This will become a
+ * system use only object in the future.
+ */
+@Deprecated
+public class WifiConfiguration implements Parcelable {
+ private static final String TAG = "WifiConfiguration";
+ /**
+ * Current Version of the Backup Serializer.
+ */
+ private static final int BACKUP_VERSION = 3;
+ /** {@hide} */
+ public static final String ssidVarName = "ssid";
+ /** {@hide} */
+ public static final String bssidVarName = "bssid";
+ /** {@hide} */
+ public static final String pskVarName = "psk";
+ /** {@hide} */
+ @Deprecated
+ @UnsupportedAppUsage
+ public static final String[] wepKeyVarNames = { "wep_key0", "wep_key1", "wep_key2", "wep_key3" };
+ /** {@hide} */
+ @Deprecated
+ public static final String wepTxKeyIdxVarName = "wep_tx_keyidx";
+ /** {@hide} */
+ public static final String priorityVarName = "priority";
+ /** {@hide} */
+ public static final String hiddenSSIDVarName = "scan_ssid";
+ /** {@hide} */
+ public static final String pmfVarName = "ieee80211w";
+ /** {@hide} */
+ public static final String updateIdentiferVarName = "update_identifier";
+ /** {@hide} */
+ public static final int INVALID_NETWORK_ID = -1;
+ /** {@hide} */
+ public static final int LOCAL_ONLY_NETWORK_ID = -2;
+
+ /** {@hide} */
+ private String mPasspointManagementObjectTree;
+ /** {@hide} */
+ private static final int MAXIMUM_RANDOM_MAC_GENERATION_RETRY = 3;
+
+ /**
+ * Recognized key management schemes.
+ */
+ public static class KeyMgmt {
+ private KeyMgmt() { }
+
+ /** WPA is not used; plaintext or static WEP could be used. */
+ public static final int NONE = 0;
+ /** WPA pre-shared key (requires {@code preSharedKey} to be specified). */
+ public static final int WPA_PSK = 1;
+ /** WPA using EAP authentication. Generally used with an external authentication server. */
+ public static final int WPA_EAP = 2;
+ /** IEEE 802.1X using EAP authentication and (optionally) dynamically
+ * generated WEP keys. */
+ public static final int IEEE8021X = 3;
+
+ /** WPA2 pre-shared key for use with soft access point
+ * (requires {@code preSharedKey} to be specified).
+ * @hide
+ */
+ @SystemApi
+ public static final int WPA2_PSK = 4;
+ /**
+ * Hotspot 2.0 r2 OSEN:
+ * @hide
+ */
+ public static final int OSEN = 5;
+
+ /**
+ * IEEE 802.11r Fast BSS Transition with PSK authentication.
+ * @hide
+ */
+ public static final int FT_PSK = 6;
+
+ /**
+ * IEEE 802.11r Fast BSS Transition with EAP authentication.
+ * @hide
+ */
+ public static final int FT_EAP = 7;
+
+ /**
+ * Simultaneous Authentication of Equals
+ */
+ public static final int SAE = 8;
+
+ /**
+ * Opportunististic Wireless Encryption
+ */
+ public static final int OWE = 9;
+
+ /**
+ * SUITE_B_192 192 bit level
+ */
+ public static final int SUITE_B_192 = 10;
+
+ /**
+ * WPA pre-shared key with stronger SHA256-based algorithms.
+ * @hide
+ */
+ public static final int WPA_PSK_SHA256 = 11;
+
+ /**
+ * WPA using EAP authentication with stronger SHA256-based algorithms.
+ * @hide
+ */
+ public static final int WPA_EAP_SHA256 = 12;
+
+ public static final String varName = "key_mgmt";
+
+ public static final String[] strings = { "NONE", "WPA_PSK", "WPA_EAP",
+ "IEEE8021X", "WPA2_PSK", "OSEN", "FT_PSK", "FT_EAP",
+ "SAE", "OWE", "SUITE_B_192", "WPA_PSK_SHA256", "WPA_EAP_SHA256" };
+ }
+
+ /**
+ * Recognized security protocols.
+ */
+ public static class Protocol {
+ private Protocol() { }
+
+ /** WPA/IEEE 802.11i/D3.0
+ * @deprecated Due to security and performance limitations, use of WPA-1 networks
+ * is discouraged. WPA-2 (RSN) should be used instead. */
+ @Deprecated
+ public static final int WPA = 0;
+ /** RSN WPA2/WPA3/IEEE 802.11i */
+ public static final int RSN = 1;
+ /** HS2.0 r2 OSEN
+ * @hide
+ */
+ public static final int OSEN = 2;
+
+ public static final String varName = "proto";
+
+ public static final String[] strings = { "WPA", "RSN", "OSEN" };
+ }
+
+ /**
+ * Recognized IEEE 802.11 authentication algorithms.
+ */
+ public static class AuthAlgorithm {
+ private AuthAlgorithm() { }
+
+ /** Open System authentication (required for WPA/WPA2) */
+ public static final int OPEN = 0;
+ /** Shared Key authentication (requires static WEP keys)
+ * @deprecated Due to security and performance limitations, use of WEP networks
+ * is discouraged. */
+ @Deprecated
+ public static final int SHARED = 1;
+ /** LEAP/Network EAP (only used with LEAP) */
+ public static final int LEAP = 2;
+
+ public static final String varName = "auth_alg";
+
+ public static final String[] strings = { "OPEN", "SHARED", "LEAP" };
+ }
+
+ /**
+ * Recognized pairwise ciphers for WPA.
+ */
+ public static class PairwiseCipher {
+ private PairwiseCipher() { }
+
+ /** Use only Group keys (deprecated) */
+ public static final int NONE = 0;
+ /** Temporal Key Integrity Protocol [IEEE 802.11i/D7.0]
+ * @deprecated Due to security and performance limitations, use of WPA-1 networks
+ * is discouraged. WPA-2 (RSN) should be used instead. */
+ @Deprecated
+ public static final int TKIP = 1;
+ /** AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0] */
+ public static final int CCMP = 2;
+ /**
+ * AES in Galois/Counter Mode
+ */
+ public static final int GCMP_256 = 3;
+
+ public static final String varName = "pairwise";
+
+ public static final String[] strings = { "NONE", "TKIP", "CCMP", "GCMP_256" };
+ }
+
+ /**
+ * Recognized group ciphers.
+ * <pre>
+ * CCMP = AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0]
+ * TKIP = Temporal Key Integrity Protocol [IEEE 802.11i/D7.0]
+ * WEP104 = WEP (Wired Equivalent Privacy) with 104-bit key
+ * WEP40 = WEP (Wired Equivalent Privacy) with 40-bit key (original 802.11)
+ * GCMP_256 = AES in Galois/Counter Mode
+ * </pre>
+ */
+ public static class GroupCipher {
+ private GroupCipher() { }
+
+ /** WEP40 = WEP (Wired Equivalent Privacy) with 40-bit key (original 802.11)
+ * @deprecated Due to security and performance limitations, use of WEP networks
+ * is discouraged. */
+ @Deprecated
+ public static final int WEP40 = 0;
+ /** WEP104 = WEP (Wired Equivalent Privacy) with 104-bit key
+ * @deprecated Due to security and performance limitations, use of WEP networks
+ * is discouraged. */
+ @Deprecated
+ public static final int WEP104 = 1;
+ /** Temporal Key Integrity Protocol [IEEE 802.11i/D7.0] */
+ public static final int TKIP = 2;
+ /** AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0] */
+ public static final int CCMP = 3;
+ /** Hotspot 2.0 r2 OSEN
+ * @hide
+ */
+ public static final int GTK_NOT_USED = 4;
+ /**
+ * AES in Galois/Counter Mode
+ */
+ public static final int GCMP_256 = 5;
+
+ public static final String varName = "group";
+
+ public static final String[] strings =
+ { /* deprecated */ "WEP40", /* deprecated */ "WEP104",
+ "TKIP", "CCMP", "GTK_NOT_USED", "GCMP_256" };
+ }
+
+ /**
+ * Recognized group management ciphers.
+ * <pre>
+ * BIP_CMAC_256 = Cipher-based Message Authentication Code 256 bits
+ * BIP_GMAC_128 = Galois Message Authentication Code 128 bits
+ * BIP_GMAC_256 = Galois Message Authentication Code 256 bits
+ * </pre>
+ */
+ public static class GroupMgmtCipher {
+ private GroupMgmtCipher() { }
+
+ /** CMAC-256 = Cipher-based Message Authentication Code */
+ public static final int BIP_CMAC_256 = 0;
+
+ /** GMAC-128 = Galois Message Authentication Code */
+ public static final int BIP_GMAC_128 = 1;
+
+ /** GMAC-256 = Galois Message Authentication Code */
+ public static final int BIP_GMAC_256 = 2;
+
+ private static final String varName = "groupMgmt";
+
+ private static final String[] strings = { "BIP_CMAC_256",
+ "BIP_GMAC_128", "BIP_GMAC_256"};
+ }
+
+ /**
+ * Recognized suiteB ciphers.
+ * <pre>
+ * ECDHE_ECDSA
+ * ECDHE_RSA
+ * </pre>
+ * @hide
+ */
+ public static class SuiteBCipher {
+ private SuiteBCipher() { }
+
+ /** Diffie-Hellman with Elliptic Curve_ECDSA signature */
+ public static final int ECDHE_ECDSA = 0;
+
+ /** Diffie-Hellman with_RSA signature */
+ public static final int ECDHE_RSA = 1;
+
+ private static final String varName = "SuiteB";
+
+ private static final String[] strings = { "ECDHE_ECDSA", "ECDHE_RSA" };
+ }
+
+ /** Possible status of a network configuration. */
+ public static class Status {
+ private Status() { }
+
+ /** this is the network we are currently connected to */
+ public static final int CURRENT = 0;
+ /** supplicant will not attempt to use this network */
+ public static final int DISABLED = 1;
+ /** supplicant will consider this network available for association */
+ public static final int ENABLED = 2;
+
+ public static final String[] strings = { "current", "disabled", "enabled" };
+ }
+
+ /**
+ * Security types we support.
+ */
+ /** @hide */
+ public static final int SECURITY_TYPE_OPEN = 0;
+ /** @hide */
+ public static final int SECURITY_TYPE_WEP = 1;
+ /** @hide */
+ public static final int SECURITY_TYPE_PSK = 2;
+ /** @hide */
+ public static final int SECURITY_TYPE_EAP = 3;
+ /** @hide */
+ public static final int SECURITY_TYPE_SAE = 4;
+ /** @hide */
+ public static final int SECURITY_TYPE_EAP_SUITE_B = 5;
+ /** @hide */
+ public static final int SECURITY_TYPE_OWE = 6;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "SECURITY_TYPE_" }, value = {
+ SECURITY_TYPE_OPEN,
+ SECURITY_TYPE_WEP,
+ SECURITY_TYPE_PSK,
+ SECURITY_TYPE_EAP,
+ SECURITY_TYPE_SAE,
+ SECURITY_TYPE_EAP_SUITE_B,
+ SECURITY_TYPE_OWE
+ })
+ public @interface SecurityType {}
+
+ /**
+ * @hide
+ * Set security params (sets the various bitsets exposed in WifiConfiguration).
+ *
+ * @param securityType One of the security types from {@link SecurityType}.
+ */
+ public void setSecurityParams(@SecurityType int securityType) {
+ // Clear all the bitsets.
+ allowedKeyManagement.clear();
+ allowedProtocols.clear();
+ allowedAuthAlgorithms.clear();
+ allowedPairwiseCiphers.clear();
+ allowedGroupCiphers.clear();
+ allowedGroupManagementCiphers.clear();
+ allowedSuiteBCiphers.clear();
+
+ switch (securityType) {
+ case SECURITY_TYPE_OPEN:
+ allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+ break;
+ case SECURITY_TYPE_WEP:
+ allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+ allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
+ allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
+ break;
+ case SECURITY_TYPE_PSK:
+ allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+ break;
+ case SECURITY_TYPE_EAP:
+ allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+ allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
+ break;
+ case SECURITY_TYPE_SAE:
+ allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SAE);
+ requirePMF = true;
+ break;
+ case SECURITY_TYPE_EAP_SUITE_B:
+ allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SUITE_B_192);
+ allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
+ allowedGroupManagementCiphers.set(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256);
+ // Note: allowedSuiteBCiphers bitset will be set by the service once the
+ // certificates are attached to this profile
+ requirePMF = true;
+ break;
+ case SECURITY_TYPE_OWE:
+ allowedKeyManagement.set(WifiConfiguration.KeyMgmt.OWE);
+ requirePMF = true;
+ break;
+ default:
+ throw new IllegalArgumentException("unknown security type " + securityType);
+ }
+ }
+
+ /** @hide */
+ public static final int UNKNOWN_UID = -1;
+
+ /**
+ * The ID number that the supplicant uses to identify this
+ * network configuration entry. This must be passed as an argument
+ * to most calls into the supplicant.
+ */
+ public int networkId;
+
+ // Fixme We need remove this field to use only Quality network selection status only
+ /**
+ * The current status of this network configuration entry.
+ * @see Status
+ */
+ public int status;
+
+ /**
+ * The network's SSID. Can either be a UTF-8 string,
+ * which must be enclosed in double quotation marks
+ * (e.g., {@code "MyNetwork"}), or a string of
+ * hex digits, which are not enclosed in quotes
+ * (e.g., {@code 01a243f405}).
+ */
+ public String SSID;
+
+ /**
+ * When set, this network configuration entry should only be used when
+ * associating with the AP having the specified BSSID. The value is
+ * a string in the format of an Ethernet MAC address, e.g.,
+ * <code>XX:XX:XX:XX:XX:XX</code> where each <code>X</code> is a hex digit.
+ */
+ public String BSSID;
+
+ /**
+ * 2GHz band.
+ * @hide
+ */
+ public static final int AP_BAND_2GHZ = 0;
+
+ /**
+ * 5GHz band.
+ * @hide
+ */
+ public static final int AP_BAND_5GHZ = 1;
+
+ /**
+ * Device is allowed to choose the optimal band (2Ghz or 5Ghz) based on device capability,
+ * operating country code and current radio conditions.
+ * @hide
+ */
+ public static final int AP_BAND_ANY = -1;
+
+ /**
+ * The band which AP resides on
+ * -1:Any 0:2G 1:5G
+ * By default, 2G is chosen
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public int apBand = AP_BAND_2GHZ;
+
+ /**
+ * The channel which AP resides on,currently, US only
+ * 2G 1-11
+ * 5G 36,40,44,48,149,153,157,161,165
+ * 0 - find a random available channel according to the apBand
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public int apChannel = 0;
+
+ /**
+ * Pre-shared key for use with WPA-PSK. Either an ASCII string enclosed in
+ * double quotation marks (e.g., {@code "abcdefghij"} for PSK passphrase or
+ * a string of 64 hex digits for raw PSK.
+ * <p/>
+ * When the value of this key is read, the actual key is
+ * not returned, just a "*" if the key has a value, or the null
+ * string otherwise.
+ */
+ public String preSharedKey;
+
+ /**
+ * Four WEP keys. For each of the four values, provide either an ASCII
+ * string enclosed in double quotation marks (e.g., {@code "abcdef"}),
+ * a string of hex digits (e.g., {@code 0102030405}), or an empty string
+ * (e.g., {@code ""}).
+ * <p/>
+ * When the value of one of these keys is read, the actual key is
+ * not returned, just a "*" if the key has a value, or the null
+ * string otherwise.
+ * @deprecated Due to security and performance limitations, use of WEP networks
+ * is discouraged.
+ */
+ @Deprecated
+ public String[] wepKeys;
+
+ /** Default WEP key index, ranging from 0 to 3.
+ * @deprecated Due to security and performance limitations, use of WEP networks
+ * is discouraged. */
+ @Deprecated
+ public int wepTxKeyIndex;
+
+ /**
+ * Priority determines the preference given to a network by {@code wpa_supplicant}
+ * when choosing an access point with which to associate.
+ * @deprecated This field does not exist anymore.
+ */
+ @Deprecated
+ public int priority;
+
+ /**
+ * This is a network that does not broadcast its SSID, so an
+ * SSID-specific probe request must be used for scans.
+ */
+ public boolean hiddenSSID;
+
+ /**
+ * This is a network that requries Protected Management Frames (PMF).
+ * @hide
+ */
+ public boolean requirePMF;
+
+ /**
+ * Update identifier, for Passpoint network.
+ * @hide
+ */
+ public String updateIdentifier;
+
+ /**
+ * The set of key management protocols supported by this configuration.
+ * See {@link KeyMgmt} for descriptions of the values.
+ * Defaults to WPA-PSK WPA-EAP.
+ */
+ @NonNull
+ public BitSet allowedKeyManagement;
+ /**
+ * The set of security protocols supported by this configuration.
+ * See {@link Protocol} for descriptions of the values.
+ * Defaults to WPA RSN.
+ */
+ @NonNull
+ public BitSet allowedProtocols;
+ /**
+ * The set of authentication protocols supported by this configuration.
+ * See {@link AuthAlgorithm} for descriptions of the values.
+ * Defaults to automatic selection.
+ */
+ @NonNull
+ public BitSet allowedAuthAlgorithms;
+ /**
+ * The set of pairwise ciphers for WPA supported by this configuration.
+ * See {@link PairwiseCipher} for descriptions of the values.
+ * Defaults to CCMP TKIP.
+ */
+ @NonNull
+ public BitSet allowedPairwiseCiphers;
+ /**
+ * The set of group ciphers supported by this configuration.
+ * See {@link GroupCipher} for descriptions of the values.
+ * Defaults to CCMP TKIP WEP104 WEP40.
+ */
+ @NonNull
+ public BitSet allowedGroupCiphers;
+ /**
+ * The set of group management ciphers supported by this configuration.
+ * See {@link GroupMgmtCipher} for descriptions of the values.
+ */
+ @NonNull
+ public BitSet allowedGroupManagementCiphers;
+ /**
+ * The set of SuiteB ciphers supported by this configuration.
+ * To be used for WPA3-Enterprise mode.
+ * See {@link SuiteBCipher} for descriptions of the values.
+ */
+ @NonNull
+ public BitSet allowedSuiteBCiphers;
+ /**
+ * The enterprise configuration details specifying the EAP method,
+ * certificates and other settings associated with the EAP.
+ */
+ public WifiEnterpriseConfig enterpriseConfig;
+
+ /**
+ * Fully qualified domain name of a Passpoint configuration
+ */
+ public String FQDN;
+
+ /**
+ * Name of Passpoint credential provider
+ */
+ public String providerFriendlyName;
+
+ /**
+ * Flag indicating if this network is provided by a home Passpoint provider or a roaming
+ * Passpoint provider. This flag will be {@code true} if this network is provided by
+ * a home Passpoint provider and {@code false} if is provided by a roaming Passpoint provider
+ * or is a non-Passpoint network.
+ */
+ public boolean isHomeProviderNetwork;
+
+ /**
+ * Roaming Consortium Id list for Passpoint credential; identifies a set of networks where
+ * Passpoint credential will be considered valid
+ */
+ public long[] roamingConsortiumIds;
+
+ /**
+ * @hide
+ * This network configuration is visible to and usable by other users on the
+ * same device.
+ */
+ @UnsupportedAppUsage
+ public boolean shared;
+
+ /**
+ * @hide
+ */
+ @NonNull
+ @UnsupportedAppUsage
+ private IpConfiguration mIpConfiguration;
+
+ /**
+ * @hide
+ * dhcp server MAC address if known
+ */
+ public String dhcpServer;
+
+ /**
+ * @hide
+ * default Gateway MAC address if known
+ */
+ @UnsupportedAppUsage
+ public String defaultGwMacAddress;
+
+ /**
+ * @hide
+ * last time we connected, this configuration had validated internet access
+ */
+ @UnsupportedAppUsage
+ public boolean validatedInternetAccess;
+
+ /**
+ * @hide
+ * The number of beacon intervals between Delivery Traffic Indication Maps (DTIM)
+ * This value is populated from scan results that contain Beacon Frames, which are infrequent.
+ * The value is not guaranteed to be set or current (Although it SHOULDNT change once set)
+ * Valid values are from 1 - 255. Initialized here as 0, use this to check if set.
+ */
+ public int dtimInterval = 0;
+
+ /**
+ * Flag indicating if this configuration represents a legacy Passpoint configuration
+ * (Release N or older). This is used for migrating Passpoint configuration from N to O.
+ * This will no longer be needed after O.
+ * @hide
+ */
+ public boolean isLegacyPasspointConfig = false;
+ /**
+ * @hide
+ * Uid of app creating the configuration
+ */
+ @SystemApi
+ public int creatorUid;
+
+ /**
+ * @hide
+ * Uid of last app issuing a connection related command
+ */
+ @UnsupportedAppUsage
+ public int lastConnectUid;
+
+ /**
+ * @hide
+ * Uid of last app modifying the configuration
+ */
+ @SystemApi
+ public int lastUpdateUid;
+
+ /**
+ * @hide
+ * Universal name for app creating the configuration
+ * see {@link PackageManager#getNameForUid(int)}
+ */
+ @SystemApi
+ public String creatorName;
+
+ /**
+ * @hide
+ * Universal name for app updating the configuration
+ * see {@link PackageManager#getNameForUid(int)}
+ */
+ @SystemApi
+ public String lastUpdateName;
+
+ /**
+ * @hide
+ * Status of user approval for connection
+ */
+ public int userApproved = USER_UNSPECIFIED;
+
+ /** The Below RSSI thresholds are used to configure AutoJoin
+ * - GOOD/LOW/BAD thresholds are used so as to calculate link score
+ * - UNWANTED_SOFT are used by the blacklisting logic so as to handle
+ * the unwanted network message coming from CS
+ * - UNBLACKLIST thresholds are used so as to tweak the speed at which
+ * the network is unblacklisted (i.e. if
+ * it is seen with good RSSI, it is blacklisted faster)
+ * - INITIAL_AUTOJOIN_ATTEMPT, used to determine how close from
+ * the network we need to be before autojoin kicks in
+ */
+ /** @hide **/
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public static int INVALID_RSSI = -127;
+
+ // States for the userApproved field
+ /**
+ * @hide
+ * User hasn't specified if connection is okay
+ */
+ public static final int USER_UNSPECIFIED = 0;
+ /**
+ * @hide
+ * User has approved this for connection
+ */
+ public static final int USER_APPROVED = 1;
+ /**
+ * @hide
+ * User has banned this from connection
+ */
+ public static final int USER_BANNED = 2;
+ /**
+ * @hide
+ * Waiting for user input
+ */
+ public static final int USER_PENDING = 3;
+
+ /**
+ * @hide
+ * Number of reports indicating no Internet Access
+ */
+ @UnsupportedAppUsage
+ public int numNoInternetAccessReports;
+
+ /**
+ * @hide
+ * For debug: date at which the config was last updated
+ */
+ public String updateTime;
+
+ /**
+ * @hide
+ * For debug: date at which the config was last updated
+ */
+ public String creationTime;
+
+ /**
+ * @hide
+ * The WiFi configuration is considered to have no internet access for purpose of autojoining
+ * if there has been a report of it having no internet access, and, it never have had
+ * internet access in the past.
+ */
+ @SystemApi
+ public boolean hasNoInternetAccess() {
+ return numNoInternetAccessReports > 0 && !validatedInternetAccess;
+ }
+
+ /**
+ * The WiFi configuration is expected not to have Internet access (e.g., a wireless printer, a
+ * Chromecast hotspot, etc.). This will be set if the user explicitly confirms a connection to
+ * this configuration and selects "don't ask again".
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public boolean noInternetAccessExpected;
+
+ /**
+ * The WiFi configuration is expected not to have Internet access (e.g., a wireless printer, a
+ * Chromecast hotspot, etc.). This will be set if the user explicitly confirms a connection to
+ * this configuration and selects "don't ask again".
+ * @hide
+ */
+ @SystemApi
+ public boolean isNoInternetAccessExpected() {
+ return noInternetAccessExpected;
+ }
+
+ /**
+ * This Wifi configuration is expected for OSU(Online Sign Up) of Passpoint Release 2.
+ * @hide
+ */
+ public boolean osu;
+
+ /**
+ * @hide
+ * Last time the system was connected to this configuration.
+ */
+ public long lastConnected;
+
+ /**
+ * @hide
+ * Last time the system was disconnected to this configuration.
+ */
+ public long lastDisconnected;
+
+ /**
+ * Set if the configuration was self added by the framework
+ * This boolean is cleared if we get a connect/save/ update or
+ * any wifiManager command that indicate the user interacted with the configuration
+ * since we will now consider that the configuration belong to him.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public boolean selfAdded;
+
+ /**
+ * Set if the configuration was self added by the framework
+ * This boolean is set once and never cleared. It is used
+ * so as we never loose track of who created the
+ * configuration in the first place.
+ * @hide
+ */
+ public boolean didSelfAdd;
+
+ /**
+ * Peer WifiConfiguration this WifiConfiguration was added for
+ * @hide
+ */
+ public String peerWifiConfiguration;
+
+ /**
+ * @hide
+ * Indicate that a WifiConfiguration is temporary and should not be saved
+ * nor considered by AutoJoin.
+ */
+ public boolean ephemeral;
+
+ /**
+ * @hide
+ * Indicate that a WifiConfiguration is temporary and should not be saved
+ * nor considered by AutoJoin.
+ */
+ @SystemApi
+ public boolean isEphemeral() {
+ return ephemeral;
+ }
+
+ /**
+ * Indicate whther the network is trusted or not. Networks are considered trusted
+ * if the user explicitly allowed this network connection.
+ * @hide
+ */
+ public boolean trusted;
+
+ /**
+ * This Wifi configuration is created from a {@link WifiNetworkSuggestion}
+ * @hide
+ */
+ public boolean fromWifiNetworkSuggestion;
+
+ /**
+ * This Wifi configuration is created from a {@link WifiNetworkSpecifier}
+ * @hide
+ */
+ public boolean fromWifiNetworkSpecifier;
+
+ /**
+ * Indicates if the creator of this configuration has expressed that it
+ * should be considered metered.
+ *
+ * @see #isMetered(WifiConfiguration, WifiInfo)
+ * @hide
+ */
+ @SystemApi
+ public boolean meteredHint;
+
+ /** {@hide} */
+ public static final int METERED_OVERRIDE_NONE = 0;
+ /** {@hide} */
+ public static final int METERED_OVERRIDE_METERED = 1;
+ /** {@hide} */
+ public static final int METERED_OVERRIDE_NOT_METERED = 2;
+
+ /**
+ * Indicates if the end user has expressed an explicit opinion about the
+ * meteredness of this network, such as through the Settings app.
+ * <p>
+ * This should always override any values from {@link #meteredHint} or
+ * {@link WifiInfo#getMeteredHint()}.
+ *
+ * @see #isMetered(WifiConfiguration, WifiInfo)
+ * @hide
+ */
+ public int meteredOverride = METERED_OVERRIDE_NONE;
+
+ /**
+ * Blend together all the various opinions to decide if the given network
+ * should be considered metered or not.
+ *
+ * @hide
+ */
+ public static boolean isMetered(WifiConfiguration config, WifiInfo info) {
+ boolean metered = false;
+ if (info != null && info.getMeteredHint()) {
+ metered = true;
+ }
+ if (config != null && config.meteredHint) {
+ metered = true;
+ }
+ if (config != null
+ && config.meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED) {
+ metered = true;
+ }
+ if (config != null
+ && config.meteredOverride == WifiConfiguration.METERED_OVERRIDE_NOT_METERED) {
+ metered = false;
+ }
+ return metered;
+ }
+
+ /**
+ * @hide
+ * Returns true if this WiFi config is for an open network.
+ */
+ public boolean isOpenNetwork() {
+ final int cardinality = allowedKeyManagement.cardinality();
+ final boolean hasNoKeyMgmt = cardinality == 0
+ || (cardinality == 1 && (allowedKeyManagement.get(KeyMgmt.NONE)
+ || allowedKeyManagement.get(KeyMgmt.OWE)));
+
+ boolean hasNoWepKeys = true;
+ if (wepKeys != null) {
+ for (int i = 0; i < wepKeys.length; i++) {
+ if (wepKeys[i] != null) {
+ hasNoWepKeys = false;
+ break;
+ }
+ }
+ }
+
+ return hasNoKeyMgmt && hasNoWepKeys;
+ }
+
+ /**
+ * @hide
+ * Setting this value will force scan results associated with this configuration to
+ * be included in the bucket of networks that are externally scored.
+ * If not set, associated scan results will be treated as legacy saved networks and
+ * will take precedence over networks in the scored category.
+ */
+ @SystemApi
+ public boolean useExternalScores;
+
+ /**
+ * @hide
+ * Number of time the scorer overrode a the priority based choice, when comparing two
+ * WifiConfigurations, note that since comparing WifiConfiguration happens very often
+ * potentially at every scan, this number might become very large, even on an idle
+ * system.
+ */
+ @SystemApi
+ public int numScorerOverride;
+
+ /**
+ * @hide
+ * Number of time the scorer overrode a the priority based choice, and the comparison
+ * triggered a network switch
+ */
+ @SystemApi
+ public int numScorerOverrideAndSwitchedNetwork;
+
+ /**
+ * @hide
+ * Number of time we associated to this configuration.
+ */
+ @SystemApi
+ public int numAssociation;
+
+ /**
+ * @hide
+ * Use factory MAC when connecting to this network
+ */
+ public static final int RANDOMIZATION_NONE = 0;
+ /**
+ * @hide
+ * Generate a randomized MAC once and reuse it for all connections to this network
+ */
+ public static final int RANDOMIZATION_PERSISTENT = 1;
+
+ /**
+ * @hide
+ * Level of MAC randomization for this network
+ */
+ public int macRandomizationSetting = RANDOMIZATION_PERSISTENT;
+
+ /**
+ * @hide
+ * Randomized MAC address to use with this particular network
+ */
+ @NonNull
+ private MacAddress mRandomizedMacAddress;
+
+ /**
+ * @hide
+ * Checks if the given MAC address can be used for Connected Mac Randomization
+ * by verifying that it is non-null, unicast, locally assigned, and not default mac.
+ * @param mac MacAddress to check
+ * @return true if mac is good to use
+ */
+ public static boolean isValidMacAddressForRandomization(MacAddress mac) {
+ return mac != null && !mac.isMulticastAddress() && mac.isLocallyAssigned()
+ && !MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS).equals(mac);
+ }
+
+ /**
+ * @hide
+ * Returns Randomized MAC address to use with the network.
+ * If it is not set/valid, creates a new randomized address.
+ * If it can't generate a valid mac, returns the default MAC.
+ */
+ public @NonNull MacAddress getOrCreateRandomizedMacAddress() {
+ int randomMacGenerationCount = 0;
+ while (!isValidMacAddressForRandomization(mRandomizedMacAddress)
+ && randomMacGenerationCount < MAXIMUM_RANDOM_MAC_GENERATION_RETRY) {
+ mRandomizedMacAddress = MacAddress.createRandomUnicastAddress();
+ randomMacGenerationCount++;
+ }
+
+ if (!isValidMacAddressForRandomization(mRandomizedMacAddress)) {
+ mRandomizedMacAddress = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS);
+ }
+ return mRandomizedMacAddress;
+ }
+
+ /**
+ * Returns MAC address set to be the local randomized MAC address.
+ * Depending on user preference, the device may or may not use the returned MAC address for
+ * connections to this network.
+ * <p>
+ * Information is restricted to Device Owner, Profile Owner, and Carrier apps
+ * (which will only obtain addresses for configurations which they create). Other callers
+ * will receive a default "02:00:00:00:00:00" MAC address.
+ */
+ public @NonNull MacAddress getRandomizedMacAddress() {
+ return mRandomizedMacAddress;
+ }
+
+ /**
+ * @hide
+ * @param mac MacAddress to change into
+ */
+ public void setRandomizedMacAddress(@NonNull MacAddress mac) {
+ if (mac == null) {
+ Log.e(TAG, "setRandomizedMacAddress received null MacAddress.");
+ return;
+ }
+ mRandomizedMacAddress = mac;
+ }
+
+ /** @hide
+ * Boost given to RSSI on a home network for the purpose of calculating the score
+ * This adds stickiness to home networks, as defined by:
+ * - less than 4 known BSSIDs
+ * - PSK only
+ * - TODO: add a test to verify that all BSSIDs are behind same gateway
+ ***/
+ public static final int HOME_NETWORK_RSSI_BOOST = 5;
+
+ /**
+ * @hide
+ * This class is used to contain all the information and API used for quality network selection
+ */
+ public static class NetworkSelectionStatus {
+ /**
+ * Quality Network Selection Status enable, temporary disabled, permanently disabled
+ */
+ /**
+ * This network is allowed to join Quality Network Selection
+ */
+ public static final int NETWORK_SELECTION_ENABLED = 0;
+ /**
+ * network was temporary disabled. Can be re-enabled after a time period expire
+ */
+ public static final int NETWORK_SELECTION_TEMPORARY_DISABLED = 1;
+ /**
+ * network was permanently disabled.
+ */
+ public static final int NETWORK_SELECTION_PERMANENTLY_DISABLED = 2;
+ /**
+ * Maximum Network selection status
+ */
+ public static final int NETWORK_SELECTION_STATUS_MAX = 3;
+
+ /**
+ * Quality network selection status String (for debug purpose). Use Quality network
+ * selection status value as index to extec the corresponding debug string
+ */
+ public static final String[] QUALITY_NETWORK_SELECTION_STATUS = {
+ "NETWORK_SELECTION_ENABLED",
+ "NETWORK_SELECTION_TEMPORARY_DISABLED",
+ "NETWORK_SELECTION_PERMANENTLY_DISABLED"};
+
+ //Quality Network disabled reasons
+ /**
+ * Default value. Means not disabled
+ */
+ public static final int NETWORK_SELECTION_ENABLE = 0;
+ /**
+ * The starting index for network selection disabled reasons
+ */
+ public static final int NETWORK_SELECTION_DISABLED_STARTING_INDEX = 1;
+ /**
+ * @deprecated it is not used any more.
+ * This network is disabled because higher layer (>2) network is bad
+ */
+ public static final int DISABLED_BAD_LINK = 1;
+ /**
+ * This network is disabled because multiple association rejects
+ */
+ public static final int DISABLED_ASSOCIATION_REJECTION = 2;
+ /**
+ * This network is disabled because multiple authentication failure
+ */
+ public static final int DISABLED_AUTHENTICATION_FAILURE = 3;
+ /**
+ * This network is disabled because multiple DHCP failure
+ */
+ public static final int DISABLED_DHCP_FAILURE = 4;
+ /**
+ * This network is disabled because of security network but no credentials
+ */
+ public static final int DISABLED_DNS_FAILURE = 5;
+ /**
+ * This network is temporarily disabled because it has no Internet access.
+ */
+ public static final int DISABLED_NO_INTERNET_TEMPORARY = 6;
+ /**
+ * This network is disabled because we started WPS
+ */
+ public static final int DISABLED_WPS_START = 7;
+ /**
+ * This network is disabled because EAP-TLS failure
+ */
+ public static final int DISABLED_TLS_VERSION_MISMATCH = 8;
+ // Values above are for temporary disablement; values below are for permanent disablement.
+ /**
+ * This network is disabled due to absence of user credentials
+ */
+ public static final int DISABLED_AUTHENTICATION_NO_CREDENTIALS = 9;
+ /**
+ * This network is permanently disabled because it has no Internet access and user does not
+ * want to stay connected.
+ */
+ public static final int DISABLED_NO_INTERNET_PERMANENT = 10;
+ /**
+ * This network is disabled due to WifiManager disable it explicitly
+ */
+ public static final int DISABLED_BY_WIFI_MANAGER = 11;
+ /**
+ * This network is disabled due to user switching
+ */
+ public static final int DISABLED_DUE_TO_USER_SWITCH = 12;
+ /**
+ * This network is disabled due to wrong password
+ */
+ public static final int DISABLED_BY_WRONG_PASSWORD = 13;
+ /**
+ * This network is disabled because service is not subscribed
+ */
+ public static final int DISABLED_AUTHENTICATION_NO_SUBSCRIPTION = 14;
+ /**
+ * This Maximum disable reason value
+ */
+ public static final int NETWORK_SELECTION_DISABLED_MAX = 15;
+
+ /**
+ * Quality network selection disable reason String (for debug purpose)
+ */
+ public static final String[] QUALITY_NETWORK_SELECTION_DISABLE_REASON = {
+ "NETWORK_SELECTION_ENABLE",
+ "NETWORK_SELECTION_DISABLED_BAD_LINK", // deprecated
+ "NETWORK_SELECTION_DISABLED_ASSOCIATION_REJECTION ",
+ "NETWORK_SELECTION_DISABLED_AUTHENTICATION_FAILURE",
+ "NETWORK_SELECTION_DISABLED_DHCP_FAILURE",
+ "NETWORK_SELECTION_DISABLED_DNS_FAILURE",
+ "NETWORK_SELECTION_DISABLED_NO_INTERNET_TEMPORARY",
+ "NETWORK_SELECTION_DISABLED_WPS_START",
+ "NETWORK_SELECTION_DISABLED_TLS_VERSION",
+ "NETWORK_SELECTION_DISABLED_AUTHENTICATION_NO_CREDENTIALS",
+ "NETWORK_SELECTION_DISABLED_NO_INTERNET_PERMANENT",
+ "NETWORK_SELECTION_DISABLED_BY_WIFI_MANAGER",
+ "NETWORK_SELECTION_DISABLED_BY_USER_SWITCH",
+ "NETWORK_SELECTION_DISABLED_BY_WRONG_PASSWORD",
+ "NETWORK_SELECTION_DISABLED_AUTHENTICATION_NO_SUBSCRIPTION"
+ };
+
+ /**
+ * Invalid time stamp for network selection disable
+ */
+ public static final long INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP = -1L;
+
+ /**
+ * This constant indicates the current configuration has connect choice set
+ */
+ private static final int CONNECT_CHOICE_EXISTS = 1;
+
+ /**
+ * This constant indicates the current configuration does not have connect choice set
+ */
+ private static final int CONNECT_CHOICE_NOT_EXISTS = -1;
+
+ // fields for QualityNetwork Selection
+ /**
+ * Network selection status, should be in one of three status: enable, temporaily disabled
+ * or permanently disabled
+ */
+ private int mStatus;
+
+ /**
+ * Reason for disable this network
+ */
+ private int mNetworkSelectionDisableReason;
+
+ /**
+ * Last time we temporarily disabled the configuration
+ */
+ private long mTemporarilyDisabledTimestamp = INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP;
+
+ /**
+ * counter for each Network selection disable reason
+ */
+ private int[] mNetworkSeclectionDisableCounter = new int[NETWORK_SELECTION_DISABLED_MAX];
+
+ /**
+ * Connect Choice over this configuration
+ *
+ * When current wifi configuration is visible to the user but user explicitly choose to
+ * connect to another network X, the another networks X's configure key will be stored here.
+ * We will consider user has a preference of X over this network. And in the future,
+ * network selection will always give X a higher preference over this configuration.
+ * configKey is : "SSID"-WEP-WPA_PSK-WPA_EAP
+ */
+ private String mConnectChoice;
+
+ /**
+ * The system timestamp when we records the connectChoice. This value is obtained from
+ * System.currentTimeMillis
+ */
+ private long mConnectChoiceTimestamp = INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP;
+
+ /**
+ * Used to cache the temporary candidate during the network selection procedure. It will be
+ * kept updating once a new scan result has a higher score than current one
+ */
+ private ScanResult mCandidate;
+
+ /**
+ * Used to cache the score of the current temporary candidate during the network
+ * selection procedure.
+ */
+ private int mCandidateScore;
+
+ /**
+ * Indicate whether this network is visible in latest Qualified Network Selection. This
+ * means there is scan result found related to this Configuration and meet the minimum
+ * requirement. The saved network need not join latest Qualified Network Selection. For
+ * example, it is disabled. True means network is visible in latest Qualified Network
+ * Selection and false means network is invisible
+ */
+ private boolean mSeenInLastQualifiedNetworkSelection;
+
+ /**
+ * Boolean indicating if we have ever successfully connected to this network.
+ *
+ * This value will be set to true upon a successful connection.
+ * This value will be set to false if a previous value was not stored in the config or if
+ * the credentials are updated (ex. a password change).
+ */
+ private boolean mHasEverConnected;
+
+ /**
+ * Boolean indicating whether {@link com.android.server.wifi.RecommendedNetworkEvaluator}
+ * chose not to connect to this network in the last qualified network selection process.
+ */
+ private boolean mNotRecommended;
+
+ /**
+ * Set whether {@link com.android.server.wifi.RecommendedNetworkEvaluator} does not
+ * recommend connecting to this network.
+ */
+ public void setNotRecommended(boolean notRecommended) {
+ mNotRecommended = notRecommended;
+ }
+
+ /**
+ * Returns whether {@link com.android.server.wifi.RecommendedNetworkEvaluator} does not
+ * recommend connecting to this network.
+ */
+ public boolean isNotRecommended() {
+ return mNotRecommended;
+ }
+
+ /**
+ * set whether this network is visible in latest Qualified Network Selection
+ * @param seen value set to candidate
+ */
+ public void setSeenInLastQualifiedNetworkSelection(boolean seen) {
+ mSeenInLastQualifiedNetworkSelection = seen;
+ }
+
+ /**
+ * get whether this network is visible in latest Qualified Network Selection
+ * @return returns true -- network is visible in latest Qualified Network Selection
+ * false -- network is invisible in latest Qualified Network Selection
+ */
+ public boolean getSeenInLastQualifiedNetworkSelection() {
+ return mSeenInLastQualifiedNetworkSelection;
+ }
+ /**
+ * set the temporary candidate of current network selection procedure
+ * @param scanCandidate {@link ScanResult} the candidate set to mCandidate
+ */
+ public void setCandidate(ScanResult scanCandidate) {
+ mCandidate = scanCandidate;
+ }
+
+ /**
+ * get the temporary candidate of current network selection procedure
+ * @return returns {@link ScanResult} temporary candidate of current network selection
+ * procedure
+ */
+ public ScanResult getCandidate() {
+ return mCandidate;
+ }
+
+ /**
+ * set the score of the temporary candidate of current network selection procedure
+ * @param score value set to mCandidateScore
+ */
+ public void setCandidateScore(int score) {
+ mCandidateScore = score;
+ }
+
+ /**
+ * get the score of the temporary candidate of current network selection procedure
+ * @return returns score of the temporary candidate of current network selection procedure
+ */
+ public int getCandidateScore() {
+ return mCandidateScore;
+ }
+
+ /**
+ * get user preferred choice over this configuration
+ *@return returns configKey of user preferred choice over this configuration
+ */
+ public String getConnectChoice() {
+ return mConnectChoice;
+ }
+
+ /**
+ * set user preferred choice over this configuration
+ * @param newConnectChoice, the configKey of user preferred choice over this configuration
+ */
+ public void setConnectChoice(String newConnectChoice) {
+ mConnectChoice = newConnectChoice;
+ }
+
+ /**
+ * get the timeStamp when user select a choice over this configuration
+ * @return returns when current connectChoice is set (time from System.currentTimeMillis)
+ */
+ public long getConnectChoiceTimestamp() {
+ return mConnectChoiceTimestamp;
+ }
+
+ /**
+ * set the timeStamp when user select a choice over this configuration
+ * @param timeStamp, the timestamp set to connectChoiceTimestamp, expected timestamp should
+ * be obtained from System.currentTimeMillis
+ */
+ public void setConnectChoiceTimestamp(long timeStamp) {
+ mConnectChoiceTimestamp = timeStamp;
+ }
+
+ /**
+ * get current Quality network selection status
+ * @return returns current Quality network selection status in String (for debug purpose)
+ */
+ public String getNetworkStatusString() {
+ return QUALITY_NETWORK_SELECTION_STATUS[mStatus];
+ }
+
+ public void setHasEverConnected(boolean value) {
+ mHasEverConnected = value;
+ }
+
+ public boolean getHasEverConnected() {
+ return mHasEverConnected;
+ }
+
+ public NetworkSelectionStatus() {
+ // previously stored configs will not have this parameter, so we default to false.
+ mHasEverConnected = false;
+ };
+
+ /**
+ * @param reason specific error reason
+ * @return corresponding network disable reason String (for debug purpose)
+ */
+ public static String getNetworkDisableReasonString(int reason) {
+ if (reason >= NETWORK_SELECTION_ENABLE && reason < NETWORK_SELECTION_DISABLED_MAX) {
+ return QUALITY_NETWORK_SELECTION_DISABLE_REASON[reason];
+ } else {
+ return null;
+ }
+ }
+ /**
+ * get current network disable reason
+ * @return current network disable reason in String (for debug purpose)
+ */
+ public String getNetworkDisableReasonString() {
+ return QUALITY_NETWORK_SELECTION_DISABLE_REASON[mNetworkSelectionDisableReason];
+ }
+
+ /**
+ * get current network network selection status
+ * @return return current network network selection status
+ */
+ public int getNetworkSelectionStatus() {
+ return mStatus;
+ }
+ /**
+ * @return whether current network is enabled to join network selection
+ */
+ public boolean isNetworkEnabled() {
+ return mStatus == NETWORK_SELECTION_ENABLED;
+ }
+
+ /**
+ * @return whether current network is temporary disabled
+ */
+ public boolean isNetworkTemporaryDisabled() {
+ return mStatus == NETWORK_SELECTION_TEMPORARY_DISABLED;
+ }
+
+ /**
+ * @return returns whether current network is permanently disabled
+ */
+ public boolean isNetworkPermanentlyDisabled() {
+ return mStatus == NETWORK_SELECTION_PERMANENTLY_DISABLED;
+ }
+
+ /**
+ * set current networ work selection status
+ * @param status network selection status to set
+ */
+ public void setNetworkSelectionStatus(int status) {
+ if (status >= 0 && status < NETWORK_SELECTION_STATUS_MAX) {
+ mStatus = status;
+ }
+ }
+
+ /**
+ * @return returns current network's disable reason
+ */
+ public int getNetworkSelectionDisableReason() {
+ return mNetworkSelectionDisableReason;
+ }
+
+ /**
+ * set Network disable reason
+ * @param reason Network disable reason
+ */
+ public void setNetworkSelectionDisableReason(int reason) {
+ if (reason >= 0 && reason < NETWORK_SELECTION_DISABLED_MAX) {
+ mNetworkSelectionDisableReason = reason;
+ } else {
+ throw new IllegalArgumentException("Illegal reason value: " + reason);
+ }
+ }
+
+ /**
+ * check whether network is disabled by this reason
+ * @param reason a specific disable reason
+ * @return true -- network is disabled for this reason
+ * false -- network is not disabled for this reason
+ */
+ public boolean isDisabledByReason(int reason) {
+ return mNetworkSelectionDisableReason == reason;
+ }
+
+ /**
+ * @param timeStamp Set when current network is disabled in millisecond since January 1,
+ * 1970 00:00:00.0 UTC
+ */
+ public void setDisableTime(long timeStamp) {
+ mTemporarilyDisabledTimestamp = timeStamp;
+ }
+
+ /**
+ * @return returns when current network is disabled in millisecond since January 1,
+ * 1970 00:00:00.0 UTC
+ */
+ public long getDisableTime() {
+ return mTemporarilyDisabledTimestamp;
+ }
+
+ /**
+ * get the disable counter of a specific reason
+ * @param reason specific failure reason
+ * @exception throw IllegalArgumentException for illegal input
+ * @return counter number for specific error reason.
+ */
+ public int getDisableReasonCounter(int reason) {
+ if (reason >= NETWORK_SELECTION_ENABLE && reason < NETWORK_SELECTION_DISABLED_MAX) {
+ return mNetworkSeclectionDisableCounter[reason];
+ } else {
+ throw new IllegalArgumentException("Illegal reason value: " + reason);
+ }
+ }
+
+ /**
+ * set the counter of a specific failure reason
+ * @param reason reason for disable error
+ * @param value the counter value for this specific reason
+ * @exception throw IllegalArgumentException for illegal input
+ */
+ public void setDisableReasonCounter(int reason, int value) {
+ if (reason >= NETWORK_SELECTION_ENABLE && reason < NETWORK_SELECTION_DISABLED_MAX) {
+ mNetworkSeclectionDisableCounter[reason] = value;
+ } else {
+ throw new IllegalArgumentException("Illegal reason value: " + reason);
+ }
+ }
+
+ /**
+ * increment the counter of a specific failure reason
+ * @param reason a specific failure reason
+ * @exception throw IllegalArgumentException for illegal input
+ */
+ public void incrementDisableReasonCounter(int reason) {
+ if (reason >= NETWORK_SELECTION_ENABLE && reason < NETWORK_SELECTION_DISABLED_MAX) {
+ mNetworkSeclectionDisableCounter[reason]++;
+ } else {
+ throw new IllegalArgumentException("Illegal reason value: " + reason);
+ }
+ }
+
+ /**
+ * clear the counter of a specific failure reason
+ * @hide
+ * @param reason a specific failure reason
+ * @exception throw IllegalArgumentException for illegal input
+ */
+ public void clearDisableReasonCounter(int reason) {
+ if (reason >= NETWORK_SELECTION_ENABLE && reason < NETWORK_SELECTION_DISABLED_MAX) {
+ mNetworkSeclectionDisableCounter[reason] = NETWORK_SELECTION_ENABLE;
+ } else {
+ throw new IllegalArgumentException("Illegal reason value: " + reason);
+ }
+ }
+
+ /**
+ * clear all the failure reason counters
+ */
+ public void clearDisableReasonCounter() {
+ Arrays.fill(mNetworkSeclectionDisableCounter, NETWORK_SELECTION_ENABLE);
+ }
+
+ /**
+ * BSSID for connection to this network (through network selection procedure)
+ */
+ private String mNetworkSelectionBSSID;
+
+ /**
+ * get current network Selection BSSID
+ * @return current network Selection BSSID
+ */
+ public String getNetworkSelectionBSSID() {
+ return mNetworkSelectionBSSID;
+ }
+
+ /**
+ * set network Selection BSSID
+ * @param bssid The target BSSID for assocaition
+ */
+ public void setNetworkSelectionBSSID(String bssid) {
+ mNetworkSelectionBSSID = bssid;
+ }
+
+ public void copy(NetworkSelectionStatus source) {
+ mStatus = source.mStatus;
+ mNetworkSelectionDisableReason = source.mNetworkSelectionDisableReason;
+ for (int index = NETWORK_SELECTION_ENABLE; index < NETWORK_SELECTION_DISABLED_MAX;
+ index++) {
+ mNetworkSeclectionDisableCounter[index] =
+ source.mNetworkSeclectionDisableCounter[index];
+ }
+ mTemporarilyDisabledTimestamp = source.mTemporarilyDisabledTimestamp;
+ mNetworkSelectionBSSID = source.mNetworkSelectionBSSID;
+ setSeenInLastQualifiedNetworkSelection(source.getSeenInLastQualifiedNetworkSelection());
+ setCandidate(source.getCandidate());
+ setCandidateScore(source.getCandidateScore());
+ setConnectChoice(source.getConnectChoice());
+ setConnectChoiceTimestamp(source.getConnectChoiceTimestamp());
+ setHasEverConnected(source.getHasEverConnected());
+ setNotRecommended(source.isNotRecommended());
+ }
+
+ public void writeToParcel(Parcel dest) {
+ dest.writeInt(getNetworkSelectionStatus());
+ dest.writeInt(getNetworkSelectionDisableReason());
+ for (int index = NETWORK_SELECTION_ENABLE; index < NETWORK_SELECTION_DISABLED_MAX;
+ index++) {
+ dest.writeInt(getDisableReasonCounter(index));
+ }
+ dest.writeLong(getDisableTime());
+ dest.writeString(getNetworkSelectionBSSID());
+ if (getConnectChoice() != null) {
+ dest.writeInt(CONNECT_CHOICE_EXISTS);
+ dest.writeString(getConnectChoice());
+ dest.writeLong(getConnectChoiceTimestamp());
+ } else {
+ dest.writeInt(CONNECT_CHOICE_NOT_EXISTS);
+ }
+ dest.writeInt(getHasEverConnected() ? 1 : 0);
+ dest.writeInt(isNotRecommended() ? 1 : 0);
+ }
+
+ public void readFromParcel(Parcel in) {
+ setNetworkSelectionStatus(in.readInt());
+ setNetworkSelectionDisableReason(in.readInt());
+ for (int index = NETWORK_SELECTION_ENABLE; index < NETWORK_SELECTION_DISABLED_MAX;
+ index++) {
+ setDisableReasonCounter(index, in.readInt());
+ }
+ setDisableTime(in.readLong());
+ setNetworkSelectionBSSID(in.readString());
+ if (in.readInt() == CONNECT_CHOICE_EXISTS) {
+ setConnectChoice(in.readString());
+ setConnectChoiceTimestamp(in.readLong());
+ } else {
+ setConnectChoice(null);
+ setConnectChoiceTimestamp(INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
+ }
+ setHasEverConnected(in.readInt() != 0);
+ setNotRecommended(in.readInt() != 0);
+ }
+ }
+
+ /**
+ * @hide
+ * network selection related member
+ */
+ private NetworkSelectionStatus mNetworkSelectionStatus = new NetworkSelectionStatus();
+
+ /**
+ * @hide
+ * This class is intended to store extra failure reason information for the most recent
+ * connection attempt, so that it may be surfaced to the settings UI
+ */
+ public static class RecentFailure {
+
+ /**
+ * No recent failure, or no specific reason given for the recent connection failure
+ */
+ public static final int NONE = 0;
+ /**
+ * Connection to this network recently failed due to Association Rejection Status 17
+ * (AP is full)
+ */
+ public static final int STATUS_AP_UNABLE_TO_HANDLE_NEW_STA = 17;
+ /**
+ * Association Rejection Status code (NONE for success/non-association-rejection-fail)
+ */
+ private int mAssociationStatus = NONE;
+
+ /**
+ * @param status the association status code for the recent failure
+ */
+ public void setAssociationStatus(int status) {
+ mAssociationStatus = status;
+ }
+ /**
+ * Sets the RecentFailure to NONE
+ */
+ public void clear() {
+ mAssociationStatus = NONE;
+ }
+ /**
+ * Get the recent failure code
+ */
+ public int getAssociationStatus() {
+ return mAssociationStatus;
+ }
+ }
+
+ /**
+ * @hide
+ * RecentFailure member
+ */
+ final public RecentFailure recentFailure = new RecentFailure();
+
+ /**
+ * @hide
+ * @return network selection status
+ */
+ public NetworkSelectionStatus getNetworkSelectionStatus() {
+ return mNetworkSelectionStatus;
+ }
+
+ /**
+ * Set the network selection status
+ * @hide
+ */
+ public void setNetworkSelectionStatus(NetworkSelectionStatus status) {
+ mNetworkSelectionStatus = status;
+ }
+
+ /**
+ * @hide
+ * Linked Configurations: represent the set of Wificonfigurations that are equivalent
+ * regarding roaming and auto-joining.
+ * The linked configuration may or may not have same SSID, and may or may not have same
+ * credentials.
+ * For instance, linked configurations will have same defaultGwMacAddress or same dhcp server.
+ */
+ public HashMap<String, Integer> linkedConfigurations;
+
+ public WifiConfiguration() {
+ networkId = INVALID_NETWORK_ID;
+ SSID = null;
+ BSSID = null;
+ FQDN = null;
+ roamingConsortiumIds = new long[0];
+ priority = 0;
+ hiddenSSID = false;
+ allowedKeyManagement = new BitSet();
+ allowedProtocols = new BitSet();
+ allowedAuthAlgorithms = new BitSet();
+ allowedPairwiseCiphers = new BitSet();
+ allowedGroupCiphers = new BitSet();
+ allowedGroupManagementCiphers = new BitSet();
+ allowedSuiteBCiphers = new BitSet();
+ wepKeys = new String[4];
+ for (int i = 0; i < wepKeys.length; i++) {
+ wepKeys[i] = null;
+ }
+ enterpriseConfig = new WifiEnterpriseConfig();
+ selfAdded = false;
+ didSelfAdd = false;
+ ephemeral = false;
+ osu = false;
+ trusted = true; // Networks are considered trusted by default.
+ fromWifiNetworkSuggestion = false;
+ fromWifiNetworkSpecifier = false;
+ meteredHint = false;
+ meteredOverride = METERED_OVERRIDE_NONE;
+ useExternalScores = false;
+ validatedInternetAccess = false;
+ mIpConfiguration = new IpConfiguration();
+ lastUpdateUid = -1;
+ creatorUid = -1;
+ shared = true;
+ dtimInterval = 0;
+ mRandomizedMacAddress = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS);
+ }
+
+ /**
+ * Identify if this configuration represents a Passpoint network
+ */
+ public boolean isPasspoint() {
+ return !TextUtils.isEmpty(FQDN)
+ && !TextUtils.isEmpty(providerFriendlyName)
+ && enterpriseConfig != null
+ && enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE;
+ }
+
+ /**
+ * Helper function, identify if a configuration is linked
+ * @hide
+ */
+ public boolean isLinked(WifiConfiguration config) {
+ if (config != null) {
+ if (config.linkedConfigurations != null && linkedConfigurations != null) {
+ if (config.linkedConfigurations.get(configKey()) != null
+ && linkedConfigurations.get(config.configKey()) != null) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Helper function, idenfity if a configuration should be treated as an enterprise network
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public boolean isEnterprise() {
+ return (allowedKeyManagement.get(KeyMgmt.WPA_EAP)
+ || allowedKeyManagement.get(KeyMgmt.IEEE8021X)
+ || allowedKeyManagement.get(KeyMgmt.SUITE_B_192))
+ && enterpriseConfig != null
+ && enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sbuf = new StringBuilder();
+ if (this.status == WifiConfiguration.Status.CURRENT) {
+ sbuf.append("* ");
+ } else if (this.status == WifiConfiguration.Status.DISABLED) {
+ sbuf.append("- DSBLE ");
+ }
+ sbuf.append("ID: ").append(this.networkId).append(" SSID: ").append(this.SSID).
+ append(" PROVIDER-NAME: ").append(this.providerFriendlyName).
+ append(" BSSID: ").append(this.BSSID).append(" FQDN: ").append(this.FQDN)
+ .append(" PRIO: ").append(this.priority)
+ .append(" HIDDEN: ").append(this.hiddenSSID)
+ .append(" PMF: ").append(this.requirePMF)
+ .append('\n');
+
+
+ sbuf.append(" NetworkSelectionStatus ")
+ .append(mNetworkSelectionStatus.getNetworkStatusString() + "\n");
+ if (mNetworkSelectionStatus.getNetworkSelectionDisableReason() > 0) {
+ sbuf.append(" mNetworkSelectionDisableReason ")
+ .append(mNetworkSelectionStatus.getNetworkDisableReasonString() + "\n");
+
+ for (int index = mNetworkSelectionStatus.NETWORK_SELECTION_ENABLE;
+ index < mNetworkSelectionStatus.NETWORK_SELECTION_DISABLED_MAX; index++) {
+ if (mNetworkSelectionStatus.getDisableReasonCounter(index) != 0) {
+ sbuf.append(NetworkSelectionStatus.getNetworkDisableReasonString(index)
+ + " counter:" + mNetworkSelectionStatus.getDisableReasonCounter(index)
+ + "\n");
+ }
+ }
+ }
+ if (mNetworkSelectionStatus.getConnectChoice() != null) {
+ sbuf.append(" connect choice: ").append(mNetworkSelectionStatus.getConnectChoice());
+ sbuf.append(" connect choice set time: ")
+ .append(TimeUtils.logTimeOfDay(
+ mNetworkSelectionStatus.getConnectChoiceTimestamp()));
+ }
+ sbuf.append(" hasEverConnected: ")
+ .append(mNetworkSelectionStatus.getHasEverConnected()).append("\n");
+
+ if (this.numAssociation > 0) {
+ sbuf.append(" numAssociation ").append(this.numAssociation).append("\n");
+ }
+ if (this.numNoInternetAccessReports > 0) {
+ sbuf.append(" numNoInternetAccessReports ");
+ sbuf.append(this.numNoInternetAccessReports).append("\n");
+ }
+ if (this.updateTime != null) {
+ sbuf.append(" update ").append(this.updateTime).append("\n");
+ }
+ if (this.creationTime != null) {
+ sbuf.append(" creation ").append(this.creationTime).append("\n");
+ }
+ if (this.didSelfAdd) sbuf.append(" didSelfAdd");
+ if (this.selfAdded) sbuf.append(" selfAdded");
+ if (this.validatedInternetAccess) sbuf.append(" validatedInternetAccess");
+ if (this.ephemeral) sbuf.append(" ephemeral");
+ if (this.osu) sbuf.append(" osu");
+ if (this.trusted) sbuf.append(" trusted");
+ if (this.fromWifiNetworkSuggestion) sbuf.append(" fromWifiNetworkSuggestion");
+ if (this.fromWifiNetworkSpecifier) sbuf.append(" fromWifiNetworkSpecifier");
+ if (this.meteredHint) sbuf.append(" meteredHint");
+ if (this.useExternalScores) sbuf.append(" useExternalScores");
+ if (this.didSelfAdd || this.selfAdded || this.validatedInternetAccess
+ || this.ephemeral || this.trusted || this.fromWifiNetworkSuggestion
+ || this.fromWifiNetworkSpecifier || this.meteredHint || this.useExternalScores) {
+ sbuf.append("\n");
+ }
+ if (this.meteredOverride != METERED_OVERRIDE_NONE) {
+ sbuf.append(" meteredOverride ").append(meteredOverride).append("\n");
+ }
+ sbuf.append(" macRandomizationSetting: ").append(macRandomizationSetting).append("\n");
+ sbuf.append(" mRandomizedMacAddress: ").append(mRandomizedMacAddress).append("\n");
+ sbuf.append(" KeyMgmt:");
+ for (int k = 0; k < this.allowedKeyManagement.size(); k++) {
+ if (this.allowedKeyManagement.get(k)) {
+ sbuf.append(" ");
+ if (k < KeyMgmt.strings.length) {
+ sbuf.append(KeyMgmt.strings[k]);
+ } else {
+ sbuf.append("??");
+ }
+ }
+ }
+ sbuf.append(" Protocols:");
+ for (int p = 0; p < this.allowedProtocols.size(); p++) {
+ if (this.allowedProtocols.get(p)) {
+ sbuf.append(" ");
+ if (p < Protocol.strings.length) {
+ sbuf.append(Protocol.strings[p]);
+ } else {
+ sbuf.append("??");
+ }
+ }
+ }
+ sbuf.append('\n');
+ sbuf.append(" AuthAlgorithms:");
+ for (int a = 0; a < this.allowedAuthAlgorithms.size(); a++) {
+ if (this.allowedAuthAlgorithms.get(a)) {
+ sbuf.append(" ");
+ if (a < AuthAlgorithm.strings.length) {
+ sbuf.append(AuthAlgorithm.strings[a]);
+ } else {
+ sbuf.append("??");
+ }
+ }
+ }
+ sbuf.append('\n');
+ sbuf.append(" PairwiseCiphers:");
+ for (int pc = 0; pc < this.allowedPairwiseCiphers.size(); pc++) {
+ if (this.allowedPairwiseCiphers.get(pc)) {
+ sbuf.append(" ");
+ if (pc < PairwiseCipher.strings.length) {
+ sbuf.append(PairwiseCipher.strings[pc]);
+ } else {
+ sbuf.append("??");
+ }
+ }
+ }
+ sbuf.append('\n');
+ sbuf.append(" GroupCiphers:");
+ for (int gc = 0; gc < this.allowedGroupCiphers.size(); gc++) {
+ if (this.allowedGroupCiphers.get(gc)) {
+ sbuf.append(" ");
+ if (gc < GroupCipher.strings.length) {
+ sbuf.append(GroupCipher.strings[gc]);
+ } else {
+ sbuf.append("??");
+ }
+ }
+ }
+ sbuf.append('\n');
+ sbuf.append(" GroupMgmtCiphers:");
+ for (int gmc = 0; gmc < this.allowedGroupManagementCiphers.size(); gmc++) {
+ if (this.allowedGroupManagementCiphers.get(gmc)) {
+ sbuf.append(" ");
+ if (gmc < GroupMgmtCipher.strings.length) {
+ sbuf.append(GroupMgmtCipher.strings[gmc]);
+ } else {
+ sbuf.append("??");
+ }
+ }
+ }
+ sbuf.append('\n');
+ sbuf.append(" SuiteBCiphers:");
+ for (int sbc = 0; sbc < this.allowedSuiteBCiphers.size(); sbc++) {
+ if (this.allowedSuiteBCiphers.get(sbc)) {
+ sbuf.append(" ");
+ if (sbc < SuiteBCipher.strings.length) {
+ sbuf.append(SuiteBCipher.strings[sbc]);
+ } else {
+ sbuf.append("??");
+ }
+ }
+ }
+ sbuf.append('\n').append(" PSK/SAE: ");
+ if (this.preSharedKey != null) {
+ sbuf.append('*');
+ }
+
+ sbuf.append("\nEnterprise config:\n");
+ sbuf.append(enterpriseConfig);
+
+ sbuf.append("IP config:\n");
+ sbuf.append(mIpConfiguration.toString());
+
+ if (mNetworkSelectionStatus.getNetworkSelectionBSSID() != null) {
+ sbuf.append(" networkSelectionBSSID="
+ + mNetworkSelectionStatus.getNetworkSelectionBSSID());
+ }
+ long now_ms = SystemClock.elapsedRealtime();
+ if (mNetworkSelectionStatus.getDisableTime() != NetworkSelectionStatus
+ .INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP) {
+ sbuf.append('\n');
+ long diff = now_ms - mNetworkSelectionStatus.getDisableTime();
+ if (diff <= 0) {
+ sbuf.append(" blackListed since <incorrect>");
+ } else {
+ sbuf.append(" blackListed: ").append(Long.toString(diff / 1000)).append("sec ");
+ }
+ }
+ if (creatorUid != 0) sbuf.append(" cuid=" + creatorUid);
+ if (creatorName != null) sbuf.append(" cname=" + creatorName);
+ if (lastUpdateUid != 0) sbuf.append(" luid=" + lastUpdateUid);
+ if (lastUpdateName != null) sbuf.append(" lname=" + lastUpdateName);
+ if (updateIdentifier != null) sbuf.append(" updateIdentifier=" + updateIdentifier);
+ sbuf.append(" lcuid=" + lastConnectUid);
+ sbuf.append(" userApproved=" + userApprovedAsString(userApproved));
+ sbuf.append(" noInternetAccessExpected=" + noInternetAccessExpected);
+ sbuf.append(" ");
+
+ if (this.lastConnected != 0) {
+ sbuf.append('\n');
+ sbuf.append("lastConnected: ").append(TimeUtils.logTimeOfDay(this.lastConnected));
+ sbuf.append(" ");
+ }
+ sbuf.append('\n');
+ if (this.linkedConfigurations != null) {
+ for (String key : this.linkedConfigurations.keySet()) {
+ sbuf.append(" linked: ").append(key);
+ sbuf.append('\n');
+ }
+ }
+ sbuf.append("recentFailure: ").append("Association Rejection code: ")
+ .append(recentFailure.getAssociationStatus()).append("\n");
+ return sbuf.toString();
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public String getPrintableSsid() {
+ if (SSID == null) return "";
+ final int length = SSID.length();
+ if (length > 2 && (SSID.charAt(0) == '"') && SSID.charAt(length - 1) == '"') {
+ return SSID.substring(1, length - 1);
+ }
+
+ /** The ascii-encoded string format is P"<ascii-encoded-string>"
+ * The decoding is implemented in the supplicant for a newly configured
+ * network.
+ */
+ if (length > 3 && (SSID.charAt(0) == 'P') && (SSID.charAt(1) == '"') &&
+ (SSID.charAt(length-1) == '"')) {
+ WifiSsid wifiSsid = WifiSsid.createFromAsciiEncoded(
+ SSID.substring(2, length - 1));
+ return wifiSsid.toString();
+ }
+ return SSID;
+ }
+
+ /** @hide **/
+ public static String userApprovedAsString(int userApproved) {
+ switch (userApproved) {
+ case USER_APPROVED:
+ return "USER_APPROVED";
+ case USER_BANNED:
+ return "USER_BANNED";
+ case USER_UNSPECIFIED:
+ return "USER_UNSPECIFIED";
+ default:
+ return "INVALID";
+ }
+ }
+
+ /**
+ * Get an identifier for associating credentials with this config
+ * @param current configuration contains values for additional fields
+ * that are not part of this configuration. Used
+ * when a config with some fields is passed by an application.
+ * @throws IllegalStateException if config is invalid for key id generation
+ * @hide
+ */
+ public String getKeyIdForCredentials(WifiConfiguration current) {
+ String keyMgmt = "";
+
+ try {
+ // Get current config details for fields that are not initialized
+ if (TextUtils.isEmpty(SSID)) SSID = current.SSID;
+ if (allowedKeyManagement.cardinality() == 0) {
+ allowedKeyManagement = current.allowedKeyManagement;
+ }
+ if (allowedKeyManagement.get(KeyMgmt.WPA_EAP)) {
+ keyMgmt += KeyMgmt.strings[KeyMgmt.WPA_EAP];
+ }
+ if (allowedKeyManagement.get(KeyMgmt.OSEN)) {
+ keyMgmt += KeyMgmt.strings[KeyMgmt.OSEN];
+ }
+ if (allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
+ keyMgmt += KeyMgmt.strings[KeyMgmt.IEEE8021X];
+ }
+ if (allowedKeyManagement.get(KeyMgmt.SUITE_B_192)) {
+ keyMgmt += KeyMgmt.strings[KeyMgmt.SUITE_B_192];
+ }
+
+ if (TextUtils.isEmpty(keyMgmt)) {
+ throw new IllegalStateException("Not an EAP network");
+ }
+
+ return trimStringForKeyId(SSID) + "_" + keyMgmt + "_" +
+ trimStringForKeyId(enterpriseConfig.getKeyId(current != null ?
+ current.enterpriseConfig : null));
+ } catch (NullPointerException e) {
+ throw new IllegalStateException("Invalid config details");
+ }
+ }
+
+ private String trimStringForKeyId(String string) {
+ // Remove quotes and spaces
+ return string.replace("\"", "").replace(" ", "");
+ }
+
+ private static BitSet readBitSet(Parcel src) {
+ int cardinality = src.readInt();
+
+ BitSet set = new BitSet();
+ for (int i = 0; i < cardinality; i++) {
+ set.set(src.readInt());
+ }
+
+ return set;
+ }
+
+ private static void writeBitSet(Parcel dest, BitSet set) {
+ int nextSetBit = -1;
+
+ dest.writeInt(set.cardinality());
+
+ while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) {
+ dest.writeInt(nextSetBit);
+ }
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public int getAuthType() {
+ if (allowedKeyManagement.cardinality() > 1) {
+ throw new IllegalStateException("More than one auth type set");
+ }
+ if (allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
+ return KeyMgmt.WPA_PSK;
+ } else if (allowedKeyManagement.get(KeyMgmt.WPA2_PSK)) {
+ return KeyMgmt.WPA2_PSK;
+ } else if (allowedKeyManagement.get(KeyMgmt.WPA_EAP)) {
+ return KeyMgmt.WPA_EAP;
+ } else if (allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
+ return KeyMgmt.IEEE8021X;
+ } else if (allowedKeyManagement.get(KeyMgmt.SAE)) {
+ return KeyMgmt.SAE;
+ } else if (allowedKeyManagement.get(KeyMgmt.OWE)) {
+ return KeyMgmt.OWE;
+ } else if (allowedKeyManagement.get(KeyMgmt.SUITE_B_192)) {
+ return KeyMgmt.SUITE_B_192;
+ }
+ return KeyMgmt.NONE;
+ }
+
+ /* @hide
+ * Cache the config key, this seems useful as a speed up since a lot of
+ * lookups in the config store are done and based on this key.
+ */
+ String mCachedConfigKey;
+
+ /** @hide
+ * return the string used to calculate the hash in WifiConfigStore
+ * and uniquely identify this WifiConfiguration
+ */
+ public String configKey(boolean allowCached) {
+ String key;
+ if (allowCached && mCachedConfigKey != null) {
+ key = mCachedConfigKey;
+ } else if (providerFriendlyName != null) {
+ key = FQDN + KeyMgmt.strings[KeyMgmt.WPA_EAP];
+ if (!shared) {
+ key += "-" + Integer.toString(UserHandle.getUserId(creatorUid));
+ }
+ } else {
+ key = getSsidAndSecurityTypeString();
+ if (!shared) {
+ key += "-" + Integer.toString(UserHandle.getUserId(creatorUid));
+ }
+ mCachedConfigKey = key;
+ }
+ return key;
+ }
+
+ /** @hide
+ * return the SSID + security type in String format.
+ */
+ public String getSsidAndSecurityTypeString() {
+ String key;
+ if (allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
+ key = SSID + KeyMgmt.strings[KeyMgmt.WPA_PSK];
+ } else if (allowedKeyManagement.get(KeyMgmt.WPA_EAP)
+ || allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
+ key = SSID + KeyMgmt.strings[KeyMgmt.WPA_EAP];
+ } else if (wepKeys[0] != null) {
+ key = SSID + "WEP";
+ } else if (allowedKeyManagement.get(KeyMgmt.OWE)) {
+ key = SSID + KeyMgmt.strings[KeyMgmt.OWE];
+ } else if (allowedKeyManagement.get(KeyMgmt.SAE)) {
+ key = SSID + KeyMgmt.strings[KeyMgmt.SAE];
+ } else if (allowedKeyManagement.get(KeyMgmt.SUITE_B_192)) {
+ key = SSID + KeyMgmt.strings[KeyMgmt.SUITE_B_192];
+ } else {
+ key = SSID + KeyMgmt.strings[KeyMgmt.NONE];
+ }
+ return key;
+ }
+
+ /** @hide
+ * get configKey, force calculating the config string
+ */
+ public String configKey() {
+ return configKey(false);
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public IpConfiguration getIpConfiguration() {
+ return mIpConfiguration;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public void setIpConfiguration(IpConfiguration ipConfiguration) {
+ if (ipConfiguration == null) ipConfiguration = new IpConfiguration();
+ mIpConfiguration = ipConfiguration;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public StaticIpConfiguration getStaticIpConfiguration() {
+ return mIpConfiguration.getStaticIpConfiguration();
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public void setStaticIpConfiguration(StaticIpConfiguration staticIpConfiguration) {
+ mIpConfiguration.setStaticIpConfiguration(staticIpConfiguration);
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public IpConfiguration.IpAssignment getIpAssignment() {
+ return mIpConfiguration.ipAssignment;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public void setIpAssignment(IpConfiguration.IpAssignment ipAssignment) {
+ mIpConfiguration.ipAssignment = ipAssignment;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public IpConfiguration.ProxySettings getProxySettings() {
+ return mIpConfiguration.proxySettings;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public void setProxySettings(IpConfiguration.ProxySettings proxySettings) {
+ mIpConfiguration.proxySettings = proxySettings;
+ }
+
+ /**
+ * Returns the HTTP proxy used by this object.
+ * @return a {@link ProxyInfo httpProxy} representing the proxy specified by this
+ * WifiConfiguration, or {@code null} if no proxy is specified.
+ */
+ public ProxyInfo getHttpProxy() {
+ if (mIpConfiguration.proxySettings == IpConfiguration.ProxySettings.NONE) {
+ return null;
+ }
+ return new ProxyInfo(mIpConfiguration.httpProxy);
+ }
+
+ /**
+ * Set the {@link ProxyInfo} for this WifiConfiguration. This method should only be used by a
+ * device owner or profile owner. When other apps attempt to save a {@link WifiConfiguration}
+ * with modified proxy settings, the methods {@link WifiManager#addNetwork} and
+ * {@link WifiManager#updateNetwork} fail and return {@code -1}.
+ *
+ * @param httpProxy {@link ProxyInfo} representing the httpProxy to be used by this
+ * WifiConfiguration. Setting this to {@code null} will explicitly set no
+ * proxy, removing any proxy that was previously set.
+ * @exception IllegalArgumentException for invalid httpProxy
+ */
+ public void setHttpProxy(ProxyInfo httpProxy) {
+ if (httpProxy == null) {
+ mIpConfiguration.setProxySettings(IpConfiguration.ProxySettings.NONE);
+ mIpConfiguration.setHttpProxy(null);
+ return;
+ }
+ ProxyInfo httpProxyCopy;
+ ProxySettings proxySettingCopy;
+ if (!Uri.EMPTY.equals(httpProxy.getPacFileUrl())) {
+ proxySettingCopy = IpConfiguration.ProxySettings.PAC;
+ // Construct a new PAC URL Proxy
+ httpProxyCopy = new ProxyInfo(httpProxy.getPacFileUrl(), httpProxy.getPort());
+ } else {
+ proxySettingCopy = IpConfiguration.ProxySettings.STATIC;
+ // Construct a new HTTP Proxy
+ httpProxyCopy = new ProxyInfo(httpProxy.getHost(), httpProxy.getPort(),
+ httpProxy.getExclusionListAsString());
+ }
+ if (!httpProxyCopy.isValid()) {
+ throw new IllegalArgumentException("Invalid ProxyInfo: " + httpProxyCopy.toString());
+ }
+ mIpConfiguration.setProxySettings(proxySettingCopy);
+ mIpConfiguration.setHttpProxy(httpProxyCopy);
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public void setProxy(ProxySettings settings, ProxyInfo proxy) {
+ mIpConfiguration.proxySettings = settings;
+ mIpConfiguration.httpProxy = proxy;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ public void setPasspointManagementObjectTree(String passpointManagementObjectTree) {
+ mPasspointManagementObjectTree = passpointManagementObjectTree;
+ }
+
+ /** @hide */
+ public String getMoTree() {
+ return mPasspointManagementObjectTree;
+ }
+
+ /** copy constructor {@hide} */
+ @UnsupportedAppUsage
+ public WifiConfiguration(WifiConfiguration source) {
+ if (source != null) {
+ networkId = source.networkId;
+ status = source.status;
+ SSID = source.SSID;
+ BSSID = source.BSSID;
+ FQDN = source.FQDN;
+ roamingConsortiumIds = source.roamingConsortiumIds.clone();
+ providerFriendlyName = source.providerFriendlyName;
+ isHomeProviderNetwork = source.isHomeProviderNetwork;
+ preSharedKey = source.preSharedKey;
+
+ mNetworkSelectionStatus.copy(source.getNetworkSelectionStatus());
+ apBand = source.apBand;
+ apChannel = source.apChannel;
+
+ wepKeys = new String[4];
+ for (int i = 0; i < wepKeys.length; i++) {
+ wepKeys[i] = source.wepKeys[i];
+ }
+
+ wepTxKeyIndex = source.wepTxKeyIndex;
+ priority = source.priority;
+ hiddenSSID = source.hiddenSSID;
+ allowedKeyManagement = (BitSet) source.allowedKeyManagement.clone();
+ allowedProtocols = (BitSet) source.allowedProtocols.clone();
+ allowedAuthAlgorithms = (BitSet) source.allowedAuthAlgorithms.clone();
+ allowedPairwiseCiphers = (BitSet) source.allowedPairwiseCiphers.clone();
+ allowedGroupCiphers = (BitSet) source.allowedGroupCiphers.clone();
+ allowedGroupManagementCiphers = (BitSet) source.allowedGroupManagementCiphers.clone();
+ allowedSuiteBCiphers = (BitSet) source.allowedSuiteBCiphers.clone();
+ enterpriseConfig = new WifiEnterpriseConfig(source.enterpriseConfig);
+
+ defaultGwMacAddress = source.defaultGwMacAddress;
+
+ mIpConfiguration = new IpConfiguration(source.mIpConfiguration);
+
+ if ((source.linkedConfigurations != null)
+ && (source.linkedConfigurations.size() > 0)) {
+ linkedConfigurations = new HashMap<String, Integer>();
+ linkedConfigurations.putAll(source.linkedConfigurations);
+ }
+ mCachedConfigKey = null; //force null configKey
+ selfAdded = source.selfAdded;
+ validatedInternetAccess = source.validatedInternetAccess;
+ isLegacyPasspointConfig = source.isLegacyPasspointConfig;
+ ephemeral = source.ephemeral;
+ osu = source.osu;
+ trusted = source.trusted;
+ fromWifiNetworkSuggestion = source.fromWifiNetworkSuggestion;
+ fromWifiNetworkSpecifier = source.fromWifiNetworkSpecifier;
+ meteredHint = source.meteredHint;
+ meteredOverride = source.meteredOverride;
+ useExternalScores = source.useExternalScores;
+
+ didSelfAdd = source.didSelfAdd;
+ lastConnectUid = source.lastConnectUid;
+ lastUpdateUid = source.lastUpdateUid;
+ creatorUid = source.creatorUid;
+ creatorName = source.creatorName;
+ lastUpdateName = source.lastUpdateName;
+ peerWifiConfiguration = source.peerWifiConfiguration;
+
+ lastConnected = source.lastConnected;
+ lastDisconnected = source.lastDisconnected;
+ numScorerOverride = source.numScorerOverride;
+ numScorerOverrideAndSwitchedNetwork = source.numScorerOverrideAndSwitchedNetwork;
+ numAssociation = source.numAssociation;
+ userApproved = source.userApproved;
+ numNoInternetAccessReports = source.numNoInternetAccessReports;
+ noInternetAccessExpected = source.noInternetAccessExpected;
+ creationTime = source.creationTime;
+ updateTime = source.updateTime;
+ shared = source.shared;
+ recentFailure.setAssociationStatus(source.recentFailure.getAssociationStatus());
+ mRandomizedMacAddress = source.mRandomizedMacAddress;
+ macRandomizationSetting = source.macRandomizationSetting;
+ requirePMF = source.requirePMF;
+ updateIdentifier = source.updateIdentifier;
+ }
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(networkId);
+ dest.writeInt(status);
+ mNetworkSelectionStatus.writeToParcel(dest);
+ dest.writeString(SSID);
+ dest.writeString(BSSID);
+ dest.writeInt(apBand);
+ dest.writeInt(apChannel);
+ dest.writeString(FQDN);
+ dest.writeString(providerFriendlyName);
+ dest.writeInt(isHomeProviderNetwork ? 1 : 0);
+ dest.writeInt(roamingConsortiumIds.length);
+ for (long roamingConsortiumId : roamingConsortiumIds) {
+ dest.writeLong(roamingConsortiumId);
+ }
+ dest.writeString(preSharedKey);
+ for (String wepKey : wepKeys) {
+ dest.writeString(wepKey);
+ }
+ dest.writeInt(wepTxKeyIndex);
+ dest.writeInt(priority);
+ dest.writeInt(hiddenSSID ? 1 : 0);
+ dest.writeInt(requirePMF ? 1 : 0);
+ dest.writeString(updateIdentifier);
+
+ writeBitSet(dest, allowedKeyManagement);
+ writeBitSet(dest, allowedProtocols);
+ writeBitSet(dest, allowedAuthAlgorithms);
+ writeBitSet(dest, allowedPairwiseCiphers);
+ writeBitSet(dest, allowedGroupCiphers);
+ writeBitSet(dest, allowedGroupManagementCiphers);
+ writeBitSet(dest, allowedSuiteBCiphers);
+
+ dest.writeParcelable(enterpriseConfig, flags);
+
+ dest.writeParcelable(mIpConfiguration, flags);
+ dest.writeString(dhcpServer);
+ dest.writeString(defaultGwMacAddress);
+ dest.writeInt(selfAdded ? 1 : 0);
+ dest.writeInt(didSelfAdd ? 1 : 0);
+ dest.writeInt(validatedInternetAccess ? 1 : 0);
+ dest.writeInt(isLegacyPasspointConfig ? 1 : 0);
+ dest.writeInt(ephemeral ? 1 : 0);
+ dest.writeInt(trusted ? 1 : 0);
+ dest.writeInt(fromWifiNetworkSuggestion ? 1 : 0);
+ dest.writeInt(fromWifiNetworkSpecifier ? 1 : 0);
+ dest.writeInt(meteredHint ? 1 : 0);
+ dest.writeInt(meteredOverride);
+ dest.writeInt(useExternalScores ? 1 : 0);
+ dest.writeInt(creatorUid);
+ dest.writeInt(lastConnectUid);
+ dest.writeInt(lastUpdateUid);
+ dest.writeString(creatorName);
+ dest.writeString(lastUpdateName);
+ dest.writeInt(numScorerOverride);
+ dest.writeInt(numScorerOverrideAndSwitchedNetwork);
+ dest.writeInt(numAssociation);
+ dest.writeInt(userApproved);
+ dest.writeInt(numNoInternetAccessReports);
+ dest.writeInt(noInternetAccessExpected ? 1 : 0);
+ dest.writeInt(shared ? 1 : 0);
+ dest.writeString(mPasspointManagementObjectTree);
+ dest.writeInt(recentFailure.getAssociationStatus());
+ dest.writeParcelable(mRandomizedMacAddress, flags);
+ dest.writeInt(macRandomizationSetting);
+ dest.writeInt(osu ? 1 : 0);
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ @UnsupportedAppUsage
+ public static final @android.annotation.NonNull Creator<WifiConfiguration> CREATOR =
+ new Creator<WifiConfiguration>() {
+ public WifiConfiguration createFromParcel(Parcel in) {
+ WifiConfiguration config = new WifiConfiguration();
+ config.networkId = in.readInt();
+ config.status = in.readInt();
+ config.mNetworkSelectionStatus.readFromParcel(in);
+ config.SSID = in.readString();
+ config.BSSID = in.readString();
+ config.apBand = in.readInt();
+ config.apChannel = in.readInt();
+ config.FQDN = in.readString();
+ config.providerFriendlyName = in.readString();
+ config.isHomeProviderNetwork = in.readInt() != 0;
+ int numRoamingConsortiumIds = in.readInt();
+ config.roamingConsortiumIds = new long[numRoamingConsortiumIds];
+ for (int i = 0; i < numRoamingConsortiumIds; i++) {
+ config.roamingConsortiumIds[i] = in.readLong();
+ }
+ config.preSharedKey = in.readString();
+ for (int i = 0; i < config.wepKeys.length; i++) {
+ config.wepKeys[i] = in.readString();
+ }
+ config.wepTxKeyIndex = in.readInt();
+ config.priority = in.readInt();
+ config.hiddenSSID = in.readInt() != 0;
+ config.requirePMF = in.readInt() != 0;
+ config.updateIdentifier = in.readString();
+
+ config.allowedKeyManagement = readBitSet(in);
+ config.allowedProtocols = readBitSet(in);
+ config.allowedAuthAlgorithms = readBitSet(in);
+ config.allowedPairwiseCiphers = readBitSet(in);
+ config.allowedGroupCiphers = readBitSet(in);
+ config.allowedGroupManagementCiphers = readBitSet(in);
+ config.allowedSuiteBCiphers = readBitSet(in);
+
+ config.enterpriseConfig = in.readParcelable(null);
+ config.setIpConfiguration(in.readParcelable(null));
+ config.dhcpServer = in.readString();
+ config.defaultGwMacAddress = in.readString();
+ config.selfAdded = in.readInt() != 0;
+ config.didSelfAdd = in.readInt() != 0;
+ config.validatedInternetAccess = in.readInt() != 0;
+ config.isLegacyPasspointConfig = in.readInt() != 0;
+ config.ephemeral = in.readInt() != 0;
+ config.trusted = in.readInt() != 0;
+ config.fromWifiNetworkSuggestion = in.readInt() != 0;
+ config.fromWifiNetworkSpecifier = in.readInt() != 0;
+ config.meteredHint = in.readInt() != 0;
+ config.meteredOverride = in.readInt();
+ config.useExternalScores = in.readInt() != 0;
+ config.creatorUid = in.readInt();
+ config.lastConnectUid = in.readInt();
+ config.lastUpdateUid = in.readInt();
+ config.creatorName = in.readString();
+ config.lastUpdateName = in.readString();
+ config.numScorerOverride = in.readInt();
+ config.numScorerOverrideAndSwitchedNetwork = in.readInt();
+ config.numAssociation = in.readInt();
+ config.userApproved = in.readInt();
+ config.numNoInternetAccessReports = in.readInt();
+ config.noInternetAccessExpected = in.readInt() != 0;
+ config.shared = in.readInt() != 0;
+ config.mPasspointManagementObjectTree = in.readString();
+ config.recentFailure.setAssociationStatus(in.readInt());
+ config.mRandomizedMacAddress = in.readParcelable(null);
+ config.macRandomizationSetting = in.readInt();
+ config.osu = in.readInt() != 0;
+ return config;
+ }
+
+ public WifiConfiguration[] newArray(int size) {
+ return new WifiConfiguration[size];
+ }
+ };
+
+ /**
+ * Serializes the object for backup
+ * @hide
+ */
+ public byte[] getBytesForBackup() throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream out = new DataOutputStream(baos);
+
+ out.writeInt(BACKUP_VERSION);
+ BackupUtils.writeString(out, SSID);
+ out.writeInt(apBand);
+ out.writeInt(apChannel);
+ BackupUtils.writeString(out, preSharedKey);
+ out.writeInt(getAuthType());
+ out.writeBoolean(hiddenSSID);
+ return baos.toByteArray();
+ }
+
+ /**
+ * Deserializes a byte array into the WiFiConfiguration Object
+ * @hide
+ */
+ public static WifiConfiguration getWifiConfigFromBackup(DataInputStream in) throws IOException,
+ BackupUtils.BadVersionException {
+ WifiConfiguration config = new WifiConfiguration();
+ int version = in.readInt();
+ if (version < 1 || version > BACKUP_VERSION) {
+ throw new BackupUtils.BadVersionException("Unknown Backup Serialization Version");
+ }
+
+ if (version == 1) return null; // Version 1 is a bad dataset.
+
+ config.SSID = BackupUtils.readString(in);
+ config.apBand = in.readInt();
+ config.apChannel = in.readInt();
+ config.preSharedKey = BackupUtils.readString(in);
+ config.allowedKeyManagement.set(in.readInt());
+ if (version >= 3) {
+ config.hiddenSSID = in.readBoolean();
+ }
+ return config;
+ }
+}
diff --git a/android/net/wifi/WifiEnterpriseConfig.java b/android/net/wifi/WifiEnterpriseConfig.java
new file mode 100644
index 0000000..9d4b837
--- /dev/null
+++ b/android/net/wifi/WifiEnterpriseConfig.java
@@ -0,0 +1,1193 @@
+/*
+ * Copyright (C) 2013 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.Nullable;
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.security.Credentials;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.nio.charset.StandardCharsets;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Enterprise configuration details for Wi-Fi. Stores details about the EAP method
+ * and any associated credentials.
+ */
+public class WifiEnterpriseConfig implements Parcelable {
+
+ /** @hide */
+ public static final String EMPTY_VALUE = "NULL";
+ /** @hide */
+ public static final String EAP_KEY = "eap";
+ /** @hide */
+ public static final String PHASE2_KEY = "phase2";
+ /** @hide */
+ public static final String IDENTITY_KEY = "identity";
+ /** @hide */
+ public static final String ANON_IDENTITY_KEY = "anonymous_identity";
+ /** @hide */
+ public static final String PASSWORD_KEY = "password";
+ /** @hide */
+ public static final String SUBJECT_MATCH_KEY = "subject_match";
+ /** @hide */
+ public static final String ALTSUBJECT_MATCH_KEY = "altsubject_match";
+ /** @hide */
+ public static final String DOM_SUFFIX_MATCH_KEY = "domain_suffix_match";
+ /** @hide */
+ public static final String OPP_KEY_CACHING = "proactive_key_caching";
+ /**
+ * String representing the keystore OpenSSL ENGINE's ID.
+ * @hide
+ */
+ public static final String ENGINE_ID_KEYSTORE = "keystore";
+
+ /**
+ * String representing the keystore URI used for wpa_supplicant.
+ * @hide
+ */
+ public static final String KEYSTORE_URI = "keystore://";
+
+ /**
+ * String representing the keystore URI used for wpa_supplicant,
+ * Unlike #KEYSTORE_URI, this supports a list of space-delimited aliases
+ * @hide
+ */
+ public static final String KEYSTORES_URI = "keystores://";
+
+ /**
+ * String to set the engine value to when it should be enabled.
+ * @hide
+ */
+ public static final String ENGINE_ENABLE = "1";
+
+ /**
+ * String to set the engine value to when it should be disabled.
+ * @hide
+ */
+ public static final String ENGINE_DISABLE = "0";
+
+ /** @hide */
+ public static final String CA_CERT_PREFIX = KEYSTORE_URI + Credentials.CA_CERTIFICATE;
+ /** @hide */
+ public static final String CLIENT_CERT_PREFIX = KEYSTORE_URI + Credentials.USER_CERTIFICATE;
+ /** @hide */
+ public static final String CLIENT_CERT_KEY = "client_cert";
+ /** @hide */
+ public static final String CA_CERT_KEY = "ca_cert";
+ /** @hide */
+ public static final String CA_PATH_KEY = "ca_path";
+ /** @hide */
+ public static final String ENGINE_KEY = "engine";
+ /** @hide */
+ public static final String ENGINE_ID_KEY = "engine_id";
+ /** @hide */
+ public static final String PRIVATE_KEY_ID_KEY = "key_id";
+ /** @hide */
+ public static final String REALM_KEY = "realm";
+ /** @hide */
+ public static final String PLMN_KEY = "plmn";
+ /** @hide */
+ public static final String CA_CERT_ALIAS_DELIMITER = " ";
+
+ // Fields to copy verbatim from wpa_supplicant.
+ private static final String[] SUPPLICANT_CONFIG_KEYS = new String[] {
+ IDENTITY_KEY,
+ ANON_IDENTITY_KEY,
+ PASSWORD_KEY,
+ CLIENT_CERT_KEY,
+ CA_CERT_KEY,
+ SUBJECT_MATCH_KEY,
+ ENGINE_KEY,
+ ENGINE_ID_KEY,
+ PRIVATE_KEY_ID_KEY,
+ ALTSUBJECT_MATCH_KEY,
+ DOM_SUFFIX_MATCH_KEY,
+ CA_PATH_KEY
+ };
+
+ /**
+ * Fields that have unquoted values in {@link #mFields}.
+ */
+ private static final List<String> UNQUOTED_KEYS = Arrays.asList(ENGINE_KEY, OPP_KEY_CACHING);
+
+ @UnsupportedAppUsage
+ private HashMap<String, String> mFields = new HashMap<String, String>();
+ private X509Certificate[] mCaCerts;
+ private PrivateKey mClientPrivateKey;
+ private X509Certificate[] mClientCertificateChain;
+ private int mEapMethod = Eap.NONE;
+ private int mPhase2Method = Phase2.NONE;
+ private boolean mIsAppInstalledDeviceKeyAndCert = false;
+ private boolean mIsAppInstalledCaCert = false;
+
+ private static final String TAG = "WifiEnterpriseConfig";
+
+ public WifiEnterpriseConfig() {
+ // Do not set defaults so that the enterprise fields that are not changed
+ // by API are not changed underneath
+ // This is essential because an app may not have all fields like password
+ // available. It allows modification of subset of fields.
+
+ }
+
+ /**
+ * Copy over the contents of the source WifiEnterpriseConfig object over to this object.
+ *
+ * @param source Source WifiEnterpriseConfig object.
+ * @param ignoreMaskedPassword Set to true to ignore masked password field, false otherwise.
+ * @param mask if |ignoreMaskedPassword| is set, check if the incoming password field is set
+ * to this value.
+ */
+ private void copyFrom(WifiEnterpriseConfig source, boolean ignoreMaskedPassword, String mask) {
+ for (String key : source.mFields.keySet()) {
+ if (ignoreMaskedPassword && key.equals(PASSWORD_KEY)
+ && TextUtils.equals(source.mFields.get(key), mask)) {
+ continue;
+ }
+ mFields.put(key, source.mFields.get(key));
+ }
+ if (source.mCaCerts != null) {
+ mCaCerts = Arrays.copyOf(source.mCaCerts, source.mCaCerts.length);
+ } else {
+ mCaCerts = null;
+ }
+ mClientPrivateKey = source.mClientPrivateKey;
+ if (source.mClientCertificateChain != null) {
+ mClientCertificateChain = Arrays.copyOf(
+ source.mClientCertificateChain,
+ source.mClientCertificateChain.length);
+ } else {
+ mClientCertificateChain = null;
+ }
+ mEapMethod = source.mEapMethod;
+ mPhase2Method = source.mPhase2Method;
+ mIsAppInstalledDeviceKeyAndCert = source.mIsAppInstalledDeviceKeyAndCert;
+ mIsAppInstalledCaCert = source.mIsAppInstalledCaCert;
+ }
+
+ /**
+ * Copy constructor.
+ * This copies over all the fields verbatim (does not ignore masked password fields).
+ *
+ * @param source Source WifiEnterpriseConfig object.
+ */
+ public WifiEnterpriseConfig(WifiEnterpriseConfig source) {
+ copyFrom(source, false, "");
+ }
+
+ /**
+ * Copy fields from the provided external WifiEnterpriseConfig.
+ * This is needed to handle the WifiEnterpriseConfig objects which were sent by apps with the
+ * password field masked.
+ *
+ * @param externalConfig External WifiEnterpriseConfig object.
+ * @param mask String mask to compare against.
+ * @hide
+ */
+ public void copyFromExternal(WifiEnterpriseConfig externalConfig, String mask) {
+ copyFrom(externalConfig, true, convertToQuotedString(mask));
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mFields.size());
+ for (Map.Entry<String, String> entry : mFields.entrySet()) {
+ dest.writeString(entry.getKey());
+ dest.writeString(entry.getValue());
+ }
+
+ dest.writeInt(mEapMethod);
+ dest.writeInt(mPhase2Method);
+ ParcelUtil.writeCertificates(dest, mCaCerts);
+ ParcelUtil.writePrivateKey(dest, mClientPrivateKey);
+ ParcelUtil.writeCertificates(dest, mClientCertificateChain);
+ dest.writeBoolean(mIsAppInstalledDeviceKeyAndCert);
+ dest.writeBoolean(mIsAppInstalledCaCert);
+ }
+
+ public static final @android.annotation.NonNull Creator<WifiEnterpriseConfig> CREATOR =
+ new Creator<WifiEnterpriseConfig>() {
+ @Override
+ public WifiEnterpriseConfig createFromParcel(Parcel in) {
+ WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+ int count = in.readInt();
+ for (int i = 0; i < count; i++) {
+ String key = in.readString();
+ String value = in.readString();
+ enterpriseConfig.mFields.put(key, value);
+ }
+
+ enterpriseConfig.mEapMethod = in.readInt();
+ enterpriseConfig.mPhase2Method = in.readInt();
+ enterpriseConfig.mCaCerts = ParcelUtil.readCertificates(in);
+ enterpriseConfig.mClientPrivateKey = ParcelUtil.readPrivateKey(in);
+ enterpriseConfig.mClientCertificateChain = ParcelUtil.readCertificates(in);
+ enterpriseConfig.mIsAppInstalledDeviceKeyAndCert = in.readBoolean();
+ enterpriseConfig.mIsAppInstalledCaCert = in.readBoolean();
+ return enterpriseConfig;
+ }
+
+ @Override
+ public WifiEnterpriseConfig[] newArray(int size) {
+ return new WifiEnterpriseConfig[size];
+ }
+ };
+
+ /** The Extensible Authentication Protocol method used */
+ public static final class Eap {
+ /** No EAP method used. Represents an empty config */
+ public static final int NONE = -1;
+ /** Protected EAP */
+ public static final int PEAP = 0;
+ /** EAP-Transport Layer Security */
+ public static final int TLS = 1;
+ /** EAP-Tunneled Transport Layer Security */
+ public static final int TTLS = 2;
+ /** EAP-Password */
+ public static final int PWD = 3;
+ /** EAP-Subscriber Identity Module [RFC-4186] */
+ public static final int SIM = 4;
+ /** EAP-Authentication and Key Agreement [RFC-4187] */
+ public static final int AKA = 5;
+ /** EAP-Authentication and Key Agreement Prime [RFC-5448] */
+ public static final int AKA_PRIME = 6;
+ /** Hotspot 2.0 r2 OSEN */
+ public static final int UNAUTH_TLS = 7;
+ /** @hide */
+ public static final String[] strings =
+ { "PEAP", "TLS", "TTLS", "PWD", "SIM", "AKA", "AKA'", "WFA-UNAUTH-TLS" };
+
+ /** Prevent initialization */
+ private Eap() {}
+ }
+
+ /** The inner authentication method used */
+ public static final class Phase2 {
+ public static final int NONE = 0;
+ /** Password Authentication Protocol */
+ public static final int PAP = 1;
+ /** Microsoft Challenge Handshake Authentication Protocol */
+ public static final int MSCHAP = 2;
+ /** Microsoft Challenge Handshake Authentication Protocol v2 */
+ public static final int MSCHAPV2 = 3;
+ /** Generic Token Card */
+ public static final int GTC = 4;
+ /** EAP-Subscriber Identity Module [RFC-4186] */
+ public static final int SIM = 5;
+ /** EAP-Authentication and Key Agreement [RFC-4187] */
+ public static final int AKA = 6;
+ /** EAP-Authentication and Key Agreement Prime [RFC-5448] */
+ public static final int AKA_PRIME = 7;
+ private static final String AUTH_PREFIX = "auth=";
+ private static final String AUTHEAP_PREFIX = "autheap=";
+ /** @hide */
+ public static final String[] strings = {EMPTY_VALUE, "PAP", "MSCHAP",
+ "MSCHAPV2", "GTC", "SIM", "AKA", "AKA'" };
+
+ /** Prevent initialization */
+ private Phase2() {}
+ }
+
+ // Loader and saver interfaces for exchanging data with wpa_supplicant.
+ // TODO: Decouple this object (which is just a placeholder of the configuration)
+ // from the implementation that knows what wpa_supplicant wants.
+ /**
+ * Interface used for retrieving supplicant configuration from WifiEnterpriseConfig
+ * @hide
+ */
+ public interface SupplicantSaver {
+ /**
+ * Set a value within wpa_supplicant configuration
+ * @param key index to set within wpa_supplciant
+ * @param value the value for the key
+ * @return true if successful; false otherwise
+ */
+ boolean saveValue(String key, String value);
+ }
+
+ /**
+ * Interface used for populating a WifiEnterpriseConfig from supplicant configuration
+ * @hide
+ */
+ public interface SupplicantLoader {
+ /**
+ * Returns a value within wpa_supplicant configuration
+ * @param key index to set within wpa_supplciant
+ * @return string value if successful; null otherwise
+ */
+ String loadValue(String key);
+ }
+
+ /**
+ * Internal use only; supply field values to wpa_supplicant config. The configuration
+ * process aborts on the first failed call on {@code saver}.
+ * @param saver proxy for setting configuration in wpa_supplciant
+ * @return whether the save succeeded on all attempts
+ * @hide
+ */
+ public boolean saveToSupplicant(SupplicantSaver saver) {
+ if (!isEapMethodValid()) {
+ return false;
+ }
+
+ // wpa_supplicant can update the anonymous identity for these kinds of networks after
+ // framework reads them, so make sure the framework doesn't try to overwrite them.
+ boolean shouldNotWriteAnonIdentity = mEapMethod == WifiEnterpriseConfig.Eap.SIM
+ || mEapMethod == WifiEnterpriseConfig.Eap.AKA
+ || mEapMethod == WifiEnterpriseConfig.Eap.AKA_PRIME;
+ for (String key : mFields.keySet()) {
+ if (shouldNotWriteAnonIdentity && ANON_IDENTITY_KEY.equals(key)) {
+ continue;
+ }
+ if (!saver.saveValue(key, mFields.get(key))) {
+ return false;
+ }
+ }
+
+ if (!saver.saveValue(EAP_KEY, Eap.strings[mEapMethod])) {
+ return false;
+ }
+
+ if (mEapMethod != Eap.TLS && mPhase2Method != Phase2.NONE) {
+ boolean is_autheap = mEapMethod == Eap.TTLS && mPhase2Method == Phase2.GTC;
+ String prefix = is_autheap ? Phase2.AUTHEAP_PREFIX : Phase2.AUTH_PREFIX;
+ String value = convertToQuotedString(prefix + Phase2.strings[mPhase2Method]);
+ return saver.saveValue(PHASE2_KEY, value);
+ } else if (mPhase2Method == Phase2.NONE) {
+ // By default, send a null phase 2 to clear old configuration values.
+ return saver.saveValue(PHASE2_KEY, null);
+ } else {
+ Log.e(TAG, "WiFi enterprise configuration is invalid as it supplies a "
+ + "phase 2 method but the phase1 method does not support it.");
+ return false;
+ }
+ }
+
+ /**
+ * Internal use only; retrieve configuration from wpa_supplicant config.
+ * @param loader proxy for retrieving configuration keys from wpa_supplicant
+ * @hide
+ */
+ public void loadFromSupplicant(SupplicantLoader loader) {
+ for (String key : SUPPLICANT_CONFIG_KEYS) {
+ String value = loader.loadValue(key);
+ if (value == null) {
+ mFields.put(key, EMPTY_VALUE);
+ } else {
+ mFields.put(key, value);
+ }
+ }
+ String eapMethod = loader.loadValue(EAP_KEY);
+ mEapMethod = getStringIndex(Eap.strings, eapMethod, Eap.NONE);
+
+ String phase2Method = removeDoubleQuotes(loader.loadValue(PHASE2_KEY));
+ // Remove "auth=" or "autheap=" prefix.
+ if (phase2Method.startsWith(Phase2.AUTH_PREFIX)) {
+ phase2Method = phase2Method.substring(Phase2.AUTH_PREFIX.length());
+ } else if (phase2Method.startsWith(Phase2.AUTHEAP_PREFIX)) {
+ phase2Method = phase2Method.substring(Phase2.AUTHEAP_PREFIX.length());
+ }
+ mPhase2Method = getStringIndex(Phase2.strings, phase2Method, Phase2.NONE);
+ }
+
+ /**
+ * Set the EAP authentication method.
+ * @param eapMethod is one {@link Eap#PEAP}, {@link Eap#TLS}, {@link Eap#TTLS} or
+ * {@link Eap#PWD}
+ * @throws IllegalArgumentException on an invalid eap method
+ */
+ public void setEapMethod(int eapMethod) {
+ switch (eapMethod) {
+ /** Valid methods */
+ case Eap.TLS:
+ case Eap.UNAUTH_TLS:
+ setPhase2Method(Phase2.NONE);
+ /* fall through */
+ case Eap.PEAP:
+ case Eap.PWD:
+ case Eap.TTLS:
+ case Eap.SIM:
+ case Eap.AKA:
+ case Eap.AKA_PRIME:
+ mEapMethod = eapMethod;
+ setFieldValue(OPP_KEY_CACHING, "1");
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown EAP method");
+ }
+ }
+
+ /**
+ * Get the eap method.
+ * @return eap method configured
+ */
+ public int getEapMethod() {
+ return mEapMethod;
+ }
+
+ /**
+ * Set Phase 2 authentication method. Sets the inner authentication method to be used in
+ * phase 2 after setting up a secure channel
+ * @param phase2Method is the inner authentication method and can be one of {@link Phase2#NONE},
+ * {@link Phase2#PAP}, {@link Phase2#MSCHAP}, {@link Phase2#MSCHAPV2},
+ * {@link Phase2#GTC}
+ * @throws IllegalArgumentException on an invalid phase2 method
+ *
+ */
+ public void setPhase2Method(int phase2Method) {
+ switch (phase2Method) {
+ case Phase2.NONE:
+ case Phase2.PAP:
+ case Phase2.MSCHAP:
+ case Phase2.MSCHAPV2:
+ case Phase2.GTC:
+ case Phase2.SIM:
+ case Phase2.AKA:
+ case Phase2.AKA_PRIME:
+ mPhase2Method = phase2Method;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown Phase 2 method");
+ }
+ }
+
+ /**
+ * Get the phase 2 authentication method.
+ * @return a phase 2 method defined at {@link Phase2}
+ * */
+ public int getPhase2Method() {
+ return mPhase2Method;
+ }
+
+ /**
+ * Set the identity
+ * @param identity
+ */
+ public void setIdentity(String identity) {
+ setFieldValue(IDENTITY_KEY, identity, "");
+ }
+
+ /**
+ * Get the identity
+ * @return the identity
+ */
+ public String getIdentity() {
+ return getFieldValue(IDENTITY_KEY);
+ }
+
+ /**
+ * Set anonymous identity. This is used as the unencrypted identity with
+ * certain EAP types
+ * @param anonymousIdentity the anonymous identity
+ */
+ public void setAnonymousIdentity(String anonymousIdentity) {
+ setFieldValue(ANON_IDENTITY_KEY, anonymousIdentity);
+ }
+
+ /**
+ * Get the anonymous identity
+ * @return anonymous identity
+ */
+ public String getAnonymousIdentity() {
+ return getFieldValue(ANON_IDENTITY_KEY);
+ }
+
+ /**
+ * Set the password.
+ * @param password the password
+ */
+ public void setPassword(String password) {
+ setFieldValue(PASSWORD_KEY, password);
+ }
+
+ /**
+ * Get the password.
+ *
+ * Returns locally set password value. For networks fetched from
+ * framework, returns "*".
+ */
+ public String getPassword() {
+ return getFieldValue(PASSWORD_KEY);
+ }
+
+ /**
+ * Encode a CA certificate alias so it does not contain illegal character.
+ * @hide
+ */
+ public static String encodeCaCertificateAlias(String alias) {
+ byte[] bytes = alias.getBytes(StandardCharsets.UTF_8);
+ StringBuilder sb = new StringBuilder(bytes.length * 2);
+ for (byte o : bytes) {
+ sb.append(String.format("%02x", o & 0xFF));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Decode a previously-encoded CA certificate alias.
+ * @hide
+ */
+ public static String decodeCaCertificateAlias(String alias) {
+ byte[] data = new byte[alias.length() >> 1];
+ for (int n = 0, position = 0; n < alias.length(); n += 2, position++) {
+ data[position] = (byte) Integer.parseInt(alias.substring(n, n + 2), 16);
+ }
+ try {
+ return new String(data, StandardCharsets.UTF_8);
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ return alias;
+ }
+ }
+
+ /**
+ * Set CA certificate alias.
+ *
+ * <p> See the {@link android.security.KeyChain} for details on installing or choosing
+ * a certificate
+ * </p>
+ * @param alias identifies the certificate
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setCaCertificateAlias(String alias) {
+ setFieldValue(CA_CERT_KEY, alias, CA_CERT_PREFIX);
+ }
+
+ /**
+ * Set CA certificate aliases. When creating installing the corresponding certificate to
+ * the keystore, please use alias encoded by {@link #encodeCaCertificateAlias(String)}.
+ *
+ * <p> See the {@link android.security.KeyChain} for details on installing or choosing
+ * a certificate.
+ * </p>
+ * @param aliases identifies the certificate
+ * @hide
+ */
+ public void setCaCertificateAliases(@Nullable String[] aliases) {
+ if (aliases == null) {
+ setFieldValue(CA_CERT_KEY, null, CA_CERT_PREFIX);
+ } else if (aliases.length == 1) {
+ // Backwards compatibility: use the original cert prefix if setting only one alias.
+ setCaCertificateAlias(aliases[0]);
+ } else {
+ // Use KEYSTORES_URI which supports multiple aliases.
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < aliases.length; i++) {
+ if (i > 0) {
+ sb.append(CA_CERT_ALIAS_DELIMITER);
+ }
+ sb.append(encodeCaCertificateAlias(Credentials.CA_CERTIFICATE + aliases[i]));
+ }
+ setFieldValue(CA_CERT_KEY, sb.toString(), KEYSTORES_URI);
+ }
+ }
+
+ /**
+ * Get CA certificate alias
+ * @return alias to the CA certificate
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public String getCaCertificateAlias() {
+ return getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX);
+ }
+
+ /**
+ * Get CA certificate aliases
+ * @return alias to the CA certificate
+ * @hide
+ */
+ @Nullable public String[] getCaCertificateAliases() {
+ String value = getFieldValue(CA_CERT_KEY);
+ if (value.startsWith(CA_CERT_PREFIX)) {
+ // Backwards compatibility: parse the original alias prefix.
+ return new String[] {getFieldValue(CA_CERT_KEY, CA_CERT_PREFIX)};
+ } else if (value.startsWith(KEYSTORES_URI)) {
+ String values = value.substring(KEYSTORES_URI.length());
+
+ String[] aliases = TextUtils.split(values, CA_CERT_ALIAS_DELIMITER);
+ for (int i = 0; i < aliases.length; i++) {
+ aliases[i] = decodeCaCertificateAlias(aliases[i]);
+ if (aliases[i].startsWith(Credentials.CA_CERTIFICATE)) {
+ aliases[i] = aliases[i].substring(Credentials.CA_CERTIFICATE.length());
+ }
+ }
+ return aliases.length != 0 ? aliases : null;
+ } else {
+ return TextUtils.isEmpty(value) ? null : new String[] {value};
+ }
+ }
+
+ /**
+ * Specify a X.509 certificate that identifies the server.
+ *
+ * <p>A default name is automatically assigned to the certificate and used
+ * with this configuration. The framework takes care of installing the
+ * certificate when the config is saved and removing the certificate when
+ * the config is removed.
+ *
+ * @param cert X.509 CA certificate
+ * @throws IllegalArgumentException if not a CA certificate
+ */
+ public void setCaCertificate(@Nullable X509Certificate cert) {
+ if (cert != null) {
+ if (cert.getBasicConstraints() >= 0) {
+ mIsAppInstalledCaCert = true;
+ mCaCerts = new X509Certificate[] {cert};
+ } else {
+ mCaCerts = null;
+ throw new IllegalArgumentException("Not a CA certificate");
+ }
+ } else {
+ mCaCerts = null;
+ }
+ }
+
+ /**
+ * Get CA certificate. If multiple CA certificates are configured previously,
+ * return the first one.
+ * @return X.509 CA certificate
+ */
+ @Nullable public X509Certificate getCaCertificate() {
+ if (mCaCerts != null && mCaCerts.length > 0) {
+ return mCaCerts[0];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Specify a list of X.509 certificates that identifies the server. The validation
+ * passes if the CA of server certificate matches one of the given certificates.
+
+ * <p>Default names are automatically assigned to the certificates and used
+ * with this configuration. The framework takes care of installing the
+ * certificates when the config is saved and removing the certificates when
+ * the config is removed.
+ *
+ * @param certs X.509 CA certificates
+ * @throws IllegalArgumentException if any of the provided certificates is
+ * not a CA certificate
+ */
+ public void setCaCertificates(@Nullable X509Certificate[] certs) {
+ if (certs != null) {
+ X509Certificate[] newCerts = new X509Certificate[certs.length];
+ for (int i = 0; i < certs.length; i++) {
+ if (certs[i].getBasicConstraints() >= 0) {
+ newCerts[i] = certs[i];
+ } else {
+ mCaCerts = null;
+ throw new IllegalArgumentException("Not a CA certificate");
+ }
+ }
+ mCaCerts = newCerts;
+ mIsAppInstalledCaCert = true;
+ } else {
+ mCaCerts = null;
+ }
+ }
+
+ /**
+ * Get CA certificates.
+ */
+ @Nullable public X509Certificate[] getCaCertificates() {
+ if (mCaCerts != null && mCaCerts.length > 0) {
+ return mCaCerts;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void resetCaCertificate() {
+ mCaCerts = null;
+ }
+
+ /**
+ * Set the ca_path directive on wpa_supplicant.
+ *
+ * From wpa_supplicant documentation:
+ *
+ * Directory path for CA certificate files (PEM). This path may contain
+ * multiple CA certificates in OpenSSL format. Common use for this is to
+ * point to system trusted CA list which is often installed into directory
+ * like /etc/ssl/certs. If configured, these certificates are added to the
+ * list of trusted CAs. ca_cert may also be included in that case, but it is
+ * not required.
+ * @param domain The path for CA certificate files
+ * @hide
+ */
+ public void setCaPath(String path) {
+ setFieldValue(CA_PATH_KEY, path);
+ }
+
+ /**
+ * Get the domain_suffix_match value. See setDomSuffixMatch.
+ * @return The path for CA certificate files.
+ * @hide
+ */
+ public String getCaPath() {
+ return getFieldValue(CA_PATH_KEY);
+ }
+
+ /** Set Client certificate alias.
+ *
+ * <p> See the {@link android.security.KeyChain} for details on installing or choosing
+ * a certificate
+ * </p>
+ * @param alias identifies the certificate
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setClientCertificateAlias(String alias) {
+ setFieldValue(CLIENT_CERT_KEY, alias, CLIENT_CERT_PREFIX);
+ setFieldValue(PRIVATE_KEY_ID_KEY, alias, Credentials.USER_PRIVATE_KEY);
+ // Also, set engine parameters
+ if (TextUtils.isEmpty(alias)) {
+ setFieldValue(ENGINE_KEY, ENGINE_DISABLE);
+ setFieldValue(ENGINE_ID_KEY, "");
+ } else {
+ setFieldValue(ENGINE_KEY, ENGINE_ENABLE);
+ setFieldValue(ENGINE_ID_KEY, ENGINE_ID_KEYSTORE);
+ }
+ }
+
+ /**
+ * Get client certificate alias
+ * @return alias to the client certificate
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public String getClientCertificateAlias() {
+ return getFieldValue(CLIENT_CERT_KEY, CLIENT_CERT_PREFIX);
+ }
+
+ /**
+ * Specify a private key and client certificate for client authorization.
+ *
+ * <p>A default name is automatically assigned to the key entry and used
+ * with this configuration. The framework takes care of installing the
+ * key entry when the config is saved and removing the key entry when
+ * the config is removed.
+
+ * @param privateKey a PrivateKey instance for the end certificate.
+ * @param clientCertificate an X509Certificate representing the end certificate.
+ * @throws IllegalArgumentException for an invalid key or certificate.
+ */
+ public void setClientKeyEntry(PrivateKey privateKey, X509Certificate clientCertificate) {
+ X509Certificate[] clientCertificates = null;
+ if (clientCertificate != null) {
+ clientCertificates = new X509Certificate[] {clientCertificate};
+ }
+ setClientKeyEntryWithCertificateChain(privateKey, clientCertificates);
+ }
+
+ /**
+ * Specify a private key and client certificate chain for client authorization.
+ *
+ * <p>A default name is automatically assigned to the key entry and used
+ * with this configuration. The framework takes care of installing the
+ * key entry when the config is saved and removing the key entry when
+ * the config is removed.
+ *
+ * @param privateKey a PrivateKey instance for the end certificate.
+ * @param clientCertificateChain an array of X509Certificate instances which starts with
+ * end certificate and continues with additional CA certificates necessary to
+ * link the end certificate with some root certificate known by the authenticator.
+ * @throws IllegalArgumentException for an invalid key or certificate.
+ */
+ public void setClientKeyEntryWithCertificateChain(PrivateKey privateKey,
+ X509Certificate[] clientCertificateChain) {
+ X509Certificate[] newCerts = null;
+ if (clientCertificateChain != null && clientCertificateChain.length > 0) {
+ // We validate that this is a well formed chain that starts
+ // with an end-certificate and is followed by CA certificates.
+ // We don't validate that each following certificate verifies
+ // the previous. https://en.wikipedia.org/wiki/Chain_of_trust
+ //
+ // Basic constraints is an X.509 extension type that defines
+ // whether a given certificate is allowed to sign additional
+ // certificates and what path length restrictions may exist.
+ // We use this to judge whether the certificate is an end
+ // certificate or a CA certificate.
+ // https://cryptography.io/en/latest/x509/reference/
+ if (clientCertificateChain[0].getBasicConstraints() != -1) {
+ throw new IllegalArgumentException(
+ "First certificate in the chain must be a client end certificate");
+ }
+
+ for (int i = 1; i < clientCertificateChain.length; i++) {
+ if (clientCertificateChain[i].getBasicConstraints() == -1) {
+ throw new IllegalArgumentException(
+ "All certificates following the first must be CA certificates");
+ }
+ }
+ newCerts = Arrays.copyOf(clientCertificateChain,
+ clientCertificateChain.length);
+
+ if (privateKey == null) {
+ throw new IllegalArgumentException("Client cert without a private key");
+ }
+ if (privateKey.getEncoded() == null) {
+ throw new IllegalArgumentException("Private key cannot be encoded");
+ }
+ }
+
+ mClientPrivateKey = privateKey;
+ mClientCertificateChain = newCerts;
+ mIsAppInstalledDeviceKeyAndCert = true;
+ }
+
+ /**
+ * Get client certificate
+ *
+ * @return X.509 client certificate
+ */
+ public X509Certificate getClientCertificate() {
+ if (mClientCertificateChain != null && mClientCertificateChain.length > 0) {
+ return mClientCertificateChain[0];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get the complete client certificate chain in the same order as it was last supplied.
+ *
+ * <p>If the chain was last supplied by a call to
+ * {@link #setClientKeyEntry(java.security.PrivateKey, java.security.cert.X509Certificate)}
+ * with a non-null * certificate instance, a single-element array containing the certificate
+ * will be * returned. If {@link #setClientKeyEntryWithCertificateChain(
+ * java.security.PrivateKey, java.security.cert.X509Certificate[])} was last called with a
+ * non-empty array, this array will be returned in the same order as it was supplied.
+ * Otherwise, {@code null} will be returned.
+ *
+ * @return X.509 client certificates
+ */
+ @Nullable public X509Certificate[] getClientCertificateChain() {
+ if (mClientCertificateChain != null && mClientCertificateChain.length > 0) {
+ return mClientCertificateChain;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void resetClientKeyEntry() {
+ mClientPrivateKey = null;
+ mClientCertificateChain = null;
+ }
+
+ /**
+ * @hide
+ */
+ public PrivateKey getClientPrivateKey() {
+ return mClientPrivateKey;
+ }
+
+ /**
+ * Set subject match (deprecated). This is the substring to be matched against the subject of
+ * the authentication server certificate.
+ * @param subjectMatch substring to be matched
+ * @deprecated in favor of altSubjectMatch
+ */
+ public void setSubjectMatch(String subjectMatch) {
+ setFieldValue(SUBJECT_MATCH_KEY, subjectMatch);
+ }
+
+ /**
+ * Get subject match (deprecated)
+ * @return the subject match string
+ * @deprecated in favor of altSubjectMatch
+ */
+ public String getSubjectMatch() {
+ return getFieldValue(SUBJECT_MATCH_KEY);
+ }
+
+ /**
+ * Set alternate subject match. This is the substring to be matched against the
+ * alternate subject of the authentication server certificate.
+ * @param altSubjectMatch substring to be matched, for example
+ * DNS:server.example.com;EMAIL:[email protected]
+ */
+ public void setAltSubjectMatch(String altSubjectMatch) {
+ setFieldValue(ALTSUBJECT_MATCH_KEY, altSubjectMatch);
+ }
+
+ /**
+ * Get alternate subject match
+ * @return the alternate subject match string
+ */
+ public String getAltSubjectMatch() {
+ return getFieldValue(ALTSUBJECT_MATCH_KEY);
+ }
+
+ /**
+ * Set the domain_suffix_match directive on wpa_supplicant. This is the parameter to use
+ * for Hotspot 2.0 defined matching of AAA server certs per WFA HS2.0 spec, section 7.3.3.2,
+ * second paragraph.
+ *
+ * <p>From wpa_supplicant documentation:
+ * <p>Constraint for server domain name. If set, this FQDN is used as a suffix match requirement
+ * for the AAAserver certificate in SubjectAltName dNSName element(s). If a matching dNSName is
+ * found, this constraint is met.
+ * <p>Suffix match here means that the host/domain name is compared one label at a time starting
+ * from the top-level domain and all the labels in domain_suffix_match shall be included in the
+ * certificate. The certificate may include additional sub-level labels in addition to the
+ * required labels.
+ * <p>More than one match string can be provided by using semicolons to separate the strings
+ * (e.g., example.org;example.com). When multiple strings are specified, a match with any one of
+ * the values is considered a sufficient match for the certificate, i.e., the conditions are
+ * ORed ogether.
+ * <p>For example, domain_suffix_match=example.com would match test.example.com but would not
+ * match test-example.com.
+ * @param domain The domain value
+ */
+ public void setDomainSuffixMatch(String domain) {
+ setFieldValue(DOM_SUFFIX_MATCH_KEY, domain);
+ }
+
+ /**
+ * Get the domain_suffix_match value. See setDomSuffixMatch.
+ * @return The domain value.
+ */
+ public String getDomainSuffixMatch() {
+ return getFieldValue(DOM_SUFFIX_MATCH_KEY);
+ }
+
+ /**
+ * Set realm for Passpoint credential; realm identifies a set of networks where your
+ * Passpoint credential can be used
+ * @param realm the realm
+ */
+ public void setRealm(String realm) {
+ setFieldValue(REALM_KEY, realm);
+ }
+
+ /**
+ * Get realm for Passpoint credential; see {@link #setRealm(String)} for more information
+ * @return the realm
+ */
+ public String getRealm() {
+ return getFieldValue(REALM_KEY);
+ }
+
+ /**
+ * Set plmn (Public Land Mobile Network) of the provider of Passpoint credential
+ * @param plmn the plmn value derived from mcc (mobile country code) & mnc (mobile network code)
+ */
+ public void setPlmn(String plmn) {
+ setFieldValue(PLMN_KEY, plmn);
+ }
+
+ /**
+ * Get plmn (Public Land Mobile Network) for Passpoint credential; see {@link #setPlmn
+ * (String)} for more information
+ * @return the plmn
+ */
+ public String getPlmn() {
+ return getFieldValue(PLMN_KEY);
+ }
+
+ /** See {@link WifiConfiguration#getKeyIdForCredentials} @hide */
+ public String getKeyId(WifiEnterpriseConfig current) {
+ // If EAP method is not initialized, use current config details
+ if (mEapMethod == Eap.NONE) {
+ return (current != null) ? current.getKeyId(null) : EMPTY_VALUE;
+ }
+ if (!isEapMethodValid()) {
+ return EMPTY_VALUE;
+ }
+ return Eap.strings[mEapMethod] + "_" + Phase2.strings[mPhase2Method];
+ }
+
+ private String removeDoubleQuotes(String string) {
+ if (TextUtils.isEmpty(string)) return "";
+ int length = string.length();
+ if ((length > 1) && (string.charAt(0) == '"')
+ && (string.charAt(length - 1) == '"')) {
+ return string.substring(1, length - 1);
+ }
+ return string;
+ }
+
+ private String convertToQuotedString(String string) {
+ return "\"" + string + "\"";
+ }
+
+ /**
+ * Returns the index at which the toBeFound string is found in the array.
+ * @param arr array of strings
+ * @param toBeFound string to be found
+ * @param defaultIndex default index to be returned when string is not found
+ * @return the index into array
+ */
+ private int getStringIndex(String arr[], String toBeFound, int defaultIndex) {
+ if (TextUtils.isEmpty(toBeFound)) return defaultIndex;
+ for (int i = 0; i < arr.length; i++) {
+ if (toBeFound.equals(arr[i])) return i;
+ }
+ return defaultIndex;
+ }
+
+ /**
+ * Returns the field value for the key with prefix removed.
+ * @param key into the hash
+ * @param prefix is the prefix that the value may have
+ * @return value
+ * @hide
+ */
+ private String getFieldValue(String key, String prefix) {
+ // TODO: Should raise an exception if |key| is EAP_KEY or PHASE2_KEY since
+ // neither of these keys should be retrieved in this manner.
+ String value = mFields.get(key);
+ // Uninitialized or known to be empty after reading from supplicant
+ if (TextUtils.isEmpty(value) || EMPTY_VALUE.equals(value)) return "";
+
+ value = removeDoubleQuotes(value);
+ if (value.startsWith(prefix)) {
+ return value.substring(prefix.length());
+ } else {
+ return value;
+ }
+ }
+
+ /**
+ * Returns the field value for the key.
+ * @param key into the hash
+ * @return value
+ * @hide
+ */
+ public String getFieldValue(String key) {
+ return getFieldValue(key, "");
+ }
+
+ /**
+ * Set a value with an optional prefix at key
+ * @param key into the hash
+ * @param value to be set
+ * @param prefix an optional value to be prefixed to actual value
+ * @hide
+ */
+ private void setFieldValue(String key, String value, String prefix) {
+ // TODO: Should raise an exception if |key| is EAP_KEY or PHASE2_KEY since
+ // neither of these keys should be set in this manner.
+ if (TextUtils.isEmpty(value)) {
+ mFields.put(key, EMPTY_VALUE);
+ } else {
+ String valueToSet;
+ if (!UNQUOTED_KEYS.contains(key)) {
+ valueToSet = convertToQuotedString(prefix + value);
+ } else {
+ valueToSet = prefix + value;
+ }
+ mFields.put(key, valueToSet);
+ }
+ }
+
+ /**
+ * Set a value at key
+ * @param key into the hash
+ * @param value to be set
+ * @hide
+ */
+ public void setFieldValue(String key, String value) {
+ setFieldValue(key, value, "");
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ for (String key : mFields.keySet()) {
+ // Don't display password in toString().
+ String value = PASSWORD_KEY.equals(key) ? "<removed>" : mFields.get(key);
+ sb.append(key).append(" ").append(value).append("\n");
+ }
+ if (mEapMethod >= 0 && mEapMethod < Eap.strings.length) {
+ sb.append("eap_method: ").append(Eap.strings[mEapMethod]).append("\n");
+ }
+ if (mPhase2Method > 0 && mPhase2Method < Phase2.strings.length) {
+ sb.append("phase2_method: ").append(Phase2.strings[mPhase2Method]).append("\n");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Returns whether the EAP method data is valid, i.e., whether mEapMethod and mPhase2Method
+ * are valid indices into {@code Eap.strings[]} and {@code Phase2.strings[]} respectively.
+ */
+ private boolean isEapMethodValid() {
+ if (mEapMethod == Eap.NONE) {
+ Log.e(TAG, "WiFi enterprise configuration is invalid as it supplies no EAP method.");
+ return false;
+ }
+ if (mEapMethod < 0 || mEapMethod >= Eap.strings.length) {
+ Log.e(TAG, "mEapMethod is invald for WiFi enterprise configuration: " + mEapMethod);
+ return false;
+ }
+ if (mPhase2Method < 0 || mPhase2Method >= Phase2.strings.length) {
+ Log.e(TAG, "mPhase2Method is invald for WiFi enterprise configuration: "
+ + mPhase2Method);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Check if certificate was installed by an app, or manually (not by an app). If true,
+ * certificate and keys will be removed from key storage when this network is removed. If not,
+ * then certificates and keys remain persistent until the user manually removes them.
+ *
+ * @return true if certificate was installed by an app, false if certificate was installed
+ * manually by the user.
+ * @hide
+ */
+ public boolean isAppInstalledDeviceKeyAndCert() {
+ return mIsAppInstalledDeviceKeyAndCert;
+ }
+
+ /**
+ * Check if CA certificate was installed by an app, or manually (not by an app). If true,
+ * CA certificate will be removed from key storage when this network is removed. If not,
+ * then certificates and keys remain persistent until the user manually removes them.
+ *
+ * @return true if CA certificate was installed by an app, false if CA certificate was installed
+ * manually by the user.
+ * @hide
+ */
+ public boolean isAppInstalledCaCert() {
+ return mIsAppInstalledCaCert;
+ }
+}
diff --git a/android/net/wifi/WifiInfo.java b/android/net/wifi/WifiInfo.java
new file mode 100644
index 0000000..0b55794
--- /dev/null
+++ b/android/net/wifi/WifiInfo.java
@@ -0,0 +1,785 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.annotation.IntRange;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.UnsupportedAppUsage;
+import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkUtils;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.EnumMap;
+import java.util.Locale;
+
+/**
+ * Describes the state of any Wifi connection that is active or
+ * is in the process of being set up.
+ */
+public class WifiInfo implements Parcelable {
+ private static final String TAG = "WifiInfo";
+ /**
+ * This is the map described in the Javadoc comment above. The positions
+ * of the elements of the array must correspond to the ordinal values
+ * of <code>DetailedState</code>.
+ */
+ private static final EnumMap<SupplicantState, DetailedState> stateMap =
+ new EnumMap<SupplicantState, DetailedState>(SupplicantState.class);
+
+ /**
+ * Default MAC address reported to a client that does not have the
+ * android.permission.LOCAL_MAC_ADDRESS permission.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static final String DEFAULT_MAC_ADDRESS = "02:00:00:00:00:00";
+
+ static {
+ stateMap.put(SupplicantState.DISCONNECTED, DetailedState.DISCONNECTED);
+ stateMap.put(SupplicantState.INTERFACE_DISABLED, DetailedState.DISCONNECTED);
+ stateMap.put(SupplicantState.INACTIVE, DetailedState.IDLE);
+ stateMap.put(SupplicantState.SCANNING, DetailedState.SCANNING);
+ stateMap.put(SupplicantState.AUTHENTICATING, DetailedState.CONNECTING);
+ stateMap.put(SupplicantState.ASSOCIATING, DetailedState.CONNECTING);
+ stateMap.put(SupplicantState.ASSOCIATED, DetailedState.CONNECTING);
+ stateMap.put(SupplicantState.FOUR_WAY_HANDSHAKE, DetailedState.AUTHENTICATING);
+ stateMap.put(SupplicantState.GROUP_HANDSHAKE, DetailedState.AUTHENTICATING);
+ stateMap.put(SupplicantState.COMPLETED, DetailedState.OBTAINING_IPADDR);
+ stateMap.put(SupplicantState.DORMANT, DetailedState.DISCONNECTED);
+ stateMap.put(SupplicantState.UNINITIALIZED, DetailedState.IDLE);
+ stateMap.put(SupplicantState.INVALID, DetailedState.FAILED);
+ }
+
+ private SupplicantState mSupplicantState;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private String mBSSID;
+ @UnsupportedAppUsage
+ private WifiSsid mWifiSsid;
+ private int mNetworkId;
+
+ /** @hide **/
+ @UnsupportedAppUsage
+ public static final int INVALID_RSSI = -127;
+
+ /** @hide **/
+ public static final int MIN_RSSI = -126;
+
+ /** @hide **/
+ public static final int MAX_RSSI = 200;
+
+
+ /**
+ * Received Signal Strength Indicator
+ */
+ private int mRssi;
+
+ /**
+ * The unit in which links speeds are expressed.
+ */
+ public static final String LINK_SPEED_UNITS = "Mbps";
+ private int mLinkSpeed;
+
+ /**
+ * Constant for unknown link speed.
+ */
+ public static final int LINK_SPEED_UNKNOWN = -1;
+
+ /**
+ * Tx(transmit) Link speed in Mbps
+ */
+ private int mTxLinkSpeed;
+
+ /**
+ * Rx(receive) Link speed in Mbps
+ */
+ private int mRxLinkSpeed;
+
+ /**
+ * Frequency in MHz
+ */
+ public static final String FREQUENCY_UNITS = "MHz";
+ private int mFrequency;
+
+ @UnsupportedAppUsage
+ private InetAddress mIpAddress;
+ @UnsupportedAppUsage
+ private String mMacAddress = DEFAULT_MAC_ADDRESS;
+
+ /**
+ * Whether the network is ephemeral or not.
+ */
+ private boolean mEphemeral;
+
+ /**
+ * Whether the network is trusted or not.
+ */
+ private boolean mTrusted;
+
+ /**
+ * OSU (Online Sign Up) AP for Passpoint R2.
+ */
+ private boolean mOsuAp;
+
+ /**
+ * Fully qualified domain name of a Passpoint configuration
+ */
+ private String mFqdn;
+
+ /**
+ * Name of Passpoint credential provider
+ */
+ private String mProviderFriendlyName;
+
+ /**
+ * If connected to a network suggestion or specifier, store the package name of the app,
+ * else null.
+ */
+ private String mNetworkSuggestionOrSpecifierPackageName;
+
+ /**
+ * Running total count of lost (not ACKed) transmitted unicast data packets.
+ * @hide
+ */
+ public long txBad;
+ /**
+ * Running total count of transmitted unicast data retry packets.
+ * @hide
+ */
+ public long txRetries;
+ /**
+ * Running total count of successfully transmitted (ACKed) unicast data packets.
+ * @hide
+ */
+ public long txSuccess;
+ /**
+ * Running total count of received unicast data packets.
+ * @hide
+ */
+ public long rxSuccess;
+
+ /**
+ * Average rate of lost transmitted packets, in units of packets per second.
+ * @hide
+ */
+ public double txBadRate;
+ /**
+ * Average rate of transmitted retry packets, in units of packets per second.
+ * @hide
+ */
+ public double txRetriesRate;
+ /**
+ * Average rate of successfully transmitted unicast packets, in units of packets per second.
+ * @hide
+ */
+ public double txSuccessRate;
+ /**
+ * Average rate of received unicast data packets, in units of packets per second.
+ * @hide
+ */
+ public double rxSuccessRate;
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public int score;
+
+ /**
+ * Flag indicating that AP has hinted that upstream connection is metered,
+ * and sensitive to heavy data transfers.
+ */
+ private boolean mMeteredHint;
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public WifiInfo() {
+ mWifiSsid = null;
+ mBSSID = null;
+ mNetworkId = -1;
+ mSupplicantState = SupplicantState.UNINITIALIZED;
+ mRssi = INVALID_RSSI;
+ mLinkSpeed = LINK_SPEED_UNKNOWN;
+ mFrequency = -1;
+ }
+
+ /** @hide */
+ public void reset() {
+ setInetAddress(null);
+ setBSSID(null);
+ setSSID(null);
+ setNetworkId(-1);
+ setRssi(INVALID_RSSI);
+ setLinkSpeed(LINK_SPEED_UNKNOWN);
+ setTxLinkSpeedMbps(LINK_SPEED_UNKNOWN);
+ setRxLinkSpeedMbps(LINK_SPEED_UNKNOWN);
+ setFrequency(-1);
+ setMeteredHint(false);
+ setEphemeral(false);
+ setOsuAp(false);
+ setNetworkSuggestionOrSpecifierPackageName(null);
+ setFQDN(null);
+ setProviderFriendlyName(null);
+ txBad = 0;
+ txSuccess = 0;
+ rxSuccess = 0;
+ txRetries = 0;
+ txBadRate = 0;
+ txSuccessRate = 0;
+ rxSuccessRate = 0;
+ txRetriesRate = 0;
+ score = 0;
+ }
+
+ /**
+ * Copy constructor
+ * @hide
+ */
+ public WifiInfo(WifiInfo source) {
+ if (source != null) {
+ mSupplicantState = source.mSupplicantState;
+ mBSSID = source.mBSSID;
+ mWifiSsid = source.mWifiSsid;
+ mNetworkId = source.mNetworkId;
+ mRssi = source.mRssi;
+ mLinkSpeed = source.mLinkSpeed;
+ mTxLinkSpeed = source.mTxLinkSpeed;
+ mRxLinkSpeed = source.mRxLinkSpeed;
+ mFrequency = source.mFrequency;
+ mIpAddress = source.mIpAddress;
+ mMacAddress = source.mMacAddress;
+ mMeteredHint = source.mMeteredHint;
+ mEphemeral = source.mEphemeral;
+ mTrusted = source.mTrusted;
+ mNetworkSuggestionOrSpecifierPackageName =
+ source.mNetworkSuggestionOrSpecifierPackageName;
+ mOsuAp = source.mOsuAp;
+ mFqdn = source.mFqdn;
+ mProviderFriendlyName = source.mProviderFriendlyName;
+ txBad = source.txBad;
+ txRetries = source.txRetries;
+ txSuccess = source.txSuccess;
+ rxSuccess = source.rxSuccess;
+ txBadRate = source.txBadRate;
+ txRetriesRate = source.txRetriesRate;
+ txSuccessRate = source.txSuccessRate;
+ rxSuccessRate = source.rxSuccessRate;
+ score = source.score;
+ }
+ }
+
+ /** @hide */
+ public void setSSID(WifiSsid wifiSsid) {
+ mWifiSsid = wifiSsid;
+ }
+
+ /**
+ * Returns the service set identifier (SSID) of the current 802.11 network.
+ * <p>
+ * If the SSID can be decoded as UTF-8, it will be returned surrounded by double
+ * quotation marks. Otherwise, it is returned as a string of hex digits.
+ * The SSID may be
+ * <lt><unknown ssid>, if there is no network currently connected or if the caller has
+ * insufficient permissions to access the SSID.<lt>
+ * </p>
+ * <p>
+ * Prior to {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}, this method
+ * always returned the SSID with no quotes around it.
+ * </p>
+ *
+ * @return the SSID.
+ */
+ public String getSSID() {
+ if (mWifiSsid != null) {
+ String unicode = mWifiSsid.toString();
+ if (!TextUtils.isEmpty(unicode)) {
+ return "\"" + unicode + "\"";
+ } else {
+ String hex = mWifiSsid.getHexString();
+ return (hex != null) ? hex : WifiSsid.NONE;
+ }
+ }
+ return WifiSsid.NONE;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public WifiSsid getWifiSsid() {
+ return mWifiSsid;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public void setBSSID(String BSSID) {
+ mBSSID = BSSID;
+ }
+
+ /**
+ * Return the basic service set identifier (BSSID) of the current access point.
+ * <p>
+ * The BSSID may be
+ * <lt>{@code null}, if there is no network currently connected.</lt>
+ * <lt>{@code "02:00:00:00:00:00"}, if the caller has insufficient permissions to access the
+ * BSSID.<lt>
+ * </p>
+ *
+ * @return the BSSID, in the form of a six-byte MAC address: {@code XX:XX:XX:XX:XX:XX}
+ */
+ public String getBSSID() {
+ return mBSSID;
+ }
+
+ /**
+ * Returns the received signal strength indicator of the current 802.11
+ * network, in dBm.
+ *
+ * <p>Use {@link android.net.wifi.WifiManager#calculateSignalLevel} to convert this number into
+ * an absolute signal level which can be displayed to a user.
+ *
+ * @return the RSSI.
+ */
+ public int getRssi() {
+ return mRssi;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public void setRssi(int rssi) {
+ if (rssi < INVALID_RSSI)
+ rssi = INVALID_RSSI;
+ if (rssi > MAX_RSSI)
+ rssi = MAX_RSSI;
+ mRssi = rssi;
+ }
+
+ /**
+ * Returns the current link speed in {@link #LINK_SPEED_UNITS}.
+ * @return the link speed or {@link #LINK_SPEED_UNKNOWN} if link speed is unknown.
+ * @see #LINK_SPEED_UNITS
+ * @see #LINK_SPEED_UNKNOWN
+ */
+ public int getLinkSpeed() {
+ return mLinkSpeed;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public void setLinkSpeed(int linkSpeed) {
+ mLinkSpeed = linkSpeed;
+ }
+
+ /**
+ * Returns the current transmit link speed in Mbps.
+ * @return the Tx link speed or {@link #LINK_SPEED_UNKNOWN} if link speed is unknown.
+ * @see #LINK_SPEED_UNKNOWN
+ */
+ @IntRange(from = -1)
+ public int getTxLinkSpeedMbps() {
+ return mTxLinkSpeed;
+ }
+
+ /**
+ * Update the last transmitted packet bit rate in Mbps.
+ * @hide
+ */
+ public void setTxLinkSpeedMbps(int txLinkSpeed) {
+ mTxLinkSpeed = txLinkSpeed;
+ }
+
+ /**
+ * Returns the current receive link speed in Mbps.
+ * @return the Rx link speed or {@link #LINK_SPEED_UNKNOWN} if link speed is unknown.
+ * @see #LINK_SPEED_UNKNOWN
+ */
+ @IntRange(from = -1)
+ public int getRxLinkSpeedMbps() {
+ return mRxLinkSpeed;
+ }
+
+ /**
+ * Update the last received packet bit rate in Mbps.
+ * @hide
+ */
+ public void setRxLinkSpeedMbps(int rxLinkSpeed) {
+ mRxLinkSpeed = rxLinkSpeed;
+ }
+
+ /**
+ * Returns the current frequency in {@link #FREQUENCY_UNITS}.
+ * @return the frequency.
+ * @see #FREQUENCY_UNITS
+ */
+ public int getFrequency() {
+ return mFrequency;
+ }
+
+ /** @hide */
+ public void setFrequency(int frequency) {
+ this.mFrequency = frequency;
+ }
+
+ /**
+ * @hide
+ * TODO: makes real freq boundaries
+ */
+ public boolean is24GHz() {
+ return ScanResult.is24GHz(mFrequency);
+ }
+
+ /**
+ * @hide
+ * TODO: makes real freq boundaries
+ */
+ @UnsupportedAppUsage
+ public boolean is5GHz() {
+ return ScanResult.is5GHz(mFrequency);
+ }
+
+ /**
+ * Record the MAC address of the WLAN interface
+ * @param macAddress the MAC address in {@code XX:XX:XX:XX:XX:XX} form
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void setMacAddress(String macAddress) {
+ this.mMacAddress = macAddress;
+ }
+
+ public String getMacAddress() {
+ return mMacAddress;
+ }
+
+ /**
+ * @return true if {@link #getMacAddress()} has a real MAC address.
+ *
+ * @hide
+ */
+ public boolean hasRealMacAddress() {
+ return mMacAddress != null && !DEFAULT_MAC_ADDRESS.equals(mMacAddress);
+ }
+
+ /**
+ * Indicates if we've dynamically detected this active network connection as
+ * being metered.
+ *
+ * @see WifiConfiguration#isMetered(WifiConfiguration, WifiInfo)
+ * @hide
+ */
+ public void setMeteredHint(boolean meteredHint) {
+ mMeteredHint = meteredHint;
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public boolean getMeteredHint() {
+ return mMeteredHint;
+ }
+
+ /** {@hide} */
+ public void setEphemeral(boolean ephemeral) {
+ mEphemeral = ephemeral;
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public boolean isEphemeral() {
+ return mEphemeral;
+ }
+
+ /** {@hide} */
+ public void setTrusted(boolean trusted) {
+ mTrusted = trusted;
+ }
+
+ /** {@hide} */
+ public boolean isTrusted() {
+ return mTrusted;
+ }
+
+ /** {@hide} */
+ public void setOsuAp(boolean osuAp) {
+ mOsuAp = osuAp;
+ }
+
+ /** {@hide} */
+ @SystemApi
+ public boolean isOsuAp() {
+ return mOsuAp;
+ }
+
+ /** {@hide} */
+ @SystemApi
+ public boolean isPasspointAp() {
+ return mFqdn != null && mProviderFriendlyName != null;
+ }
+
+ /** {@hide} */
+ public void setFQDN(@Nullable String fqdn) {
+ mFqdn = fqdn;
+ }
+
+ /**
+ * Returns the Fully Qualified Domain Name of the network if it is a Passpoint network.
+ */
+ public @Nullable String getPasspointFqdn() {
+ return mFqdn;
+ }
+
+ /** {@hide} */
+ public void setProviderFriendlyName(@Nullable String providerFriendlyName) {
+ mProviderFriendlyName = providerFriendlyName;
+ }
+
+ /**
+ * Returns the Provider Friendly Name of the network if it is a Passpoint network.
+ */
+ public @Nullable String getPasspointProviderFriendlyName() {
+ return mProviderFriendlyName;
+ }
+
+ /** {@hide} */
+ public void setNetworkSuggestionOrSpecifierPackageName(@Nullable String packageName) {
+ mNetworkSuggestionOrSpecifierPackageName = packageName;
+ }
+
+ /** {@hide} */
+ public @Nullable String getNetworkSuggestionOrSpecifierPackageName() {
+ return mNetworkSuggestionOrSpecifierPackageName;
+ }
+
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public void setNetworkId(int id) {
+ mNetworkId = id;
+ }
+
+ /**
+ * Each configured network has a unique small integer ID, used to identify
+ * the network. This method returns the ID for the currently connected network.
+ * <p>
+ * The networkId may be {@code -1} if there is no currently connected network or if the caller
+ * has insufficient permissions to access the network ID.
+ * </p>
+ *
+ * @return the network ID.
+ */
+ public int getNetworkId() {
+ return mNetworkId;
+ }
+
+ /**
+ * Return the detailed state of the supplicant's negotiation with an
+ * access point, in the form of a {@link SupplicantState SupplicantState} object.
+ * @return the current {@link SupplicantState SupplicantState}
+ */
+ public SupplicantState getSupplicantState() {
+ return mSupplicantState;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public void setSupplicantState(SupplicantState state) {
+ mSupplicantState = state;
+ }
+
+ /** @hide */
+ public void setInetAddress(InetAddress address) {
+ mIpAddress = address;
+ }
+
+ public int getIpAddress() {
+ int result = 0;
+ if (mIpAddress instanceof Inet4Address) {
+ result = NetworkUtils.inetAddressToInt((Inet4Address)mIpAddress);
+ }
+ return result;
+ }
+
+ /**
+ * @return {@code true} if this network does not broadcast its SSID, so an
+ * SSID-specific probe request must be used for scans.
+ */
+ public boolean getHiddenSSID() {
+ if (mWifiSsid == null) return false;
+ return mWifiSsid.isHidden();
+ }
+
+ /**
+ * Map a supplicant state into a fine-grained network connectivity state.
+ * @param suppState the supplicant state
+ * @return the corresponding {@link DetailedState}
+ */
+ public static DetailedState getDetailedStateOf(SupplicantState suppState) {
+ return stateMap.get(suppState);
+ }
+
+ /**
+ * Set the <code>SupplicantState</code> from the string name
+ * of the state.
+ * @param stateName the name of the state, as a <code>String</code> returned
+ * in an event sent by {@code wpa_supplicant}.
+ */
+ @UnsupportedAppUsage
+ void setSupplicantState(String stateName) {
+ mSupplicantState = valueOf(stateName);
+ }
+
+ static SupplicantState valueOf(String stateName) {
+ if ("4WAY_HANDSHAKE".equalsIgnoreCase(stateName))
+ return SupplicantState.FOUR_WAY_HANDSHAKE;
+ else {
+ try {
+ return SupplicantState.valueOf(stateName.toUpperCase(Locale.ROOT));
+ } catch (IllegalArgumentException e) {
+ return SupplicantState.INVALID;
+ }
+ }
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public static String removeDoubleQuotes(String string) {
+ if (string == null) return null;
+ final int length = string.length();
+ if ((length > 1) && (string.charAt(0) == '"') && (string.charAt(length - 1) == '"')) {
+ return string.substring(1, length - 1);
+ }
+ return string;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ String none = "<none>";
+
+ sb.append("SSID: ").append(mWifiSsid == null ? WifiSsid.NONE : mWifiSsid)
+ .append(", BSSID: ").append(mBSSID == null ? none : mBSSID)
+ .append(", MAC: ").append(mMacAddress == null ? none : mMacAddress)
+ .append(", Supplicant state: ")
+ .append(mSupplicantState == null ? none : mSupplicantState)
+ .append(", RSSI: ").append(mRssi)
+ .append(", Link speed: ").append(mLinkSpeed).append(LINK_SPEED_UNITS)
+ .append(", Tx Link speed: ").append(mTxLinkSpeed).append(LINK_SPEED_UNITS)
+ .append(", Rx Link speed: ").append(mRxLinkSpeed).append(LINK_SPEED_UNITS)
+ .append(", Frequency: ").append(mFrequency).append(FREQUENCY_UNITS)
+ .append(", Net ID: ").append(mNetworkId)
+ .append(", Metered hint: ").append(mMeteredHint)
+ .append(", score: ").append(Integer.toString(score));
+ return sb.toString();
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mNetworkId);
+ dest.writeInt(mRssi);
+ dest.writeInt(mLinkSpeed);
+ dest.writeInt(mTxLinkSpeed);
+ dest.writeInt(mRxLinkSpeed);
+ dest.writeInt(mFrequency);
+ if (mIpAddress != null) {
+ dest.writeByte((byte)1);
+ dest.writeByteArray(mIpAddress.getAddress());
+ } else {
+ dest.writeByte((byte)0);
+ }
+ if (mWifiSsid != null) {
+ dest.writeInt(1);
+ mWifiSsid.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeString(mBSSID);
+ dest.writeString(mMacAddress);
+ dest.writeInt(mMeteredHint ? 1 : 0);
+ dest.writeInt(mEphemeral ? 1 : 0);
+ dest.writeInt(mTrusted ? 1 : 0);
+ dest.writeInt(score);
+ dest.writeLong(txSuccess);
+ dest.writeDouble(txSuccessRate);
+ dest.writeLong(txRetries);
+ dest.writeDouble(txRetriesRate);
+ dest.writeLong(txBad);
+ dest.writeDouble(txBadRate);
+ dest.writeLong(rxSuccess);
+ dest.writeDouble(rxSuccessRate);
+ mSupplicantState.writeToParcel(dest, flags);
+ dest.writeInt(mOsuAp ? 1 : 0);
+ dest.writeString(mNetworkSuggestionOrSpecifierPackageName);
+ dest.writeString(mFqdn);
+ dest.writeString(mProviderFriendlyName);
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ @UnsupportedAppUsage
+ public static final @android.annotation.NonNull Creator<WifiInfo> CREATOR =
+ new Creator<WifiInfo>() {
+ public WifiInfo createFromParcel(Parcel in) {
+ WifiInfo info = new WifiInfo();
+ info.setNetworkId(in.readInt());
+ info.setRssi(in.readInt());
+ info.setLinkSpeed(in.readInt());
+ info.setTxLinkSpeedMbps(in.readInt());
+ info.setRxLinkSpeedMbps(in.readInt());
+ info.setFrequency(in.readInt());
+ if (in.readByte() == 1) {
+ try {
+ info.setInetAddress(InetAddress.getByAddress(in.createByteArray()));
+ } catch (UnknownHostException e) {}
+ }
+ if (in.readInt() == 1) {
+ info.mWifiSsid = WifiSsid.CREATOR.createFromParcel(in);
+ }
+ info.mBSSID = in.readString();
+ info.mMacAddress = in.readString();
+ info.mMeteredHint = in.readInt() != 0;
+ info.mEphemeral = in.readInt() != 0;
+ info.mTrusted = in.readInt() != 0;
+ info.score = in.readInt();
+ info.txSuccess = in.readLong();
+ info.txSuccessRate = in.readDouble();
+ info.txRetries = in.readLong();
+ info.txRetriesRate = in.readDouble();
+ info.txBad = in.readLong();
+ info.txBadRate = in.readDouble();
+ info.rxSuccess = in.readLong();
+ info.rxSuccessRate = in.readDouble();
+ info.mSupplicantState = SupplicantState.CREATOR.createFromParcel(in);
+ info.mOsuAp = in.readInt() != 0;
+ info.mNetworkSuggestionOrSpecifierPackageName = in.readString();
+ info.mFqdn = in.readString();
+ info.mProviderFriendlyName = in.readString();
+ return info;
+ }
+
+ public WifiInfo[] newArray(int size) {
+ return new WifiInfo[size];
+ }
+ };
+}
diff --git a/android/net/wifi/WifiManager.java b/android/net/wifi/WifiManager.java
new file mode 100644
index 0000000..4115663
--- /dev/null
+++ b/android/net/wifi/WifiManager.java
@@ -0,0 +1,4925 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.ACCESS_WIFI_STATE;
+import static android.Manifest.permission.READ_WIFI_CREDENTIAL;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.UnsupportedAppUsage;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.net.ConnectivityManager;
+import android.net.DhcpInfo;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.wifi.hotspot2.IProvisioningCallback;
+import android.net.wifi.hotspot2.OsuProvider;
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.ProvisioningCallback;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.os.WorkSource;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
+import com.android.server.net.NetworkPinner;
+
+import dalvik.system.CloseGuard;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+
+/**
+ * This class provides the primary API for managing all aspects of Wi-Fi
+ * connectivity.
+ * <p>
+ * On releases before {@link android.os.Build.VERSION_CODES#N}, this object
+ * should only be obtained from an {@linkplain Context#getApplicationContext()
+ * application context}, and not from any other derived context to avoid memory
+ * leaks within the calling process.
+ * <p>
+ * It deals with several categories of items:
+ * </p>
+ * <ul>
+ * <li>The list of configured networks. The list can be viewed and updated, and
+ * attributes of individual entries can be modified.</li>
+ * <li>The currently active Wi-Fi network, if any. Connectivity can be
+ * established or torn down, and dynamic information about the state of the
+ * network can be queried.</li>
+ * <li>Results of access point scans, containing enough information to make
+ * decisions about what access point to connect to.</li>
+ * <li>It defines the names of various Intent actions that are broadcast upon
+ * any sort of change in Wi-Fi state.
+ * </ul>
+ * <p>
+ * This is the API to use when performing Wi-Fi specific operations. To perform
+ * operations that pertain to network connectivity at an abstract level, use
+ * {@link android.net.ConnectivityManager}.
+ * </p>
+ */
+@SystemService(Context.WIFI_SERVICE)
+public class WifiManager {
+
+ private static final String TAG = "WifiManager";
+ // Supplicant error codes:
+ /**
+ * The error code if there was a problem authenticating.
+ * @deprecated This is no longer supported.
+ */
+ @Deprecated
+ public static final int ERROR_AUTHENTICATING = 1;
+
+ /**
+ * The reason code if there is no error during authentication.
+ * It could also imply that there no authentication in progress,
+ * this reason code also serves as a reset value.
+ * @deprecated This is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final int ERROR_AUTH_FAILURE_NONE = 0;
+
+ /**
+ * The reason code if there was a timeout authenticating.
+ * @deprecated This is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final int ERROR_AUTH_FAILURE_TIMEOUT = 1;
+
+ /**
+ * The reason code if there was a wrong password while
+ * authenticating.
+ * @deprecated This is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final int ERROR_AUTH_FAILURE_WRONG_PSWD = 2;
+
+ /**
+ * The reason code if there was EAP failure while
+ * authenticating.
+ * @deprecated This is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final int ERROR_AUTH_FAILURE_EAP_FAILURE = 3;
+
+ /**
+ * Maximum number of active network suggestions allowed per app.
+ * @hide
+ */
+ public static final int NETWORK_SUGGESTIONS_MAX_PER_APP =
+ ActivityManager.isLowRamDeviceStatic() ? 256 : 1024;
+
+ /**
+ * Reason code if all of the network suggestions were successfully added or removed.
+ */
+ public static final int STATUS_NETWORK_SUGGESTIONS_SUCCESS = 0;
+
+ /**
+ * Reason code if there was an internal error in the platform while processing the addition or
+ * removal of suggestions.
+ */
+ public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL = 1;
+
+ /**
+ * Reason code if the user has disallowed "android:change_wifi_state" app-ops from the app.
+ * @see android.app.AppOpsManager#unsafeCheckOp(String, int, String).
+ */
+ public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_APP_DISALLOWED = 2;
+
+ /**
+ * Reason code if one or more of the network suggestions added already exists in platform's
+ * database.
+ * @see WifiNetworkSuggestion#equals(Object)
+ */
+ public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE = 3;
+
+ /**
+ * Reason code if the number of network suggestions provided by the app crosses the max
+ * threshold set per app.
+ * @see #getMaxNumberOfNetworkSuggestionsPerApp()
+ */
+ public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP = 4;
+
+ /**
+ * Reason code if one or more of the network suggestions removed does not exist in platform's
+ * database.
+ */
+ public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID = 5;
+
+ /** @hide */
+ @IntDef(prefix = { "STATUS_NETWORK_SUGGESTIONS_" }, value = {
+ STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+ STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL,
+ STATUS_NETWORK_SUGGESTIONS_ERROR_APP_DISALLOWED,
+ STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE,
+ STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP,
+ STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface NetworkSuggestionsStatusCode {}
+
+ /**
+ * Broadcast intent action indicating whether Wi-Fi scanning is allowed currently
+ * @hide
+ */
+ public static final String WIFI_SCAN_AVAILABLE = "wifi_scan_available";
+
+ /**
+ * Extra int indicating scan availability, WIFI_STATE_ENABLED and WIFI_STATE_DISABLED
+ * @hide
+ */
+ public static final String EXTRA_SCAN_AVAILABLE = "scan_enabled";
+
+ /**
+ * Broadcast intent action indicating that the credential of a Wi-Fi network
+ * has been changed. One extra provides the ssid of the network. Another
+ * extra provides the event type, whether the credential is saved or forgot.
+ * @hide
+ */
+ @SystemApi
+ public static final String WIFI_CREDENTIAL_CHANGED_ACTION =
+ "android.net.wifi.WIFI_CREDENTIAL_CHANGED";
+ /** @hide */
+ @SystemApi
+ public static final String EXTRA_WIFI_CREDENTIAL_EVENT_TYPE = "et";
+ /** @hide */
+ @SystemApi
+ public static final String EXTRA_WIFI_CREDENTIAL_SSID = "ssid";
+ /** @hide */
+ @SystemApi
+ public static final int WIFI_CREDENTIAL_SAVED = 0;
+ /** @hide */
+ @SystemApi
+ public static final int WIFI_CREDENTIAL_FORGOT = 1;
+
+ /** @hide */
+ @SystemApi
+ public static final int PASSPOINT_HOME_NETWORK = 0;
+
+ /** @hide */
+ @SystemApi
+ public static final int PASSPOINT_ROAMING_NETWORK = 1;
+
+ /**
+ * Broadcast intent action indicating that a Passpoint provider icon has been received.
+ *
+ * Included extras:
+ * {@link #EXTRA_BSSID_LONG}
+ * {@link #EXTRA_FILENAME}
+ * {@link #EXTRA_ICON}
+ *
+ * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE
+ *
+ * <p>Note: The broadcast is only delivered to registered receivers - no manifest registered
+ * components will be launched.
+ *
+ * @hide
+ */
+ public static final String ACTION_PASSPOINT_ICON = "android.net.wifi.action.PASSPOINT_ICON";
+ /**
+ * BSSID of an AP in long representation. The {@link #EXTRA_BSSID} contains BSSID in
+ * String representation.
+ *
+ * Retrieve with {@link android.content.Intent#getLongExtra(String, long)}.
+ *
+ * @hide
+ */
+ public static final String EXTRA_BSSID_LONG = "android.net.wifi.extra.BSSID_LONG";
+ /**
+ * Icon data.
+ *
+ * Retrieve with {@link android.content.Intent#getParcelableExtra(String)} and cast into
+ * {@link android.graphics.drawable.Icon}.
+ *
+ * @hide
+ */
+ public static final String EXTRA_ICON = "android.net.wifi.extra.ICON";
+ /**
+ * Name of a file.
+ *
+ * Retrieve with {@link android.content.Intent#getStringExtra(String)}.
+ *
+ * @hide
+ */
+ public static final String EXTRA_FILENAME = "android.net.wifi.extra.FILENAME";
+
+ /**
+ * Broadcast intent action indicating a Passpoint OSU Providers List element has been received.
+ *
+ * Included extras:
+ * {@link #EXTRA_BSSID_LONG}
+ * {@link #EXTRA_ANQP_ELEMENT_DATA}
+ *
+ * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE
+ *
+ * <p>Note: The broadcast is only delivered to registered receivers - no manifest registered
+ * components will be launched.
+ *
+ * @hide
+ */
+ public static final String ACTION_PASSPOINT_OSU_PROVIDERS_LIST =
+ "android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST";
+ /**
+ * Raw binary data of an ANQP (Access Network Query Protocol) element.
+ *
+ * Retrieve with {@link android.content.Intent#getByteArrayExtra(String)}.
+ *
+ * @hide
+ */
+ public static final String EXTRA_ANQP_ELEMENT_DATA =
+ "android.net.wifi.extra.ANQP_ELEMENT_DATA";
+
+ /**
+ * Broadcast intent action indicating that a Passpoint Deauth Imminent frame has been received.
+ *
+ * Included extras:
+ * {@link #EXTRA_BSSID_LONG}
+ * {@link #EXTRA_ESS}
+ * {@link #EXTRA_DELAY}
+ * {@link #EXTRA_URL}
+ *
+ * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE
+ *
+ * <p>Note: The broadcast is only delivered to registered receivers - no manifest registered
+ * components will be launched.
+ *
+ * @hide
+ */
+ public static final String ACTION_PASSPOINT_DEAUTH_IMMINENT =
+ "android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT";
+ /**
+ * Flag indicating BSS (Basic Service Set) or ESS (Extended Service Set). This will be set to
+ * {@code true} for ESS.
+ *
+ * Retrieve with {@link android.content.Intent#getBooleanExtra(String, boolean)}.
+ *
+ * @hide
+ */
+ public static final String EXTRA_ESS = "android.net.wifi.extra.ESS";
+ /**
+ * Delay in seconds.
+ *
+ * Retrieve with {@link android.content.Intent#getIntExtra(String, int)}.
+ *
+ * @hide
+ */
+ public static final String EXTRA_DELAY = "android.net.wifi.extra.DELAY";
+ /**
+ * String representation of an URL.
+ *
+ * Retrieve with {@link android.content.Intent#getStringExtra(String)}.
+ *
+ * @hide
+ */
+ public static final String EXTRA_URL = "android.net.wifi.extra.URL";
+
+ /**
+ * Broadcast intent action indicating a Passpoint subscription remediation frame has been
+ * received.
+ *
+ * Included extras:
+ * {@link #EXTRA_BSSID_LONG}
+ * {@link #EXTRA_SUBSCRIPTION_REMEDIATION_METHOD}
+ * {@link #EXTRA_URL}
+ *
+ * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE
+ *
+ * <p>Note: The broadcast is only delivered to registered receivers - no manifest registered
+ * components will be launched.
+ *
+ * @hide
+ */
+ public static final String ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION =
+ "android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION";
+ /**
+ * The protocol supported by the subscription remediation server. The possible values are:
+ * 0 - OMA DM
+ * 1 - SOAP XML SPP
+ *
+ * Retrieve with {@link android.content.Intent#getIntExtra(String, int)}.
+ *
+ * @hide
+ */
+ public static final String EXTRA_SUBSCRIPTION_REMEDIATION_METHOD =
+ "android.net.wifi.extra.SUBSCRIPTION_REMEDIATION_METHOD";
+
+ /**
+ * Activity Action: lunch OSU (Online Sign Up) view.
+ * Included extras:
+ *
+ * {@link #EXTRA_OSU_NETWORK}: {@link Network} instance associated with OSU AP.
+ * {@link #EXTRA_URL}: String representation of a server URL used for OSU process.
+ *
+ * <p>Note: The broadcast is only delivered to registered receivers - no manifest registered
+ * components will be launched.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_PASSPOINT_LAUNCH_OSU_VIEW =
+ "android.net.wifi.action.PASSPOINT_LAUNCH_OSU_VIEW";
+
+ /**
+ * The lookup key for a {@link android.net.Network} associated with OSU server.
+ *
+ * Retrieve with {@link android.content.Intent#getParcelableExtra(String)}.
+ *
+ * @hide
+ */
+ public static final String EXTRA_OSU_NETWORK = "android.net.wifi.extra.OSU_NETWORK";
+
+ /**
+ * Broadcast intent action indicating that Wi-Fi has been enabled, disabled,
+ * enabling, disabling, or unknown. One extra provides this state as an int.
+ * Another extra provides the previous state, if available.
+ *
+ * @see #EXTRA_WIFI_STATE
+ * @see #EXTRA_PREVIOUS_WIFI_STATE
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String WIFI_STATE_CHANGED_ACTION =
+ "android.net.wifi.WIFI_STATE_CHANGED";
+ /**
+ * The lookup key for an int that indicates whether Wi-Fi is enabled,
+ * disabled, enabling, disabling, or unknown. Retrieve it with
+ * {@link android.content.Intent#getIntExtra(String,int)}.
+ *
+ * @see #WIFI_STATE_DISABLED
+ * @see #WIFI_STATE_DISABLING
+ * @see #WIFI_STATE_ENABLED
+ * @see #WIFI_STATE_ENABLING
+ * @see #WIFI_STATE_UNKNOWN
+ */
+ public static final String EXTRA_WIFI_STATE = "wifi_state";
+ /**
+ * The previous Wi-Fi state.
+ *
+ * @see #EXTRA_WIFI_STATE
+ */
+ public static final String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state";
+
+ /**
+ * Wi-Fi is currently being disabled. The state will change to {@link #WIFI_STATE_DISABLED} if
+ * it finishes successfully.
+ *
+ * @see #WIFI_STATE_CHANGED_ACTION
+ * @see #getWifiState()
+ */
+ public static final int WIFI_STATE_DISABLING = 0;
+ /**
+ * Wi-Fi is disabled.
+ *
+ * @see #WIFI_STATE_CHANGED_ACTION
+ * @see #getWifiState()
+ */
+ public static final int WIFI_STATE_DISABLED = 1;
+ /**
+ * Wi-Fi is currently being enabled. The state will change to {@link #WIFI_STATE_ENABLED} if
+ * it finishes successfully.
+ *
+ * @see #WIFI_STATE_CHANGED_ACTION
+ * @see #getWifiState()
+ */
+ public static final int WIFI_STATE_ENABLING = 2;
+ /**
+ * Wi-Fi is enabled.
+ *
+ * @see #WIFI_STATE_CHANGED_ACTION
+ * @see #getWifiState()
+ */
+ public static final int WIFI_STATE_ENABLED = 3;
+ /**
+ * Wi-Fi is in an unknown state. This state will occur when an error happens while enabling
+ * or disabling.
+ *
+ * @see #WIFI_STATE_CHANGED_ACTION
+ * @see #getWifiState()
+ */
+ public static final int WIFI_STATE_UNKNOWN = 4;
+
+ /**
+ * Broadcast intent action indicating that Wi-Fi AP has been enabled, disabled,
+ * enabling, disabling, or failed.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String WIFI_AP_STATE_CHANGED_ACTION =
+ "android.net.wifi.WIFI_AP_STATE_CHANGED";
+
+ /**
+ * The lookup key for an int that indicates whether Wi-Fi AP is enabled,
+ * disabled, enabling, disabling, or failed. Retrieve it with
+ * {@link android.content.Intent#getIntExtra(String,int)}.
+ *
+ * @see #WIFI_AP_STATE_DISABLED
+ * @see #WIFI_AP_STATE_DISABLING
+ * @see #WIFI_AP_STATE_ENABLED
+ * @see #WIFI_AP_STATE_ENABLING
+ * @see #WIFI_AP_STATE_FAILED
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_WIFI_AP_STATE = "wifi_state";
+
+ /**
+ * The look up key for an int that indicates why softAP started failed
+ * currently support general and no_channel
+ * @see #SAP_START_FAILURE_GENERAL
+ * @see #SAP_START_FAILURE_NO_CHANNEL
+ *
+ * @hide
+ */
+ public static final String EXTRA_WIFI_AP_FAILURE_REASON = "wifi_ap_error_code";
+ /**
+ * The previous Wi-Fi state.
+ *
+ * @see #EXTRA_WIFI_AP_STATE
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_PREVIOUS_WIFI_AP_STATE = "previous_wifi_state";
+ /**
+ * The interface used for the softap.
+ *
+ * @hide
+ */
+ public static final String EXTRA_WIFI_AP_INTERFACE_NAME = "wifi_ap_interface_name";
+ /**
+ * The intended ip mode for this softap.
+ * @see #IFACE_IP_MODE_TETHERED
+ * @see #IFACE_IP_MODE_LOCAL_ONLY
+ *
+ * @hide
+ */
+ public static final String EXTRA_WIFI_AP_MODE = "wifi_ap_mode";
+
+ /** @hide */
+ @IntDef(flag = false, prefix = { "WIFI_AP_STATE_" }, value = {
+ WIFI_AP_STATE_DISABLING,
+ WIFI_AP_STATE_DISABLED,
+ WIFI_AP_STATE_ENABLING,
+ WIFI_AP_STATE_ENABLED,
+ WIFI_AP_STATE_FAILED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface WifiApState {}
+
+ /**
+ * Wi-Fi AP is currently being disabled. The state will change to
+ * {@link #WIFI_AP_STATE_DISABLED} if it finishes successfully.
+ *
+ * @see #WIFI_AP_STATE_CHANGED_ACTION
+ * @see #getWifiApState()
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int WIFI_AP_STATE_DISABLING = 10;
+ /**
+ * Wi-Fi AP is disabled.
+ *
+ * @see #WIFI_AP_STATE_CHANGED_ACTION
+ * @see #getWifiState()
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int WIFI_AP_STATE_DISABLED = 11;
+ /**
+ * Wi-Fi AP is currently being enabled. The state will change to
+ * {@link #WIFI_AP_STATE_ENABLED} if it finishes successfully.
+ *
+ * @see #WIFI_AP_STATE_CHANGED_ACTION
+ * @see #getWifiApState()
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int WIFI_AP_STATE_ENABLING = 12;
+ /**
+ * Wi-Fi AP is enabled.
+ *
+ * @see #WIFI_AP_STATE_CHANGED_ACTION
+ * @see #getWifiApState()
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int WIFI_AP_STATE_ENABLED = 13;
+ /**
+ * Wi-Fi AP is in a failed state. This state will occur when an error occurs during
+ * enabling or disabling
+ *
+ * @see #WIFI_AP_STATE_CHANGED_ACTION
+ * @see #getWifiApState()
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int WIFI_AP_STATE_FAILED = 14;
+
+ /** @hide */
+ @IntDef(flag = false, prefix = { "SAP_START_FAILURE_" }, value = {
+ SAP_START_FAILURE_GENERAL,
+ SAP_START_FAILURE_NO_CHANNEL,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SapStartFailure {}
+
+ /**
+ * All other reasons for AP start failure besides {@link #SAP_START_FAILURE_NO_CHANNEL}.
+ *
+ * @hide
+ */
+ public static final int SAP_START_FAILURE_GENERAL= 0;
+
+ /**
+ * If Wi-Fi AP start failed, this reason code means that no legal channel exists on user
+ * selected band due to regulatory constraints.
+ *
+ * @hide
+ */
+ public static final int SAP_START_FAILURE_NO_CHANNEL = 1;
+
+ /**
+ * Interface IP mode unspecified.
+ *
+ * @see updateInterfaceIpState(String, int)
+ *
+ * @hide
+ */
+ public static final int IFACE_IP_MODE_UNSPECIFIED = -1;
+
+ /**
+ * Interface IP mode for configuration error.
+ *
+ * @see updateInterfaceIpState(String, int)
+ *
+ * @hide
+ */
+ public static final int IFACE_IP_MODE_CONFIGURATION_ERROR = 0;
+
+ /**
+ * Interface IP mode for tethering.
+ *
+ * @see updateInterfaceIpState(String, int)
+ *
+ * @hide
+ */
+ public static final int IFACE_IP_MODE_TETHERED = 1;
+
+ /**
+ * Interface IP mode for Local Only Hotspot.
+ *
+ * @see updateInterfaceIpState(String, int)
+ *
+ * @hide
+ */
+ public static final int IFACE_IP_MODE_LOCAL_ONLY = 2;
+
+ /**
+ * Broadcast intent action indicating that a connection to the supplicant has
+ * been established (and it is now possible
+ * to perform Wi-Fi operations) or the connection to the supplicant has been
+ * lost. One extra provides the connection state as a boolean, where {@code true}
+ * means CONNECTED.
+ * @deprecated This is no longer supported.
+ * @see #EXTRA_SUPPLICANT_CONNECTED
+ */
+ @Deprecated
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SUPPLICANT_CONNECTION_CHANGE_ACTION =
+ "android.net.wifi.supplicant.CONNECTION_CHANGE";
+ /**
+ * The lookup key for a boolean that indicates whether a connection to
+ * the supplicant daemon has been gained or lost. {@code true} means
+ * a connection now exists.
+ * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}.
+ * @deprecated This is no longer supported.
+ */
+ @Deprecated
+ public static final String EXTRA_SUPPLICANT_CONNECTED = "connected";
+ /**
+ * Broadcast intent action indicating that the state of Wi-Fi connectivity
+ * has changed. An extra provides the new state
+ * in the form of a {@link android.net.NetworkInfo} object.
+ * @see #EXTRA_NETWORK_INFO
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String NETWORK_STATE_CHANGED_ACTION = "android.net.wifi.STATE_CHANGE";
+ /**
+ * The lookup key for a {@link android.net.NetworkInfo} object associated with the
+ * Wi-Fi network. Retrieve with
+ * {@link android.content.Intent#getParcelableExtra(String)}.
+ */
+ public static final String EXTRA_NETWORK_INFO = "networkInfo";
+ /**
+ * The lookup key for a String giving the BSSID of the access point to which
+ * we are connected. No longer used.
+ */
+ @Deprecated
+ public static final String EXTRA_BSSID = "bssid";
+ /**
+ * The lookup key for a {@link android.net.wifi.WifiInfo} object giving the
+ * information about the access point to which we are connected.
+ * No longer used.
+ */
+ @Deprecated
+ public static final String EXTRA_WIFI_INFO = "wifiInfo";
+ /**
+ * Broadcast intent action indicating that the state of establishing a connection to
+ * an access point has changed.One extra provides the new
+ * {@link SupplicantState}. Note that the supplicant state is Wi-Fi specific, and
+ * is not generally the most useful thing to look at if you are just interested in
+ * the overall state of connectivity.
+ * @see #EXTRA_NEW_STATE
+ * @see #EXTRA_SUPPLICANT_ERROR
+ * @deprecated This is no longer supported.
+ */
+ @Deprecated
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SUPPLICANT_STATE_CHANGED_ACTION =
+ "android.net.wifi.supplicant.STATE_CHANGE";
+ /**
+ * The lookup key for a {@link SupplicantState} describing the new state
+ * Retrieve with
+ * {@link android.content.Intent#getParcelableExtra(String)}.
+ * @deprecated This is no longer supported.
+ */
+ @Deprecated
+ public static final String EXTRA_NEW_STATE = "newState";
+
+ /**
+ * The lookup key for a {@link SupplicantState} describing the supplicant
+ * error code if any
+ * Retrieve with
+ * {@link android.content.Intent#getIntExtra(String, int)}.
+ * @see #ERROR_AUTHENTICATING
+ * @deprecated This is no longer supported.
+ */
+ @Deprecated
+ public static final String EXTRA_SUPPLICANT_ERROR = "supplicantError";
+
+ /**
+ * The lookup key for a {@link SupplicantState} describing the supplicant
+ * error reason if any
+ * Retrieve with
+ * {@link android.content.Intent#getIntExtra(String, int)}.
+ * @see #ERROR_AUTH_FAILURE_#REASON_CODE
+ * @deprecated This is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String EXTRA_SUPPLICANT_ERROR_REASON = "supplicantErrorReason";
+
+ /**
+ * Broadcast intent action indicating that the configured networks changed.
+ * This can be as a result of adding/updating/deleting a network. If
+ * {@link #EXTRA_MULTIPLE_NETWORKS_CHANGED} is set to true the new configuration
+ * can be retreived with the {@link #EXTRA_WIFI_CONFIGURATION} extra. If multiple
+ * Wi-Fi configurations changed, {@link #EXTRA_WIFI_CONFIGURATION} will not be present.
+ * @hide
+ */
+ @SystemApi
+ public static final String CONFIGURED_NETWORKS_CHANGED_ACTION =
+ "android.net.wifi.CONFIGURED_NETWORKS_CHANGE";
+ /**
+ * The lookup key for a (@link android.net.wifi.WifiConfiguration} object representing
+ * the changed Wi-Fi configuration when the {@link #CONFIGURED_NETWORKS_CHANGED_ACTION}
+ * broadcast is sent.
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_WIFI_CONFIGURATION = "wifiConfiguration";
+ /**
+ * Multiple network configurations have changed.
+ * @see #CONFIGURED_NETWORKS_CHANGED_ACTION
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges";
+ /**
+ * The lookup key for an integer indicating the reason a Wi-Fi network configuration
+ * has changed. Only present if {@link #EXTRA_MULTIPLE_NETWORKS_CHANGED} is {@code false}
+ * @see #CONFIGURED_NETWORKS_CHANGED_ACTION
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_CHANGE_REASON = "changeReason";
+ /**
+ * The configuration is new and was added.
+ * @hide
+ */
+ @SystemApi
+ public static final int CHANGE_REASON_ADDED = 0;
+ /**
+ * The configuration was removed and is no longer present in the system's list of
+ * configured networks.
+ * @hide
+ */
+ @SystemApi
+ public static final int CHANGE_REASON_REMOVED = 1;
+ /**
+ * The configuration has changed as a result of explicit action or because the system
+ * took an automated action such as disabling a malfunctioning configuration.
+ * @hide
+ */
+ @SystemApi
+ public static final int CHANGE_REASON_CONFIG_CHANGE = 2;
+ /**
+ * An access point scan has completed, and results are available.
+ * Call {@link #getScanResults()} to obtain the results.
+ * The broadcast intent may contain an extra field with the key {@link #EXTRA_RESULTS_UPDATED}
+ * and a {@code boolean} value indicating if the scan was successful.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SCAN_RESULTS_AVAILABLE_ACTION = "android.net.wifi.SCAN_RESULTS";
+
+ /**
+ * Lookup key for a {@code boolean} extra in intent {@link #SCAN_RESULTS_AVAILABLE_ACTION}
+ * representing if the scan was successful or not.
+ * Scans may fail for multiple reasons, these may include:
+ * <ol>
+ * <li>An app requested too many scans in a certain period of time.
+ * This may lead to additional scan request rejections via "scan throttling" for both
+ * foreground and background apps.
+ * Note: Apps holding android.Manifest.permission.NETWORK_SETTINGS permission are
+ * exempted from scan throttling.
+ * </li>
+ * <li>The device is idle and scanning is disabled.</li>
+ * <li>Wifi hardware reported a scan failure.</li>
+ * </ol>
+ * @return true scan was successful, results are updated
+ * @return false scan was not successful, results haven't been updated since previous scan
+ */
+ public static final String EXTRA_RESULTS_UPDATED = "resultsUpdated";
+
+ /**
+ * A batch of access point scans has been completed and the results areavailable.
+ * Call {@link #getBatchedScanResults()} to obtain the results.
+ * @deprecated This API is nolonger supported.
+ * Use {@link android.net.wifi.WifiScanner} API
+ * @hide
+ */
+ @Deprecated
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String BATCHED_SCAN_RESULTS_AVAILABLE_ACTION =
+ "android.net.wifi.BATCHED_RESULTS";
+
+ /**
+ * The RSSI (signal strength) has changed.
+ *
+ * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE
+ * @see {@link #EXTRA_NEW_RSSI}
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String RSSI_CHANGED_ACTION = "android.net.wifi.RSSI_CHANGED";
+ /**
+ * The lookup key for an {@code int} giving the new RSSI in dBm.
+ */
+ public static final String EXTRA_NEW_RSSI = "newRssi";
+
+ /**
+ * Broadcast intent action indicating that the link configuration
+ * changed on wifi.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static final String LINK_CONFIGURATION_CHANGED_ACTION =
+ "android.net.wifi.LINK_CONFIGURATION_CHANGED";
+
+ /**
+ * The lookup key for a {@link android.net.LinkProperties} object associated with the
+ * Wi-Fi network. Retrieve with
+ * {@link android.content.Intent#getParcelableExtra(String)}.
+ * @hide
+ */
+ public static final String EXTRA_LINK_PROPERTIES = "linkProperties";
+
+ /**
+ * The lookup key for a {@link android.net.NetworkCapabilities} object associated with the
+ * Wi-Fi network. Retrieve with
+ * {@link android.content.Intent#getParcelableExtra(String)}.
+ * @hide
+ */
+ public static final String EXTRA_NETWORK_CAPABILITIES = "networkCapabilities";
+
+ /**
+ * The network IDs of the configured networks could have changed.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String NETWORK_IDS_CHANGED_ACTION = "android.net.wifi.NETWORK_IDS_CHANGED";
+
+ /**
+ * Activity Action: Show a system activity that allows the user to enable
+ * scans to be available even with Wi-Fi turned off.
+ *
+ * <p>Notification of the result of this activity is posted using the
+ * {@link android.app.Activity#onActivityResult} callback. The
+ * <code>resultCode</code>
+ * will be {@link android.app.Activity#RESULT_OK} if scan always mode has
+ * been turned on or {@link android.app.Activity#RESULT_CANCELED} if the user
+ * has rejected the request or an error has occurred.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE =
+ "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE";
+
+ /**
+ * Activity Action: Pick a Wi-Fi network to connect to.
+ * <p>Input: Nothing.
+ * <p>Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
+
+ /**
+ * Activity Action: Show UI to get user approval to enable WiFi.
+ * <p>Input: {@link android.content.Intent#EXTRA_PACKAGE_NAME} string extra with
+ * the name of the app requesting the action.
+ * <p>Output: Nothing.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_REQUEST_ENABLE = "android.net.wifi.action.REQUEST_ENABLE";
+
+ /**
+ * Activity Action: Show UI to get user approval to disable WiFi.
+ * <p>Input: {@link android.content.Intent#EXTRA_PACKAGE_NAME} string extra with
+ * the name of the app requesting the action.
+ * <p>Output: Nothing.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_REQUEST_DISABLE = "android.net.wifi.action.REQUEST_DISABLE";
+
+ /**
+ * Directed broadcast intent action indicating that the device has connected to one of the
+ * network suggestions provided by the app. This will be sent post connection to a network
+ * which was created with {@link WifiNetworkSuggestion.Builder#setIsAppInteractionRequired(
+ * boolean)}
+ * flag set.
+ * <p>
+ * Note: The broadcast is sent to the app only if it holds
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission.
+ *
+ * @see #EXTRA_NETWORK_SUGGESTION
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION =
+ "android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION";
+ /**
+ * Sent as as a part of {@link #ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} that holds
+ * an instance of {@link WifiNetworkSuggestion} corresponding to the connected network.
+ */
+ public static final String EXTRA_NETWORK_SUGGESTION =
+ "android.net.wifi.extra.NETWORK_SUGGESTION";
+
+ /**
+ * Internally used Wi-Fi lock mode representing the case were no locks are held.
+ * @hide
+ */
+ public static final int WIFI_MODE_NO_LOCKS_HELD = 0;
+
+ /**
+ * In this Wi-Fi lock mode, Wi-Fi will be kept active,
+ * and will behave normally, i.e., it will attempt to automatically
+ * establish a connection to a remembered access point that is
+ * within range, and will do periodic scans if there are remembered
+ * access points but none are in range.
+ *
+ * @deprecated This API is non-functional and will have no impact.
+ */
+ @Deprecated
+ public static final int WIFI_MODE_FULL = WifiProtoEnums.WIFI_MODE_FULL; // 1
+
+ /**
+ * In this Wi-Fi lock mode, Wi-Fi will be kept active,
+ * but the only operation that will be supported is initiation of
+ * scans, and the subsequent reporting of scan results. No attempts
+ * will be made to automatically connect to remembered access points,
+ * nor will periodic scans be automatically performed looking for
+ * remembered access points. Scans must be explicitly requested by
+ * an application in this mode.
+ *
+ * @deprecated This API is non-functional and will have no impact.
+ */
+ @Deprecated
+ public static final int WIFI_MODE_SCAN_ONLY = WifiProtoEnums.WIFI_MODE_SCAN_ONLY; // 2
+
+ /**
+ * In this Wi-Fi lock mode, Wi-Fi will not go to power save.
+ * This results in operating with low packet latency.
+ * The lock is only active when the device is connected to an access point.
+ * The lock is active even when the device screen is off or the acquiring application is
+ * running in the background.
+ * This mode will consume more power and hence should be used only
+ * when there is a need for this tradeoff.
+ * <p>
+ * An example use case is when a voice connection needs to be
+ * kept active even after the device screen goes off.
+ * Holding a {@link #WIFI_MODE_FULL_HIGH_PERF} lock for the
+ * duration of the voice call may improve the call quality.
+ * <p>
+ * When there is no support from the hardware, the {@link #WIFI_MODE_FULL_HIGH_PERF}
+ * lock will have no impact.
+ */
+ public static final int WIFI_MODE_FULL_HIGH_PERF = WifiProtoEnums.WIFI_MODE_FULL_HIGH_PERF; // 3
+
+ /**
+ * In this Wi-Fi lock mode, Wi-Fi will operate with a priority to achieve low latency.
+ * {@link #WIFI_MODE_FULL_LOW_LATENCY} lock has the following limitations:
+ * <ol>
+ * <li>The lock is only active when the device is connected to an access point.</li>
+ * <li>The lock is only active when the screen is on.</li>
+ * <li>The lock is only active when the acquiring app is running in the foreground.</li>
+ * </ol>
+ * Low latency mode optimizes for reduced packet latency,
+ * and as a result other performance measures may suffer when there are trade-offs to make:
+ * <ol>
+ * <li>Battery life may be reduced.</li>
+ * <li>Throughput may be reduced.</li>
+ * <li>Frequency of Wi-Fi scanning may be reduced. This may result in: </li>
+ * <ul>
+ * <li>The device may not roam or switch to the AP with highest signal quality.</li>
+ * <li>Location accuracy may be reduced.</li>
+ * </ul>
+ * </ol>
+ * <p>
+ * Example use cases are real time gaming or virtual reality applications where
+ * low latency is a key factor for user experience.
+ * <p>
+ * Note: For an app which acquires both {@link #WIFI_MODE_FULL_LOW_LATENCY} and
+ * {@link #WIFI_MODE_FULL_HIGH_PERF} locks, {@link #WIFI_MODE_FULL_LOW_LATENCY}
+ * lock will be effective when app is running in foreground and screen is on,
+ * while the {@link #WIFI_MODE_FULL_HIGH_PERF} lock will take effect otherwise.
+ */
+ public static final int WIFI_MODE_FULL_LOW_LATENCY =
+ WifiProtoEnums.WIFI_MODE_FULL_LOW_LATENCY; // 4
+
+ /** Anything worse than or equal to this will show 0 bars. */
+ @UnsupportedAppUsage
+ private static final int MIN_RSSI = -100;
+
+ /** Anything better than or equal to this will show the max bars. */
+ @UnsupportedAppUsage
+ private static final int MAX_RSSI = -55;
+
+ /**
+ * Number of RSSI levels used in the framework to initiate
+ * {@link #RSSI_CHANGED_ACTION} broadcast
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static final int RSSI_LEVELS = 5;
+
+ /**
+ * Auto settings in the driver. The driver could choose to operate on both
+ * 2.4 GHz and 5 GHz or make a dynamic decision on selecting the band.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static final int WIFI_FREQUENCY_BAND_AUTO = 0;
+
+ /**
+ * Operation on 5 GHz alone
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static final int WIFI_FREQUENCY_BAND_5GHZ = 1;
+
+ /**
+ * Operation on 2.4 GHz alone
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public static final int WIFI_FREQUENCY_BAND_2GHZ = 2;
+
+ /** @hide */
+ public static final boolean DEFAULT_POOR_NETWORK_AVOIDANCE_ENABLED = false;
+
+ /* Maximum number of active locks we allow.
+ * This limit was added to prevent apps from creating a ridiculous number
+ * of locks and crashing the system by overflowing the global ref table.
+ */
+ private static final int MAX_ACTIVE_LOCKS = 50;
+
+ /* Number of currently active WifiLocks and MulticastLocks */
+ @UnsupportedAppUsage
+ private int mActiveLockCount;
+
+ private Context mContext;
+ @UnsupportedAppUsage
+ IWifiManager mService;
+ private final int mTargetSdkVersion;
+
+ private static final int INVALID_KEY = 0;
+ private int mListenerKey = 1;
+ private final SparseArray mListenerMap = new SparseArray();
+ private final Object mListenerMapLock = new Object();
+
+ private AsyncChannel mAsyncChannel;
+ private CountDownLatch mConnected;
+ private Looper mLooper;
+ private boolean mVerboseLoggingEnabled = false;
+
+ /* LocalOnlyHotspot callback message types */
+ /** @hide */
+ public static final int HOTSPOT_STARTED = 0;
+ /** @hide */
+ public static final int HOTSPOT_STOPPED = 1;
+ /** @hide */
+ public static final int HOTSPOT_FAILED = 2;
+ /** @hide */
+ public static final int HOTSPOT_OBSERVER_REGISTERED = 3;
+
+ private final Object mLock = new Object(); // lock guarding access to the following vars
+ @GuardedBy("mLock")
+ private LocalOnlyHotspotCallbackProxy mLOHSCallbackProxy;
+ @GuardedBy("mLock")
+ private LocalOnlyHotspotObserverProxy mLOHSObserverProxy;
+
+ /**
+ * Create a new WifiManager instance.
+ * Applications will almost always want to use
+ * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
+ * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}.
+ * @param context the application context
+ * @param service the Binder interface
+ * @hide - hide this because it takes in a parameter of type IWifiManager, which
+ * is a system private class.
+ */
+ public WifiManager(Context context, IWifiManager service, Looper looper) {
+ mContext = context;
+ mService = service;
+ mLooper = looper;
+ mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
+ updateVerboseLoggingEnabledFromService();
+ }
+
+ /**
+ * Return a list of all the networks configured for the current foreground
+ * user.
+ *
+ * Not all fields of WifiConfiguration are returned. Only the following
+ * fields are filled in:
+ * <ul>
+ * <li>networkId</li>
+ * <li>SSID</li>
+ * <li>BSSID</li>
+ * <li>priority</li>
+ * <li>allowedProtocols</li>
+ * <li>allowedKeyManagement</li>
+ * <li>allowedAuthAlgorithms</li>
+ * <li>allowedPairwiseCiphers</li>
+ * <li>allowedGroupCiphers</li>
+ * </ul>
+ * @return a list of network configurations in the form of a list
+ * of {@link WifiConfiguration} objects.
+ *
+ * @deprecated
+ * a) See {@link WifiNetworkSpecifier.Builder#build()} for new
+ * mechanism to trigger connection to a Wi-Fi network.
+ * b) See {@link #addNetworkSuggestions(List)},
+ * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
+ * when auto-connecting to wifi.
+ * <b>Compatibility Note:</b> For applications targeting
+ * {@link android.os.Build.VERSION_CODES#Q} or above, this API will return an empty list,
+ * except for:
+ * <ul>
+ * <li>Device Owner (DO) & Profile Owner (PO) apps will have access to the full list.
+ * <li>Callers with Carrier privilege will receive a restricted list only containing
+ * configurations which they created.
+ * </ul>
+ */
+ @Deprecated
+ @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE})
+ public List<WifiConfiguration> getConfiguredNetworks() {
+ try {
+ ParceledListSlice<WifiConfiguration> parceledList =
+ mService.getConfiguredNetworks(mContext.getOpPackageName());
+ if (parceledList == null) {
+ return Collections.emptyList();
+ }
+ return parceledList.getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ @SystemApi
+ @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE, READ_WIFI_CREDENTIAL})
+ public List<WifiConfiguration> getPrivilegedConfiguredNetworks() {
+ try {
+ ParceledListSlice<WifiConfiguration> parceledList =
+ mService.getPrivilegedConfiguredNetworks(mContext.getOpPackageName());
+ if (parceledList == null) {
+ return Collections.emptyList();
+ }
+ return parceledList.getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns a list of all matching WifiConfigurations for a given list of ScanResult.
+ *
+ * An empty list will be returned when no configurations are installed or if no configurations
+ * match the ScanResult.
+ *
+ * @param scanResults a list of scanResult that represents the BSSID
+ * @return List that consists of {@link WifiConfiguration} and corresponding scanResults per
+ * network type({@link #PASSPOINT_HOME_NETWORK} and {@link #PASSPOINT_ROAMING_NETWORK}).
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD
+ })
+ @NonNull
+ public List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> getAllMatchingWifiConfigs(
+ @NonNull List<ScanResult> scanResults) {
+ List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> configs = new ArrayList<>();
+ try {
+ Map<String, Map<Integer, List<ScanResult>>> results =
+ mService.getAllMatchingFqdnsForScanResults(
+ scanResults);
+ if (results.isEmpty()) {
+ return configs;
+ }
+ List<WifiConfiguration> wifiConfigurations =
+ mService.getWifiConfigsForPasspointProfiles(
+ new ArrayList<>(results.keySet()));
+ for (WifiConfiguration configuration : wifiConfigurations) {
+ Map<Integer, List<ScanResult>> scanResultsPerNetworkType = results.get(
+ configuration.FQDN);
+ if (scanResultsPerNetworkType != null) {
+ configs.add(Pair.create(configuration, scanResultsPerNetworkType));
+ }
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ return configs;
+ }
+
+ /**
+ * Returns a list of unique Hotspot 2.0 OSU (Online Sign-Up) providers associated with a given
+ * list of ScanResult.
+ *
+ * An empty list will be returned if no match is found.
+ *
+ * @param scanResults a list of ScanResult
+ * @return Map that consists {@link OsuProvider} and a list of matching {@link ScanResult}
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD
+ })
+ @NonNull
+ public Map<OsuProvider, List<ScanResult>> getMatchingOsuProviders(
+ @Nullable List<ScanResult> scanResults) {
+ if (scanResults == null) {
+ return new HashMap<>();
+ }
+ try {
+ return mService.getMatchingOsuProviders(scanResults);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the matching Passpoint R2 configurations for given OSU (Online Sign-Up) providers.
+ *
+ * Given a list of OSU providers, this only returns OSU providers that already have Passpoint R2
+ * configurations in the device.
+ * An empty map will be returned when there is no matching Passpoint R2 configuration for the
+ * given OsuProviders.
+ *
+ * @param osuProviders a set of {@link OsuProvider}
+ * @return Map that consists of {@link OsuProvider} and matching {@link PasspointConfiguration}.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD
+ })
+ @NonNull
+ public Map<OsuProvider, PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders(
+ @NonNull Set<OsuProvider> osuProviders) {
+ try {
+ return mService.getMatchingPasspointConfigsForOsuProviders(
+ new ArrayList<>(osuProviders));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Add a new network description to the set of configured networks.
+ * The {@code networkId} field of the supplied configuration object
+ * is ignored.
+ * <p/>
+ * The new network will be marked DISABLED by default. To enable it,
+ * called {@link #enableNetwork}.
+ *
+ * @param config the set of variables that describe the configuration,
+ * contained in a {@link WifiConfiguration} object.
+ * If the {@link WifiConfiguration} has an Http Proxy set
+ * the calling app must be System, or be provisioned as the Profile or Device Owner.
+ * @return the ID of the newly created network description. This is used in
+ * other operations to specified the network to be acted upon.
+ * Returns {@code -1} on failure.
+ *
+ * @deprecated
+ * a) See {@link WifiNetworkSpecifier.Builder#build()} for new
+ * mechanism to trigger connection to a Wi-Fi network.
+ * b) See {@link #addNetworkSuggestions(List)},
+ * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
+ * when auto-connecting to wifi.
+ * <b>Compatibility Note:</b> For applications targeting
+ * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always return {@code -1}.
+ */
+ @Deprecated
+ public int addNetwork(WifiConfiguration config) {
+ if (config == null) {
+ return -1;
+ }
+ config.networkId = -1;
+ return addOrUpdateNetwork(config);
+ }
+
+ /**
+ * Update the network description of an existing configured network.
+ *
+ * @param config the set of variables that describe the configuration,
+ * contained in a {@link WifiConfiguration} object. It may
+ * be sparse, so that only the items that are being changed
+ * are non-<code>null</code>. The {@code networkId} field
+ * must be set to the ID of the existing network being updated.
+ * If the {@link WifiConfiguration} has an Http Proxy set
+ * the calling app must be System, or be provisioned as the Profile or Device Owner.
+ * @return Returns the {@code networkId} of the supplied
+ * {@code WifiConfiguration} on success.
+ * <br/>
+ * Returns {@code -1} on failure, including when the {@code networkId}
+ * field of the {@code WifiConfiguration} does not refer to an
+ * existing network.
+ *
+ * @deprecated
+ * a) See {@link WifiNetworkSpecifier.Builder#build()} for new
+ * mechanism to trigger connection to a Wi-Fi network.
+ * b) See {@link #addNetworkSuggestions(List)},
+ * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
+ * when auto-connecting to wifi.
+ * <b>Compatibility Note:</b> For applications targeting
+ * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always return {@code -1}.
+ */
+ @Deprecated
+ public int updateNetwork(WifiConfiguration config) {
+ if (config == null || config.networkId < 0) {
+ return -1;
+ }
+ return addOrUpdateNetwork(config);
+ }
+
+ /**
+ * Internal method for doing the RPC that creates a new network description
+ * or updates an existing one.
+ *
+ * @param config The possibly sparse object containing the variables that
+ * are to set or updated in the network description.
+ * @return the ID of the network on success, {@code -1} on failure.
+ */
+ private int addOrUpdateNetwork(WifiConfiguration config) {
+ try {
+ return mService.addOrUpdateNetwork(config, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Interface for indicating user selection from the list of networks presented in the
+ * {@link NetworkRequestMatchCallback#onMatch(List)}.
+ *
+ * The platform will implement this callback and pass it along with the
+ * {@link NetworkRequestMatchCallback#onUserSelectionCallbackRegistration(
+ * NetworkRequestUserSelectionCallback)}. The UI component handling
+ * {@link NetworkRequestMatchCallback} will invoke {@link #select(WifiConfiguration)} or
+ * {@link #reject()} to return the user's selection back to the platform via this callback.
+ * @hide
+ */
+ public interface NetworkRequestUserSelectionCallback {
+ /**
+ * User selected this network to connect to.
+ * @param wifiConfiguration WifiConfiguration object corresponding to the network
+ * user selected.
+ */
+ void select(@NonNull WifiConfiguration wifiConfiguration);
+
+ /**
+ * User rejected the app's request.
+ */
+ void reject();
+ }
+
+ /**
+ * Interface for network request callback. Should be implemented by applications and passed when
+ * calling {@link #registerNetworkRequestMatchCallback(NetworkRequestMatchCallback, Handler)}.
+ *
+ * This is meant to be implemented by a UI component to present the user with a list of networks
+ * matching the app's request. The user is allowed to pick one of these networks to connect to
+ * or reject the request by the app.
+ * @hide
+ */
+ public interface NetworkRequestMatchCallback {
+ /**
+ * Invoked to register a callback to be invoked to convey user selection. The callback
+ * object paased in this method is to be invoked by the UI component after the service sends
+ * a list of matching scan networks using {@link #onMatch(List)} and user picks a network
+ * from that list.
+ *
+ * @param userSelectionCallback Callback object to send back the user selection.
+ */
+ void onUserSelectionCallbackRegistration(
+ @NonNull NetworkRequestUserSelectionCallback userSelectionCallback);
+
+ /**
+ * Invoked when the active network request is aborted, either because
+ * <li> The app released the request, OR</li>
+ * <li> Request was overridden by a new request</li>
+ * This signals the end of processing for the current request and should stop the UI
+ * component. No subsequent calls from the UI component will be handled by the platform.
+ */
+ void onAbort();
+
+ /**
+ * Invoked when a network request initiated by an app matches some networks in scan results.
+ * This may be invoked multiple times for a single network request as the platform finds new
+ * matching networks in scan results.
+ *
+ * @param scanResults List of {@link ScanResult} objects corresponding to the networks
+ * matching the request.
+ */
+ void onMatch(@NonNull List<ScanResult> scanResults);
+
+ /**
+ * Invoked on a successful connection with the network that the user selected
+ * via {@link NetworkRequestUserSelectionCallback}.
+ *
+ * @param wifiConfiguration WifiConfiguration object corresponding to the network that the
+ * user selected.
+ */
+ void onUserSelectionConnectSuccess(@NonNull WifiConfiguration wifiConfiguration);
+
+ /**
+ * Invoked on failure to establish connection with the network that the user selected
+ * via {@link NetworkRequestUserSelectionCallback}.
+ *
+ * @param wifiConfiguration WifiConfiguration object corresponding to the network
+ * user selected.
+ */
+ void onUserSelectionConnectFailure(@NonNull WifiConfiguration wifiConfiguration);
+ }
+
+ /**
+ * Callback proxy for NetworkRequestUserSelectionCallback objects.
+ * @hide
+ */
+ private class NetworkRequestUserSelectionCallbackProxy implements
+ NetworkRequestUserSelectionCallback {
+ private final INetworkRequestUserSelectionCallback mCallback;
+
+ NetworkRequestUserSelectionCallbackProxy(
+ INetworkRequestUserSelectionCallback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void select(@NonNull WifiConfiguration wifiConfiguration) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "NetworkRequestUserSelectionCallbackProxy: select "
+ + "wificonfiguration: " + wifiConfiguration);
+ }
+ try {
+ mCallback.select(wifiConfiguration);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to invoke onSelected", e);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void reject() {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "NetworkRequestUserSelectionCallbackProxy: reject");
+ }
+ try {
+ mCallback.reject();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to invoke onRejected", e);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Callback proxy for NetworkRequestMatchCallback objects.
+ * @hide
+ */
+ private class NetworkRequestMatchCallbackProxy extends INetworkRequestMatchCallback.Stub {
+ private final Handler mHandler;
+ private final NetworkRequestMatchCallback mCallback;
+
+ NetworkRequestMatchCallbackProxy(Looper looper, NetworkRequestMatchCallback callback) {
+ mHandler = new Handler(looper);
+ mCallback = callback;
+ }
+
+ @Override
+ public void onUserSelectionCallbackRegistration(
+ INetworkRequestUserSelectionCallback userSelectionCallback) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "NetworkRequestMatchCallbackProxy: "
+ + "onUserSelectionCallbackRegistration callback: " + userSelectionCallback);
+ }
+ mHandler.post(() -> {
+ mCallback.onUserSelectionCallbackRegistration(
+ new NetworkRequestUserSelectionCallbackProxy(userSelectionCallback));
+ });
+ }
+
+ @Override
+ public void onAbort() {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "NetworkRequestMatchCallbackProxy: onAbort");
+ }
+ mHandler.post(() -> {
+ mCallback.onAbort();
+ });
+ }
+
+ @Override
+ public void onMatch(List<ScanResult> scanResults) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "NetworkRequestMatchCallbackProxy: onMatch scanResults: "
+ + scanResults);
+ }
+ mHandler.post(() -> {
+ mCallback.onMatch(scanResults);
+ });
+ }
+
+ @Override
+ public void onUserSelectionConnectSuccess(WifiConfiguration wifiConfiguration) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "NetworkRequestMatchCallbackProxy: onUserSelectionConnectSuccess "
+ + " wificonfiguration: " + wifiConfiguration);
+ }
+ mHandler.post(() -> {
+ mCallback.onUserSelectionConnectSuccess(wifiConfiguration);
+ });
+ }
+
+ @Override
+ public void onUserSelectionConnectFailure(WifiConfiguration wifiConfiguration) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "NetworkRequestMatchCallbackProxy: onUserSelectionConnectFailure"
+ + " wificonfiguration: " + wifiConfiguration);
+ }
+ mHandler.post(() -> {
+ mCallback.onUserSelectionConnectFailure(wifiConfiguration);
+ });
+ }
+ }
+
+ /**
+ * Registers a callback for NetworkRequest matches. See {@link NetworkRequestMatchCallback}.
+ * Caller can unregister a previously registered callback using
+ * {@link #unregisterNetworkRequestMatchCallback(NetworkRequestMatchCallback)}
+ * <p>
+ * Applications should have the
+ * {@link android.Manifest.permission#NETWORK_SETTINGS} permission. Callers
+ * without the permission will trigger a {@link java.lang.SecurityException}.
+ * <p>
+ *
+ * @param callback Callback for network match events
+ * @param handler The Handler on whose thread to execute the callbacks of the {@code callback}
+ * object. If null, then the application's main thread will be used.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public void registerNetworkRequestMatchCallback(@NonNull NetworkRequestMatchCallback callback,
+ @Nullable Handler handler) {
+ if (callback == null) throw new IllegalArgumentException("callback cannot be null");
+ Log.v(TAG, "registerNetworkRequestMatchCallback: callback=" + callback
+ + ", handler=" + handler);
+
+ Looper looper = (handler == null) ? mContext.getMainLooper() : handler.getLooper();
+ Binder binder = new Binder();
+ try {
+ mService.registerNetworkRequestMatchCallback(
+ binder, new NetworkRequestMatchCallbackProxy(looper, callback),
+ callback.hashCode());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregisters a callback for NetworkRequest matches. See {@link NetworkRequestMatchCallback}.
+ * <p>
+ * Applications should have the
+ * {@link android.Manifest.permission#NETWORK_SETTINGS} permission. Callers
+ * without the permission will trigger a {@link java.lang.SecurityException}.
+ * <p>
+ *
+ * @param callback Callback for network match events
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public void unregisterNetworkRequestMatchCallback(
+ @NonNull NetworkRequestMatchCallback callback) {
+ if (callback == null) throw new IllegalArgumentException("callback cannot be null");
+ Log.v(TAG, "unregisterNetworkRequestMatchCallback: callback=" + callback);
+
+ try {
+ mService.unregisterNetworkRequestMatchCallback(callback.hashCode());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Provide a list of network suggestions to the device. See {@link WifiNetworkSuggestion}
+ * for a detailed explanation of the parameters.
+ * When the device decides to connect to one of the provided network suggestions, platform sends
+ * a directed broadcast {@link #ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} to the app if
+ * the network was created with {@link WifiNetworkSuggestion.Builder
+ * #setIsAppInteractionRequired()} flag set and the app holds
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission.
+ *<p>
+ * NOTE:
+ * <li> These networks are just a suggestion to the platform. The platform will ultimately
+ * decide on which network the device connects to. </li>
+ * <li> When an app is uninstalled, all its suggested networks are discarded. If the device is
+ * currently connected to a suggested network which is being removed then the device will
+ * disconnect from that network.</li>
+ * <li> No in-place modification of existing suggestions are allowed. Apps are expected to
+ * remove suggestions using {@link #removeNetworkSuggestions(List)} and then add the modified
+ * suggestion back using this API.</li>
+ *
+ * @param networkSuggestions List of network suggestions provided by the app.
+ * @return Status code for the operation. One of the STATUS_NETWORK_SUGGESTIONS_ values.
+ * {@link WifiNetworkSuggestion#equals(Object)} any previously provided suggestions by the app.
+ * @throws {@link SecurityException} if the caller is missing required permissions.
+ */
+ @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE)
+ public @NetworkSuggestionsStatusCode int addNetworkSuggestions(
+ @NonNull List<WifiNetworkSuggestion> networkSuggestions) {
+ try {
+ return mService.addNetworkSuggestions(networkSuggestions, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove some or all of the network suggestions that were previously provided by the app.
+ * See {@link WifiNetworkSuggestion} for a detailed explanation of the parameters.
+ * See {@link WifiNetworkSuggestion#equals(Object)} for the equivalence evaluation used.
+ *
+ * @param networkSuggestions List of network suggestions to be removed. Pass an empty list
+ * to remove all the previous suggestions provided by the app.
+ * @return Status code for the operation. One of the STATUS_NETWORK_SUGGESTIONS_ values.
+ * Any matching suggestions are removed from the device and will not be considered for any
+ * further connection attempts.
+ */
+ @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE)
+ public @NetworkSuggestionsStatusCode int removeNetworkSuggestions(
+ @NonNull List<WifiNetworkSuggestion> networkSuggestions) {
+ try {
+ return mService.removeNetworkSuggestions(
+ networkSuggestions, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the max number of network suggestions that are allowed per app on the device.
+ * @see #addNetworkSuggestions(List)
+ * @see #removeNetworkSuggestions(List)
+ */
+ public int getMaxNumberOfNetworkSuggestionsPerApp() {
+ return NETWORK_SUGGESTIONS_MAX_PER_APP;
+ }
+
+ /**
+ * Add or update a Passpoint configuration. The configuration provides a credential
+ * for connecting to Passpoint networks that are operated by the Passpoint
+ * service provider specified in the configuration.
+ *
+ * Each configuration is uniquely identified by its FQDN (Fully Qualified Domain
+ * Name). In the case when there is an existing configuration with the same
+ * FQDN, the new configuration will replace the existing configuration.
+ *
+ * @param config The Passpoint configuration to be added
+ * @throws IllegalArgumentException if configuration is invalid or Passpoint is not enabled on
+ * the device.
+ */
+ public void addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
+ try {
+ if (!mService.addOrUpdatePasspointConfiguration(config, mContext.getOpPackageName())) {
+ throw new IllegalArgumentException();
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove the Passpoint configuration identified by its FQDN (Fully Qualified Domain Name).
+ *
+ * @param fqdn The FQDN of the Passpoint configuration to be removed
+ * @throws IllegalArgumentException if no configuration is associated with the given FQDN or
+ * Passpoint is not enabled on the device.
+ * @deprecated This is no longer supported.
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public void removePasspointConfiguration(String fqdn) {
+ try {
+ if (!mService.removePasspointConfiguration(fqdn, mContext.getOpPackageName())) {
+ throw new IllegalArgumentException();
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return the list of installed Passpoint configurations.
+ *
+ * An empty list will be returned when no configurations are installed.
+ *
+ * @return A list of {@link PasspointConfiguration}
+ * @deprecated This is no longer supported.
+ */
+ @Deprecated
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD
+ })
+ public List<PasspointConfiguration> getPasspointConfigurations() {
+ try {
+ return mService.getPasspointConfigurations(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Query for a Hotspot 2.0 release 2 OSU icon file. An {@link #ACTION_PASSPOINT_ICON} intent
+ * will be broadcasted once the request is completed. The presence of the intent extra
+ * {@link #EXTRA_ICON} will indicate the result of the request.
+ * A missing intent extra {@link #EXTRA_ICON} will indicate a failure.
+ *
+ * @param bssid The BSSID of the AP
+ * @param fileName Name of the icon file (remote file) to query from the AP
+ *
+ * @throws UnsupportedOperationException if Passpoint is not enabled on the device.
+ * @hide
+ */
+ public void queryPasspointIcon(long bssid, String fileName) {
+ try {
+ mService.queryPasspointIcon(bssid, fileName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Match the currently associated network against the SP matching the given FQDN
+ * @param fqdn FQDN of the SP
+ * @return ordinal [HomeProvider, RoamingProvider, Incomplete, None, Declined]
+ * @hide
+ */
+ public int matchProviderWithCurrentNetwork(String fqdn) {
+ try {
+ return mService.matchProviderWithCurrentNetwork(fqdn);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Deauthenticate and set the re-authentication hold off time for the current network
+ * @param holdoff hold off time in milliseconds
+ * @param ess set if the hold off pertains to an ESS rather than a BSS
+ * @hide
+ */
+ public void deauthenticateNetwork(long holdoff, boolean ess) {
+ try {
+ mService.deauthenticateNetwork(holdoff, ess);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove the specified network from the list of configured networks.
+ * This may result in the asynchronous delivery of state change
+ * events.
+ *
+ * Applications are not allowed to remove networks created by other
+ * applications.
+ *
+ * @param netId the ID of the network as returned by {@link #addNetwork} or {@link
+ * #getConfiguredNetworks}.
+ * @return {@code true} if the operation succeeded
+ *
+ * @deprecated
+ * a) See {@link WifiNetworkSpecifier.Builder#build()} for new
+ * mechanism to trigger connection to a Wi-Fi network.
+ * b) See {@link #addNetworkSuggestions(List)},
+ * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
+ * when auto-connecting to wifi.
+ * <b>Compatibility Note:</b> For applications targeting
+ * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always return false.
+ */
+ @Deprecated
+ public boolean removeNetwork(int netId) {
+ try {
+ return mService.removeNetwork(netId, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Allow a previously configured network to be associated with. If
+ * <code>attemptConnect</code> is true, an attempt to connect to the selected
+ * network is initiated. This may result in the asynchronous delivery
+ * of state change events.
+ * <p>
+ * <b>Note:</b> Network communication may not use Wi-Fi even if Wi-Fi is connected;
+ * traffic may instead be sent through another network, such as cellular data,
+ * Bluetooth tethering, or Ethernet. For example, traffic will never use a
+ * Wi-Fi network that does not provide Internet access (e.g. a wireless
+ * printer), if another network that does offer Internet access (e.g.
+ * cellular data) is available. Applications that need to ensure that their
+ * network traffic uses Wi-Fi should use APIs such as
+ * {@link Network#bindSocket(java.net.Socket)},
+ * {@link Network#openConnection(java.net.URL)}, or
+ * {@link ConnectivityManager#bindProcessToNetwork} to do so.
+ *
+ * Applications are not allowed to enable networks created by other
+ * applications.
+ *
+ * @param netId the ID of the network as returned by {@link #addNetwork} or {@link
+ * #getConfiguredNetworks}.
+ * @param attemptConnect The way to select a particular network to connect to is specify
+ * {@code true} for this parameter.
+ * @return {@code true} if the operation succeeded
+ *
+ * @deprecated
+ * a) See {@link WifiNetworkSpecifier.Builder#build()} for new
+ * mechanism to trigger connection to a Wi-Fi network.
+ * b) See {@link #addNetworkSuggestions(List)},
+ * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
+ * when auto-connecting to wifi.
+ * <b>Compatibility Note:</b> For applications targeting
+ * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always return false.
+ */
+ @Deprecated
+ public boolean enableNetwork(int netId, boolean attemptConnect) {
+ final boolean pin = attemptConnect && mTargetSdkVersion < Build.VERSION_CODES.LOLLIPOP;
+ if (pin) {
+ NetworkRequest request = new NetworkRequest.Builder()
+ .clearCapabilities()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
+ NetworkPinner.pin(mContext, request);
+ }
+
+ boolean success;
+ try {
+ success = mService.enableNetwork(netId, attemptConnect, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ if (pin && !success) {
+ NetworkPinner.unpin();
+ }
+
+ return success;
+ }
+
+ /**
+ * Disable a configured network. The specified network will not be
+ * a candidate for associating. This may result in the asynchronous
+ * delivery of state change events.
+ *
+ * Applications are not allowed to disable networks created by other
+ * applications.
+ *
+ * @param netId the ID of the network as returned by {@link #addNetwork} or {@link
+ * #getConfiguredNetworks}.
+ * @return {@code true} if the operation succeeded
+ *
+ * @deprecated
+ * a) See {@link WifiNetworkSpecifier.Builder#build()} for new
+ * mechanism to trigger connection to a Wi-Fi network.
+ * b) See {@link #addNetworkSuggestions(List)},
+ * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
+ * when auto-connecting to wifi.
+ * <b>Compatibility Note:</b> For applications targeting
+ * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always return false.
+ */
+ @Deprecated
+ public boolean disableNetwork(int netId) {
+ try {
+ return mService.disableNetwork(netId, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Disassociate from the currently active access point. This may result
+ * in the asynchronous delivery of state change events.
+ * @return {@code true} if the operation succeeded
+ *
+ * @deprecated
+ * a) See {@link WifiNetworkSpecifier.Builder#build()} for new
+ * mechanism to trigger connection to a Wi-Fi network.
+ * b) See {@link #addNetworkSuggestions(List)},
+ * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
+ * when auto-connecting to wifi.
+ * <b>Compatibility Note:</b> For applications targeting
+ * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always return false.
+ */
+ @Deprecated
+ public boolean disconnect() {
+ try {
+ return mService.disconnect(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Reconnect to the currently active access point, if we are currently
+ * disconnected. This may result in the asynchronous delivery of state
+ * change events.
+ * @return {@code true} if the operation succeeded
+ *
+ * @deprecated
+ * a) See {@link WifiNetworkSpecifier.Builder#build()} for new
+ * mechanism to trigger connection to a Wi-Fi network.
+ * b) See {@link #addNetworkSuggestions(List)},
+ * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
+ * when auto-connecting to wifi.
+ * <b>Compatibility Note:</b> For applications targeting
+ * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always return false.
+ */
+ @Deprecated
+ public boolean reconnect() {
+ try {
+ return mService.reconnect(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Reconnect to the currently active access point, even if we are already
+ * connected. This may result in the asynchronous delivery of state
+ * change events.
+ * @return {@code true} if the operation succeeded
+ *
+ * @deprecated
+ * a) See {@link WifiNetworkSpecifier.Builder#build()} for new
+ * mechanism to trigger connection to a Wi-Fi network.
+ * b) See {@link #addNetworkSuggestions(List)},
+ * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration
+ * when auto-connecting to wifi.
+ * <b>Compatibility Note:</b> For applications targeting
+ * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always return false.
+ */
+ @Deprecated
+ public boolean reassociate() {
+ try {
+ return mService.reassociate(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Check that the supplicant daemon is responding to requests.
+ * @return {@code true} if we were able to communicate with the supplicant and
+ * it returned the expected response to the PING message.
+ * @deprecated Will return the output of {@link #isWifiEnabled()} instead.
+ */
+ @Deprecated
+ public boolean pingSupplicant() {
+ return isWifiEnabled();
+ }
+
+ /** @hide */
+ public static final int WIFI_FEATURE_INFRA = 0x0001; // Basic infrastructure mode
+ /** @hide */
+ public static final int WIFI_FEATURE_INFRA_5G = 0x0002; // Support for 5 GHz Band
+ /** @hide */
+ public static final int WIFI_FEATURE_PASSPOINT = 0x0004; // Support for GAS/ANQP
+ /** @hide */
+ public static final int WIFI_FEATURE_P2P = 0x0008; // Wifi-Direct
+ /** @hide */
+ public static final int WIFI_FEATURE_MOBILE_HOTSPOT = 0x0010; // Soft AP
+ /** @hide */
+ public static final int WIFI_FEATURE_SCANNER = 0x0020; // WifiScanner APIs
+ /** @hide */
+ public static final int WIFI_FEATURE_AWARE = 0x0040; // Wi-Fi AWare networking
+ /** @hide */
+ public static final int WIFI_FEATURE_D2D_RTT = 0x0080; // Device-to-device RTT
+ /** @hide */
+ public static final int WIFI_FEATURE_D2AP_RTT = 0x0100; // Device-to-AP RTT
+ /** @hide */
+ public static final int WIFI_FEATURE_BATCH_SCAN = 0x0200; // Batched Scan (deprecated)
+ /** @hide */
+ public static final int WIFI_FEATURE_PNO = 0x0400; // Preferred network offload
+ /** @hide */
+ public static final int WIFI_FEATURE_ADDITIONAL_STA = 0x0800; // Support for two STAs
+ /** @hide */
+ public static final int WIFI_FEATURE_TDLS = 0x1000; // Tunnel directed link setup
+ /** @hide */
+ public static final int WIFI_FEATURE_TDLS_OFFCHANNEL = 0x2000; // Support for TDLS off channel
+ /** @hide */
+ public static final int WIFI_FEATURE_EPR = 0x4000; // Enhanced power reporting
+ /** @hide */
+ public static final int WIFI_FEATURE_AP_STA = 0x8000; // AP STA Concurrency
+ /** @hide */
+ public static final int WIFI_FEATURE_LINK_LAYER_STATS = 0x10000; // Link layer stats collection
+ /** @hide */
+ public static final int WIFI_FEATURE_LOGGER = 0x20000; // WiFi Logger
+ /** @hide */
+ public static final int WIFI_FEATURE_HAL_EPNO = 0x40000; // Enhanced PNO
+ /** @hide */
+ public static final int WIFI_FEATURE_RSSI_MONITOR = 0x80000; // RSSI Monitor
+ /** @hide */
+ public static final int WIFI_FEATURE_MKEEP_ALIVE = 0x100000; // mkeep_alive
+ /** @hide */
+ public static final int WIFI_FEATURE_CONFIG_NDO = 0x200000; // ND offload
+ /** @hide */
+ public static final int WIFI_FEATURE_TRANSMIT_POWER = 0x400000; // Capture transmit power
+ /** @hide */
+ public static final int WIFI_FEATURE_CONTROL_ROAMING = 0x800000; // Control firmware roaming
+ /** @hide */
+ public static final int WIFI_FEATURE_IE_WHITELIST = 0x1000000; // Probe IE white listing
+ /** @hide */
+ public static final int WIFI_FEATURE_SCAN_RAND = 0x2000000; // Random MAC & Probe seq
+ /** @hide */
+ public static final int WIFI_FEATURE_TX_POWER_LIMIT = 0x4000000; // Set Tx power limit
+ /** @hide */
+ public static final int WIFI_FEATURE_WPA3_SAE = 0x8000000; // WPA3-Personal SAE
+ /** @hide */
+ public static final int WIFI_FEATURE_WPA3_SUITE_B = 0x10000000; // WPA3-Enterprise Suite-B
+ /** @hide */
+ public static final int WIFI_FEATURE_OWE = 0x20000000; // Enhanced Open
+ /** @hide */
+ public static final int WIFI_FEATURE_LOW_LATENCY = 0x40000000; // Low Latency modes
+ /** @hide */
+ public static final int WIFI_FEATURE_DPP = 0x80000000; // DPP (Easy-Connect)
+ /** @hide */
+ public static final long WIFI_FEATURE_P2P_RAND_MAC = 0x100000000L; // Random P2P MAC
+
+ private long getSupportedFeatures() {
+ try {
+ return mService.getSupportedFeatures();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private boolean isFeatureSupported(long feature) {
+ return (getSupportedFeatures() & feature) == feature;
+ }
+ /**
+ * @return true if this adapter supports 5 GHz band
+ */
+ public boolean is5GHzBandSupported() {
+ return isFeatureSupported(WIFI_FEATURE_INFRA_5G);
+ }
+
+ /**
+ * @return true if this adapter supports Passpoint
+ * @hide
+ */
+ public boolean isPasspointSupported() {
+ return isFeatureSupported(WIFI_FEATURE_PASSPOINT);
+ }
+
+ /**
+ * @return true if this adapter supports WifiP2pManager (Wi-Fi Direct)
+ */
+ public boolean isP2pSupported() {
+ return isFeatureSupported(WIFI_FEATURE_P2P);
+ }
+
+ /**
+ * @return true if this adapter supports portable Wi-Fi hotspot
+ * @hide
+ */
+ @SystemApi
+ public boolean isPortableHotspotSupported() {
+ return isFeatureSupported(WIFI_FEATURE_MOBILE_HOTSPOT);
+ }
+
+ /**
+ * @return true if this adapter supports WifiScanner APIs
+ * @hide
+ */
+ @SystemApi
+ public boolean isWifiScannerSupported() {
+ return isFeatureSupported(WIFI_FEATURE_SCANNER);
+ }
+
+ /**
+ * @return true if this adapter supports Neighbour Awareness Network APIs
+ * @hide
+ */
+ public boolean isWifiAwareSupported() {
+ return isFeatureSupported(WIFI_FEATURE_AWARE);
+ }
+
+ /**
+ * @deprecated Please use {@link android.content.pm.PackageManager#hasSystemFeature(String)}
+ * with {@link android.content.pm.PackageManager#FEATURE_WIFI_RTT} and
+ * {@link android.content.pm.PackageManager#FEATURE_WIFI_AWARE}.
+ *
+ * @return true if this adapter supports Device-to-device RTT
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public boolean isDeviceToDeviceRttSupported() {
+ return isFeatureSupported(WIFI_FEATURE_D2D_RTT);
+ }
+
+ /**
+ * @deprecated Please use {@link android.content.pm.PackageManager#hasSystemFeature(String)}
+ * with {@link android.content.pm.PackageManager#FEATURE_WIFI_RTT}.
+ *
+ * @return true if this adapter supports Device-to-AP RTT
+ */
+ @Deprecated
+ public boolean isDeviceToApRttSupported() {
+ return isFeatureSupported(WIFI_FEATURE_D2AP_RTT);
+ }
+
+ /**
+ * @return true if this adapter supports offloaded connectivity scan
+ */
+ public boolean isPreferredNetworkOffloadSupported() {
+ return isFeatureSupported(WIFI_FEATURE_PNO);
+ }
+
+ /**
+ * @return true if this adapter supports multiple simultaneous connections
+ * @hide
+ */
+ public boolean isAdditionalStaSupported() {
+ return isFeatureSupported(WIFI_FEATURE_ADDITIONAL_STA);
+ }
+
+ /**
+ * @return true if this adapter supports Tunnel Directed Link Setup
+ */
+ public boolean isTdlsSupported() {
+ return isFeatureSupported(WIFI_FEATURE_TDLS);
+ }
+
+ /**
+ * @return true if this adapter supports Off Channel Tunnel Directed Link Setup
+ * @hide
+ */
+ public boolean isOffChannelTdlsSupported() {
+ return isFeatureSupported(WIFI_FEATURE_TDLS_OFFCHANNEL);
+ }
+
+ /**
+ * @return true if this adapter supports advanced power/performance counters
+ */
+ public boolean isEnhancedPowerReportingSupported() {
+ return isFeatureSupported(WIFI_FEATURE_LINK_LAYER_STATS);
+ }
+
+ /**
+ * Return the record of {@link WifiActivityEnergyInfo} object that
+ * has the activity and energy info. This can be used to ascertain what
+ * the controller has been up to, since the last sample.
+ *
+ * @return a record with {@link WifiActivityEnergyInfo} or null if
+ * report is unavailable or unsupported
+ * @hide
+ */
+ public WifiActivityEnergyInfo getControllerActivityEnergyInfo() {
+ if (mService == null) return null;
+ try {
+ synchronized(this) {
+ return mService.reportActivityInfo();
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Request a scan for access points. Returns immediately. The availability
+ * of the results is made known later by means of an asynchronous event sent
+ * on completion of the scan.
+ * <p>
+ * To initiate a Wi-Fi scan, declare the
+ * {@link android.Manifest.permission#CHANGE_WIFI_STATE}
+ * permission in the manifest, and perform these steps:
+ * </p>
+ * <ol style="1">
+ * <li>Invoke the following method:
+ * {@code ((WifiManager) getSystemService(WIFI_SERVICE)).startScan()}</li>
+ * <li>
+ * Register a BroadcastReceiver to listen to
+ * {@code SCAN_RESULTS_AVAILABLE_ACTION}.</li>
+ * <li>When a broadcast is received, call:
+ * {@code ((WifiManager) getSystemService(WIFI_SERVICE)).getScanResults()}</li>
+ * </ol>
+ * @return {@code true} if the operation succeeded, i.e., the scan was initiated.
+ * @deprecated The ability for apps to trigger scan requests will be removed in a future
+ * release.
+ */
+ @Deprecated
+ public boolean startScan() {
+ return startScan(null);
+ }
+
+ /** @hide */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ public boolean startScan(WorkSource workSource) {
+ try {
+ String packageName = mContext.getOpPackageName();
+ return mService.startScan(packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * WPS has been deprecated from Client mode operation.
+ *
+ * @return null
+ * @hide
+ * @deprecated This API is deprecated
+ */
+ public String getCurrentNetworkWpsNfcConfigurationToken() {
+ return null;
+ }
+
+ /**
+ * Return dynamic information about the current Wi-Fi connection, if any is active.
+ * <p>
+ * In the connected state, access to the SSID and BSSID requires
+ * the same permissions as {@link #getScanResults}. If such access is not allowed,
+ * {@link WifiInfo#getSSID} will return {@code "<unknown ssid>"} and
+ * {@link WifiInfo#getBSSID} will return {@code "02:00:00:00:00:00"}.
+ *
+ * @return the Wi-Fi information, contained in {@link WifiInfo}.
+ */
+ public WifiInfo getConnectionInfo() {
+ try {
+ return mService.getConnectionInfo(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return the results of the latest access point scan.
+ * @return the list of access points found in the most recent scan. An app must hold
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission
+ * in order to get valid results.
+ */
+ public List<ScanResult> getScanResults() {
+ try {
+ return mService.getScanResults(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Check if scanning is always available.
+ *
+ * If this return {@code true}, apps can issue {@link #startScan} and fetch scan results
+ * even when Wi-Fi is turned off.
+ *
+ * To change this setting, see {@link #ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE}.
+ * @deprecated The ability for apps to trigger scan requests will be removed in a future
+ * release.
+ */
+ @Deprecated
+ public boolean isScanAlwaysAvailable() {
+ try {
+ return mService.isScanAlwaysAvailable();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Tell the device to persist the current list of configured networks.
+ * <p>
+ * Note: It is possible for this method to change the network IDs of
+ * existing networks. You should assume the network IDs can be different
+ * after calling this method.
+ *
+ * @return {@code false}.
+ * @deprecated There is no need to call this method -
+ * {@link #addNetwork(WifiConfiguration)}, {@link #updateNetwork(WifiConfiguration)}
+ * and {@link #removeNetwork(int)} already persist the configurations automatically.
+ */
+ @Deprecated
+ public boolean saveConfiguration() {
+ return false;
+ }
+
+ /**
+ * Set the country code.
+ * @param countryCode country code in ISO 3166 format.
+ *
+ * @hide
+ */
+ public void setCountryCode(@NonNull String country) {
+ try {
+ mService.setCountryCode(country);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * get the country code.
+ * @return the country code in ISO 3166 format.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public String getCountryCode() {
+ try {
+ String country = mService.getCountryCode();
+ return country;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Check if the chipset supports dual frequency band (2.4 GHz and 5 GHz)
+ * @return {@code true} if supported, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public boolean isDualBandSupported() {
+ try {
+ return mService.isDualBandSupported();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Check if the chipset requires conversion of 5GHz Only apBand to ANY.
+ * @return {@code true} if required, {@code false} otherwise.
+ * @hide
+ */
+ public boolean isDualModeSupported() {
+ try {
+ return mService.needs5GHzToAnyApBandConversion();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return the DHCP-assigned addresses from the last successful DHCP request,
+ * if any.
+ * @return the DHCP information
+ */
+ public DhcpInfo getDhcpInfo() {
+ try {
+ return mService.getDhcpInfo();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Enable or disable Wi-Fi.
+ * <p>
+ * Applications must have the {@link android.Manifest.permission#CHANGE_WIFI_STATE}
+ * permission to toggle wifi.
+ *
+ * @param enabled {@code true} to enable, {@code false} to disable.
+ * @return {@code false} if the request cannot be satisfied; {@code true} indicates that wifi is
+ * either already in the requested state, or in progress toward the requested state.
+ * @throws {@link java.lang.SecurityException} if the caller is missing required permissions.
+ *
+ * @deprecated Starting with Build.VERSION_CODES#Q, applications are not allowed to
+ * enable/disable Wi-Fi.
+ * <b>Compatibility Note:</b> For applications targeting
+ * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always return {@code false}
+ * and will have no effect. If apps are targeting an older SDK (
+ * {@link android.os.Build.VERSION_CODES#P} or below), they can continue to use this API.
+ */
+ @Deprecated
+ public boolean setWifiEnabled(boolean enabled) {
+ try {
+ return mService.setWifiEnabled(mContext.getOpPackageName(), enabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets the Wi-Fi enabled state.
+ * @return One of {@link #WIFI_STATE_DISABLED},
+ * {@link #WIFI_STATE_DISABLING}, {@link #WIFI_STATE_ENABLED},
+ * {@link #WIFI_STATE_ENABLING}, {@link #WIFI_STATE_UNKNOWN}
+ * @see #isWifiEnabled()
+ */
+ public int getWifiState() {
+ try {
+ return mService.getWifiEnabledState();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return whether Wi-Fi is enabled or disabled.
+ * @return {@code true} if Wi-Fi is enabled
+ * @see #getWifiState()
+ */
+ public boolean isWifiEnabled() {
+ return getWifiState() == WIFI_STATE_ENABLED;
+ }
+
+ /**
+ * Return TX packet counter, for CTS test of WiFi watchdog.
+ * @param listener is the interface to receive result
+ *
+ * @hide for CTS test only
+ */
+ public void getTxPacketCount(TxPacketCountListener listener) {
+ getChannel().sendMessage(RSSI_PKTCNT_FETCH, 0, putListener(listener));
+ }
+
+ /**
+ * Calculates the level of the signal. This should be used any time a signal
+ * is being shown.
+ *
+ * @param rssi The power of the signal measured in RSSI.
+ * @param numLevels The number of levels to consider in the calculated
+ * level.
+ * @return A level of the signal, given in the range of 0 to numLevels-1
+ * (both inclusive).
+ */
+ public static int calculateSignalLevel(int rssi, int numLevels) {
+ if (rssi <= MIN_RSSI) {
+ return 0;
+ } else if (rssi >= MAX_RSSI) {
+ return numLevels - 1;
+ } else {
+ float inputRange = (MAX_RSSI - MIN_RSSI);
+ float outputRange = (numLevels - 1);
+ return (int)((float)(rssi - MIN_RSSI) * outputRange / inputRange);
+ }
+ }
+
+ /**
+ * Compares two signal strengths.
+ *
+ * @param rssiA The power of the first signal measured in RSSI.
+ * @param rssiB The power of the second signal measured in RSSI.
+ * @return Returns <0 if the first signal is weaker than the second signal,
+ * 0 if the two signals have the same strength, and >0 if the first
+ * signal is stronger than the second signal.
+ */
+ public static int compareSignalLevel(int rssiA, int rssiB) {
+ return rssiA - rssiB;
+ }
+
+ /**
+ * Call allowing ConnectivityService to update WifiService with interface mode changes.
+ *
+ * The possible modes include: {@link IFACE_IP_MODE_TETHERED},
+ * {@link IFACE_IP_MODE_LOCAL_ONLY},
+ * {@link IFACE_IP_MODE_CONFIGURATION_ERROR}
+ *
+ * @param ifaceName String name of the updated interface
+ * @param mode int representing the new mode
+ *
+ * @hide
+ */
+ public void updateInterfaceIpState(String ifaceName, int mode) {
+ try {
+ mService.updateInterfaceIpState(ifaceName, mode);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Start SoftAp mode with the specified configuration.
+ * Note that starting in access point mode disables station
+ * mode operation
+ * @param wifiConfig SSID, security and channel details as
+ * part of WifiConfiguration
+ * @return {@code true} if the operation succeeds, {@code false} otherwise
+ *
+ * @hide
+ */
+ public boolean startSoftAp(@Nullable WifiConfiguration wifiConfig) {
+ try {
+ return mService.startSoftAp(wifiConfig);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Stop SoftAp mode.
+ * Note that stopping softap mode will restore the previous wifi mode.
+ * @return {@code true} if the operation succeeds, {@code false} otherwise
+ *
+ * @hide
+ */
+ public boolean stopSoftAp() {
+ try {
+ return mService.stopSoftAp();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Request a local only hotspot that an application can use to communicate between co-located
+ * devices connected to the created WiFi hotspot. The network created by this method will not
+ * have Internet access. Each application can make a single request for the hotspot, but
+ * multiple applications could be requesting the hotspot at the same time. When multiple
+ * applications have successfully registered concurrently, they will be sharing the underlying
+ * hotspot. {@link LocalOnlyHotspotCallback#onStarted(LocalOnlyHotspotReservation)} is called
+ * when the hotspot is ready for use by the application.
+ * <p>
+ * Each application can make a single active call to this method. The {@link
+ * LocalOnlyHotspotCallback#onStarted(LocalOnlyHotspotReservation)} callback supplies the
+ * requestor with a {@link LocalOnlyHotspotReservation} that contains a
+ * {@link WifiConfiguration} with the SSID, security type and credentials needed to connect
+ * to the hotspot. Communicating this information is up to the application.
+ * <p>
+ * If the LocalOnlyHotspot cannot be created, the {@link LocalOnlyHotspotCallback#onFailed(int)}
+ * method will be called. Example failures include errors bringing up the network or if
+ * there is an incompatible operating mode. For example, if the user is currently using Wifi
+ * Tethering to provide an upstream to another device, LocalOnlyHotspot will not start due to
+ * an incompatible mode. The possible error codes include:
+ * {@link LocalOnlyHotspotCallback#ERROR_NO_CHANNEL},
+ * {@link LocalOnlyHotspotCallback#ERROR_GENERIC},
+ * {@link LocalOnlyHotspotCallback#ERROR_INCOMPATIBLE_MODE} and
+ * {@link LocalOnlyHotspotCallback#ERROR_TETHERING_DISALLOWED}.
+ * <p>
+ * Internally, requests will be tracked to prevent the hotspot from being torn down while apps
+ * are still using it. The {@link LocalOnlyHotspotReservation} object passed in the {@link
+ * LocalOnlyHotspotCallback#onStarted(LocalOnlyHotspotReservation)} call should be closed when
+ * the LocalOnlyHotspot is no longer needed using {@link LocalOnlyHotspotReservation#close()}.
+ * Since the hotspot may be shared among multiple applications, removing the final registered
+ * application request will trigger the hotspot teardown. This means that applications should
+ * not listen to broadcasts containing wifi state to determine if the hotspot was stopped after
+ * they are done using it. Additionally, once {@link LocalOnlyHotspotReservation#close()} is
+ * called, applications will not receive callbacks of any kind.
+ * <p>
+ * Applications should be aware that the user may also stop the LocalOnlyHotspot through the
+ * Settings UI; it is not guaranteed to stay up as long as there is a requesting application.
+ * The requestors will be notified of this case via
+ * {@link LocalOnlyHotspotCallback#onStopped()}. Other cases may arise where the hotspot is
+ * torn down (Emergency mode, etc). Application developers should be aware that it can stop
+ * unexpectedly, but they will receive a notification if they have properly registered.
+ * <p>
+ * Applications should also be aware that this network will be shared with other applications.
+ * Applications are responsible for protecting their data on this network (e.g., TLS).
+ * <p>
+ * Applications need to have the following permissions to start LocalOnlyHotspot: {@link
+ * android.Manifest.permission#CHANGE_WIFI_STATE} and {@link
+ * android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION}. Callers without
+ * the permissions will trigger a {@link java.lang.SecurityException}.
+ * <p>
+ * @param callback LocalOnlyHotspotCallback for the application to receive updates about
+ * operating status.
+ * @param handler Handler to be used for callbacks. If the caller passes a null Handler, the
+ * main thread will be used.
+ */
+ public void startLocalOnlyHotspot(LocalOnlyHotspotCallback callback,
+ @Nullable Handler handler) {
+ synchronized (mLock) {
+ Looper looper = (handler == null) ? mContext.getMainLooper() : handler.getLooper();
+ LocalOnlyHotspotCallbackProxy proxy =
+ new LocalOnlyHotspotCallbackProxy(this, looper, callback);
+ try {
+ String packageName = mContext.getOpPackageName();
+ int returnCode = mService.startLocalOnlyHotspot(
+ proxy.getMessenger(), new Binder(), packageName);
+ if (returnCode != LocalOnlyHotspotCallback.REQUEST_REGISTERED) {
+ // Send message to the proxy to make sure we call back on the correct thread
+ proxy.notifyFailed(returnCode);
+ return;
+ }
+ mLOHSCallbackProxy = proxy;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Cancels a pending local only hotspot request. This can be used by the calling application to
+ * cancel the existing request if the provided callback has not been triggered. Calling this
+ * method will be equivalent to closing the returned LocalOnlyHotspotReservation, but it is not
+ * explicitly required.
+ * <p>
+ * When cancelling this request, application developers should be aware that there may still be
+ * outstanding local only hotspot requests and the hotspot may still start, or continue running.
+ * Additionally, if a callback was registered, it will no longer be triggered after calling
+ * cancel.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void cancelLocalOnlyHotspotRequest() {
+ synchronized (mLock) {
+ stopLocalOnlyHotspot();
+ }
+ }
+
+ /**
+ * Method used to inform WifiService that the LocalOnlyHotspot is no longer needed. This
+ * method is used by WifiManager to release LocalOnlyHotspotReservations held by calling
+ * applications and removes the internal tracking for the hotspot request. When all requesting
+ * applications are finished using the hotspot, it will be stopped and WiFi will return to the
+ * previous operational mode.
+ *
+ * This method should not be called by applications. Instead, they should call the close()
+ * method on their LocalOnlyHotspotReservation.
+ */
+ private void stopLocalOnlyHotspot() {
+ synchronized (mLock) {
+ if (mLOHSCallbackProxy == null) {
+ // nothing to do, the callback was already cleaned up.
+ return;
+ }
+ mLOHSCallbackProxy = null;
+ try {
+ mService.stopLocalOnlyHotspot();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Allow callers (Settings UI) to watch LocalOnlyHotspot state changes. Callers will
+ * receive a {@link LocalOnlyHotspotSubscription} object as a parameter of the
+ * {@link LocalOnlyHotspotObserver#onRegistered(LocalOnlyHotspotSubscription)}. The registered
+ * callers will receive the {@link LocalOnlyHotspotObserver#onStarted(WifiConfiguration)} and
+ * {@link LocalOnlyHotspotObserver#onStopped()} callbacks.
+ * <p>
+ * Applications should have the
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION}
+ * permission. Callers without the permission will trigger a
+ * {@link java.lang.SecurityException}.
+ * <p>
+ * @param observer LocalOnlyHotspotObserver callback.
+ * @param handler Handler to use for callbacks
+ *
+ * @hide
+ */
+ public void watchLocalOnlyHotspot(LocalOnlyHotspotObserver observer,
+ @Nullable Handler handler) {
+ synchronized (mLock) {
+ Looper looper = (handler == null) ? mContext.getMainLooper() : handler.getLooper();
+ mLOHSObserverProxy = new LocalOnlyHotspotObserverProxy(this, looper, observer);
+ try {
+ mService.startWatchLocalOnlyHotspot(
+ mLOHSObserverProxy.getMessenger(), new Binder());
+ mLOHSObserverProxy.registered();
+ } catch (RemoteException e) {
+ mLOHSObserverProxy = null;
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Allow callers to stop watching LocalOnlyHotspot state changes. After calling this method,
+ * applications will no longer receive callbacks.
+ *
+ * @hide
+ */
+ public void unregisterLocalOnlyHotspotObserver() {
+ synchronized (mLock) {
+ if (mLOHSObserverProxy == null) {
+ // nothing to do, the callback was already cleaned up
+ return;
+ }
+ mLOHSObserverProxy = null;
+ try {
+ mService.stopWatchLocalOnlyHotspot();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Gets the Wi-Fi enabled state.
+ * @return One of {@link #WIFI_AP_STATE_DISABLED},
+ * {@link #WIFI_AP_STATE_DISABLING}, {@link #WIFI_AP_STATE_ENABLED},
+ * {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED}
+ * @see #isWifiApEnabled()
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE)
+ public int getWifiApState() {
+ try {
+ return mService.getWifiApEnabledState();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return whether Wi-Fi AP is enabled or disabled.
+ * @return {@code true} if Wi-Fi AP is enabled
+ * @see #getWifiApState()
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE)
+ public boolean isWifiApEnabled() {
+ return getWifiApState() == WIFI_AP_STATE_ENABLED;
+ }
+
+ /**
+ * Gets the Wi-Fi AP Configuration.
+ * @return AP details in WifiConfiguration
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE)
+ public WifiConfiguration getWifiApConfiguration() {
+ try {
+ return mService.getWifiApConfiguration();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets the Wi-Fi AP Configuration. The AP configuration must either be open or
+ * WPA2 PSK networks.
+ * @return {@code true} if the operation succeeded, {@code false} otherwise
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE)
+ public boolean setWifiApConfiguration(WifiConfiguration wifiConfig) {
+ try {
+ return mService.setWifiApConfiguration(wifiConfig, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Method that triggers a notification to the user about a conversion to their saved AP config.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public void notifyUserOfApBandConversion() {
+ Log.d(TAG, "apBand was converted, notify the user");
+ try {
+ mService.notifyUserOfApBandConversion(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Enable/Disable TDLS on a specific local route.
+ *
+ * <p>
+ * TDLS enables two wireless endpoints to talk to each other directly
+ * without going through the access point that is managing the local
+ * network. It saves bandwidth and improves quality of the link.
+ * </p>
+ * <p>
+ * This API enables/disables the option of using TDLS. If enabled, the
+ * underlying hardware is free to use TDLS or a hop through the access
+ * point. If disabled, existing TDLS session is torn down and
+ * hardware is restricted to use access point for transferring wireless
+ * packets. Default value for all routes is 'disabled', meaning restricted
+ * to use access point for transferring packets.
+ * </p>
+ *
+ * @param remoteIPAddress IP address of the endpoint to setup TDLS with
+ * @param enable true = setup and false = tear down TDLS
+ */
+ public void setTdlsEnabled(InetAddress remoteIPAddress, boolean enable) {
+ try {
+ mService.enableTdls(remoteIPAddress.getHostAddress(), enable);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Similar to {@link #setTdlsEnabled(InetAddress, boolean) }, except
+ * this version allows you to specify remote endpoint with a MAC address.
+ * @param remoteMacAddress MAC address of the remote endpoint such as 00:00:0c:9f:f2:ab
+ * @param enable true = setup and false = tear down TDLS
+ */
+ public void setTdlsEnabledWithMacAddress(String remoteMacAddress, boolean enable) {
+ try {
+ mService.enableTdlsWithMacAddress(remoteMacAddress, enable);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /* TODO: deprecate synchronous API and open up the following API */
+
+ private static final int BASE = Protocol.BASE_WIFI_MANAGER;
+
+ /* Commands to WifiService */
+ /** @hide */
+ public static final int CONNECT_NETWORK = BASE + 1;
+ /** @hide */
+ public static final int CONNECT_NETWORK_FAILED = BASE + 2;
+ /** @hide */
+ public static final int CONNECT_NETWORK_SUCCEEDED = BASE + 3;
+
+ /** @hide */
+ public static final int FORGET_NETWORK = BASE + 4;
+ /** @hide */
+ public static final int FORGET_NETWORK_FAILED = BASE + 5;
+ /** @hide */
+ public static final int FORGET_NETWORK_SUCCEEDED = BASE + 6;
+
+ /** @hide */
+ public static final int SAVE_NETWORK = BASE + 7;
+ /** @hide */
+ public static final int SAVE_NETWORK_FAILED = BASE + 8;
+ /** @hide */
+ public static final int SAVE_NETWORK_SUCCEEDED = BASE + 9;
+
+ /** @hide
+ * @deprecated This is deprecated
+ */
+ public static final int START_WPS = BASE + 10;
+ /** @hide
+ * @deprecated This is deprecated
+ */
+ public static final int START_WPS_SUCCEEDED = BASE + 11;
+ /** @hide
+ * @deprecated This is deprecated
+ */
+ public static final int WPS_FAILED = BASE + 12;
+ /** @hide
+ * @deprecated This is deprecated
+ */
+ public static final int WPS_COMPLETED = BASE + 13;
+
+ /** @hide
+ * @deprecated This is deprecated
+ */
+ public static final int CANCEL_WPS = BASE + 14;
+ /** @hide
+ * @deprecated This is deprecated
+ */
+ public static final int CANCEL_WPS_FAILED = BASE + 15;
+ /** @hide
+ * @deprecated This is deprecated
+ */
+ public static final int CANCEL_WPS_SUCCEDED = BASE + 16;
+
+ /** @hide */
+ public static final int DISABLE_NETWORK = BASE + 17;
+ /** @hide */
+ public static final int DISABLE_NETWORK_FAILED = BASE + 18;
+ /** @hide */
+ public static final int DISABLE_NETWORK_SUCCEEDED = BASE + 19;
+
+ /** @hide */
+ public static final int RSSI_PKTCNT_FETCH = BASE + 20;
+ /** @hide */
+ public static final int RSSI_PKTCNT_FETCH_SUCCEEDED = BASE + 21;
+ /** @hide */
+ public static final int RSSI_PKTCNT_FETCH_FAILED = BASE + 22;
+
+ /**
+ * Passed with {@link ActionListener#onFailure}.
+ * Indicates that the operation failed due to an internal error.
+ * @hide
+ */
+ public static final int ERROR = 0;
+
+ /**
+ * Passed with {@link ActionListener#onFailure}.
+ * Indicates that the operation is already in progress
+ * @hide
+ */
+ public static final int IN_PROGRESS = 1;
+
+ /**
+ * Passed with {@link ActionListener#onFailure}.
+ * Indicates that the operation failed because the framework is busy and
+ * unable to service the request
+ * @hide
+ */
+ public static final int BUSY = 2;
+
+ /* WPS specific errors */
+ /** WPS overlap detected
+ * @deprecated This is deprecated
+ */
+ public static final int WPS_OVERLAP_ERROR = 3;
+ /** WEP on WPS is prohibited
+ * @deprecated This is deprecated
+ */
+ public static final int WPS_WEP_PROHIBITED = 4;
+ /** TKIP only prohibited
+ * @deprecated This is deprecated
+ */
+ public static final int WPS_TKIP_ONLY_PROHIBITED = 5;
+ /** Authentication failure on WPS
+ * @deprecated This is deprecated
+ */
+ public static final int WPS_AUTH_FAILURE = 6;
+ /** WPS timed out
+ * @deprecated This is deprecated
+ */
+ public static final int WPS_TIMED_OUT = 7;
+
+ /**
+ * Passed with {@link ActionListener#onFailure}.
+ * Indicates that the operation failed due to invalid inputs
+ * @hide
+ */
+ public static final int INVALID_ARGS = 8;
+
+ /**
+ * Passed with {@link ActionListener#onFailure}.
+ * Indicates that the operation failed due to user permissions.
+ * @hide
+ */
+ public static final int NOT_AUTHORIZED = 9;
+
+ /**
+ * Interface for callback invocation on an application action
+ * @hide
+ */
+ @SystemApi
+ public interface ActionListener {
+ /**
+ * The operation succeeded.
+ * This is called when the scan request has been validated and ready
+ * to sent to driver.
+ */
+ public void onSuccess();
+ /**
+ * The operation failed.
+ * This is called when the scan request failed.
+ * @param reason The reason for failure could be one of the following:
+ * {@link #REASON_INVALID_REQUEST}} is specified when scan request parameters are invalid.
+ * {@link #REASON_NOT_AUTHORIZED} is specified when requesting app doesn't have the required
+ * permission to request a scan.
+ * {@link #REASON_UNSPECIFIED} is specified when driver reports a scan failure.
+ */
+ public void onFailure(int reason);
+ }
+
+ /** Interface for callback invocation on a start WPS action
+ * @deprecated This is deprecated
+ */
+ public static abstract class WpsCallback {
+
+ /** WPS start succeeded
+ * @deprecated This API is deprecated
+ */
+ public abstract void onStarted(String pin);
+
+ /** WPS operation completed successfully
+ * @deprecated This API is deprecated
+ */
+ public abstract void onSucceeded();
+
+ /**
+ * WPS operation failed
+ * @param reason The reason for failure could be one of
+ * {@link #WPS_TKIP_ONLY_PROHIBITED}, {@link #WPS_OVERLAP_ERROR},
+ * {@link #WPS_WEP_PROHIBITED}, {@link #WPS_TIMED_OUT} or {@link #WPS_AUTH_FAILURE}
+ * and some generic errors.
+ * @deprecated This API is deprecated
+ */
+ public abstract void onFailed(int reason);
+ }
+
+ /** Interface for callback invocation on a TX packet count poll action {@hide} */
+ public interface TxPacketCountListener {
+ /**
+ * The operation succeeded
+ * @param count TX packet counter
+ */
+ public void onSuccess(int count);
+ /**
+ * The operation failed
+ * @param reason The reason for failure could be one of
+ * {@link #ERROR}, {@link #IN_PROGRESS} or {@link #BUSY}
+ */
+ public void onFailure(int reason);
+ }
+
+ /**
+ * Base class for soft AP callback. Should be extended by applications and set when calling
+ * {@link WifiManager#registerSoftApCallback(SoftApCallback, Handler)}.
+ *
+ * @hide
+ */
+ public interface SoftApCallback {
+ /**
+ * Called when soft AP state changes.
+ *
+ * @param state new new AP state. One of {@link #WIFI_AP_STATE_DISABLED},
+ * {@link #WIFI_AP_STATE_DISABLING}, {@link #WIFI_AP_STATE_ENABLED},
+ * {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED}
+ * @param failureReason reason when in failed state. One of
+ * {@link #SAP_START_FAILURE_GENERAL}, {@link #SAP_START_FAILURE_NO_CHANNEL}
+ */
+ public abstract void onStateChanged(@WifiApState int state,
+ @SapStartFailure int failureReason);
+
+ /**
+ * Called when number of connected clients to soft AP changes.
+ *
+ * @param numClients number of connected clients
+ */
+ public abstract void onNumClientsChanged(int numClients);
+ }
+
+ /**
+ * Callback proxy for SoftApCallback objects.
+ *
+ * @hide
+ */
+ private class SoftApCallbackProxy extends ISoftApCallback.Stub {
+ private final Handler mHandler;
+ private final SoftApCallback mCallback;
+
+ SoftApCallbackProxy(Looper looper, SoftApCallback callback) {
+ mHandler = new Handler(looper);
+ mCallback = callback;
+ }
+
+ @Override
+ public void onStateChanged(int state, int failureReason) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "SoftApCallbackProxy: onStateChanged: state=" + state
+ + ", failureReason=" + failureReason);
+ }
+ mHandler.post(() -> {
+ mCallback.onStateChanged(state, failureReason);
+ });
+ }
+
+ @Override
+ public void onNumClientsChanged(int numClients) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "SoftApCallbackProxy: onNumClientsChanged: numClients=" + numClients);
+ }
+ mHandler.post(() -> {
+ mCallback.onNumClientsChanged(numClients);
+ });
+ }
+ }
+
+ /**
+ * Registers a callback for Soft AP. See {@link SoftApCallback}. Caller will receive the current
+ * soft AP state and number of connected devices immediately after a successful call to this API
+ * via callback. Note that receiving an immediate WIFI_AP_STATE_FAILED value for soft AP state
+ * indicates that the latest attempt to start soft AP has failed. Caller can unregister a
+ * previously registered callback using {@link unregisterSoftApCallback}
+ * <p>
+ * Applications should have the
+ * {@link android.Manifest.permission#NETWORK_SETTINGS NETWORK_SETTINGS} permission. Callers
+ * without the permission will trigger a {@link java.lang.SecurityException}.
+ * <p>
+ *
+ * @param callback Callback for soft AP events
+ * @param handler The Handler on whose thread to execute the callbacks of the {@code callback}
+ * object. If null, then the application's main thread will be used.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public void registerSoftApCallback(@NonNull SoftApCallback callback,
+ @Nullable Handler handler) {
+ if (callback == null) throw new IllegalArgumentException("callback cannot be null");
+ Log.v(TAG, "registerSoftApCallback: callback=" + callback + ", handler=" + handler);
+
+ Looper looper = (handler == null) ? mContext.getMainLooper() : handler.getLooper();
+ Binder binder = new Binder();
+ try {
+ mService.registerSoftApCallback(binder, new SoftApCallbackProxy(looper, callback),
+ callback.hashCode());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Allow callers to unregister a previously registered callback. After calling this method,
+ * applications will no longer receive soft AP events.
+ *
+ * @param callback Callback to unregister for soft AP events
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public void unregisterSoftApCallback(@NonNull SoftApCallback callback) {
+ if (callback == null) throw new IllegalArgumentException("callback cannot be null");
+ Log.v(TAG, "unregisterSoftApCallback: callback=" + callback);
+
+ try {
+ mService.unregisterSoftApCallback(callback.hashCode());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * LocalOnlyHotspotReservation that contains the {@link WifiConfiguration} for the active
+ * LocalOnlyHotspot request.
+ * <p>
+ * Applications requesting LocalOnlyHotspot for sharing will receive an instance of the
+ * LocalOnlyHotspotReservation in the
+ * {@link LocalOnlyHotspotCallback#onStarted(LocalOnlyHotspotReservation)} call. This
+ * reservation contains the relevant {@link WifiConfiguration}.
+ * When an application is done with the LocalOnlyHotspot, they should call {@link
+ * LocalOnlyHotspotReservation#close()}. Once this happens, the application will not receive
+ * any further callbacks. If the LocalOnlyHotspot is stopped due to a
+ * user triggered mode change, applications will be notified via the {@link
+ * LocalOnlyHotspotCallback#onStopped()} callback.
+ */
+ public class LocalOnlyHotspotReservation implements AutoCloseable {
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+ private final WifiConfiguration mConfig;
+
+ /** @hide */
+ @VisibleForTesting
+ public LocalOnlyHotspotReservation(WifiConfiguration config) {
+ mConfig = config;
+ mCloseGuard.open("close");
+ }
+
+ public WifiConfiguration getWifiConfiguration() {
+ return mConfig;
+ }
+
+ @Override
+ public void close() {
+ try {
+ stopLocalOnlyHotspot();
+ mCloseGuard.close();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to stop Local Only Hotspot.");
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+ }
+
+ /**
+ * Callback class for applications to receive updates about the LocalOnlyHotspot status.
+ */
+ public static class LocalOnlyHotspotCallback {
+ /** @hide */
+ public static final int REQUEST_REGISTERED = 0;
+
+ public static final int ERROR_NO_CHANNEL = 1;
+ public static final int ERROR_GENERIC = 2;
+ public static final int ERROR_INCOMPATIBLE_MODE = 3;
+ public static final int ERROR_TETHERING_DISALLOWED = 4;
+
+ /** LocalOnlyHotspot start succeeded. */
+ public void onStarted(LocalOnlyHotspotReservation reservation) {};
+
+ /**
+ * LocalOnlyHotspot stopped.
+ * <p>
+ * The LocalOnlyHotspot can be disabled at any time by the user. When this happens,
+ * applications will be notified that it was stopped. This will not be invoked when an
+ * application calls {@link LocalOnlyHotspotReservation#close()}.
+ */
+ public void onStopped() {};
+
+ /**
+ * LocalOnlyHotspot failed to start.
+ * <p>
+ * Applications can attempt to call
+ * {@link WifiManager#startLocalOnlyHotspot(LocalOnlyHotspotCallback, Handler)} again at
+ * a later time.
+ * <p>
+ * @param reason The reason for failure could be one of: {@link
+ * #ERROR_TETHERING_DISALLOWED}, {@link #ERROR_INCOMPATIBLE_MODE},
+ * {@link #ERROR_NO_CHANNEL}, or {@link #ERROR_GENERIC}.
+ */
+ public void onFailed(int reason) { };
+ }
+
+ /**
+ * Callback proxy for LocalOnlyHotspotCallback objects.
+ */
+ private static class LocalOnlyHotspotCallbackProxy {
+ private final Handler mHandler;
+ private final WeakReference<WifiManager> mWifiManager;
+ private final Looper mLooper;
+ private final Messenger mMessenger;
+
+ /**
+ * Constructs a {@link LocalOnlyHotspotCallback} using the specified looper. All callbacks
+ * will be delivered on the thread of the specified looper.
+ *
+ * @param manager WifiManager
+ * @param looper Looper for delivering callbacks
+ * @param callback LocalOnlyHotspotCallback to notify the calling application.
+ */
+ LocalOnlyHotspotCallbackProxy(WifiManager manager, Looper looper,
+ final LocalOnlyHotspotCallback callback) {
+ mWifiManager = new WeakReference<>(manager);
+ mLooper = looper;
+
+ mHandler = new Handler(looper) {
+ @Override
+ public void handleMessage(Message msg) {
+ Log.d(TAG, "LocalOnlyHotspotCallbackProxy: handle message what: "
+ + msg.what + " msg: " + msg);
+
+ WifiManager manager = mWifiManager.get();
+ if (manager == null) {
+ Log.w(TAG, "LocalOnlyHotspotCallbackProxy: handle message post GC");
+ return;
+ }
+
+ switch (msg.what) {
+ case HOTSPOT_STARTED:
+ WifiConfiguration config = (WifiConfiguration) msg.obj;
+ if (config == null) {
+ Log.e(TAG, "LocalOnlyHotspotCallbackProxy: config cannot be null.");
+ callback.onFailed(LocalOnlyHotspotCallback.ERROR_GENERIC);
+ return;
+ }
+ callback.onStarted(manager.new LocalOnlyHotspotReservation(config));
+ break;
+ case HOTSPOT_STOPPED:
+ Log.w(TAG, "LocalOnlyHotspotCallbackProxy: hotspot stopped");
+ callback.onStopped();
+ break;
+ case HOTSPOT_FAILED:
+ int reasonCode = msg.arg1;
+ Log.w(TAG, "LocalOnlyHotspotCallbackProxy: failed to start. reason: "
+ + reasonCode);
+ callback.onFailed(reasonCode);
+ Log.w(TAG, "done with the callback...");
+ break;
+ default:
+ Log.e(TAG, "LocalOnlyHotspotCallbackProxy unhandled message. type: "
+ + msg.what);
+ }
+ }
+ };
+ mMessenger = new Messenger(mHandler);
+ }
+
+ public Messenger getMessenger() {
+ return mMessenger;
+ }
+
+ /**
+ * Helper method allowing the the incoming application call to move the onFailed callback
+ * over to the desired callback thread.
+ *
+ * @param reason int representing the error type
+ */
+ public void notifyFailed(int reason) throws RemoteException {
+ Message msg = Message.obtain();
+ msg.what = HOTSPOT_FAILED;
+ msg.arg1 = reason;
+ mMessenger.send(msg);
+ }
+ }
+
+ /**
+ * LocalOnlyHotspotSubscription that is an AutoCloseable object for tracking applications
+ * watching for LocalOnlyHotspot changes.
+ *
+ * @hide
+ */
+ public class LocalOnlyHotspotSubscription implements AutoCloseable {
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ /** @hide */
+ @VisibleForTesting
+ public LocalOnlyHotspotSubscription() {
+ mCloseGuard.open("close");
+ }
+
+ @Override
+ public void close() {
+ try {
+ unregisterLocalOnlyHotspotObserver();
+ mCloseGuard.close();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to unregister LocalOnlyHotspotObserver.");
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+ }
+
+ /**
+ * Class to notify calling applications that watch for changes in LocalOnlyHotspot of updates.
+ *
+ * @hide
+ */
+ public static class LocalOnlyHotspotObserver {
+ /**
+ * Confirm registration for LocalOnlyHotspotChanges by returning a
+ * LocalOnlyHotspotSubscription.
+ */
+ public void onRegistered(LocalOnlyHotspotSubscription subscription) {};
+
+ /**
+ * LocalOnlyHotspot started with the supplied config.
+ */
+ public void onStarted(WifiConfiguration config) {};
+
+ /**
+ * LocalOnlyHotspot stopped.
+ */
+ public void onStopped() {};
+ }
+
+ /**
+ * Callback proxy for LocalOnlyHotspotObserver objects.
+ */
+ private static class LocalOnlyHotspotObserverProxy {
+ private final Handler mHandler;
+ private final WeakReference<WifiManager> mWifiManager;
+ private final Looper mLooper;
+ private final Messenger mMessenger;
+
+ /**
+ * Constructs a {@link LocalOnlyHotspotObserverProxy} using the specified looper.
+ * All callbacks will be delivered on the thread of the specified looper.
+ *
+ * @param manager WifiManager
+ * @param looper Looper for delivering callbacks
+ * @param observer LocalOnlyHotspotObserver to notify the calling application.
+ */
+ LocalOnlyHotspotObserverProxy(WifiManager manager, Looper looper,
+ final LocalOnlyHotspotObserver observer) {
+ mWifiManager = new WeakReference<>(manager);
+ mLooper = looper;
+
+ mHandler = new Handler(looper) {
+ @Override
+ public void handleMessage(Message msg) {
+ Log.d(TAG, "LocalOnlyHotspotObserverProxy: handle message what: "
+ + msg.what + " msg: " + msg);
+
+ WifiManager manager = mWifiManager.get();
+ if (manager == null) {
+ Log.w(TAG, "LocalOnlyHotspotObserverProxy: handle message post GC");
+ return;
+ }
+
+ switch (msg.what) {
+ case HOTSPOT_OBSERVER_REGISTERED:
+ observer.onRegistered(manager.new LocalOnlyHotspotSubscription());
+ break;
+ case HOTSPOT_STARTED:
+ WifiConfiguration config = (WifiConfiguration) msg.obj;
+ if (config == null) {
+ Log.e(TAG, "LocalOnlyHotspotObserverProxy: config cannot be null.");
+ return;
+ }
+ observer.onStarted(config);
+ break;
+ case HOTSPOT_STOPPED:
+ observer.onStopped();
+ break;
+ default:
+ Log.e(TAG, "LocalOnlyHotspotObserverProxy unhandled message. type: "
+ + msg.what);
+ }
+ }
+ };
+ mMessenger = new Messenger(mHandler);
+ }
+
+ public Messenger getMessenger() {
+ return mMessenger;
+ }
+
+ public void registered() throws RemoteException {
+ Message msg = Message.obtain();
+ msg.what = HOTSPOT_OBSERVER_REGISTERED;
+ mMessenger.send(msg);
+ }
+ }
+
+ // Ensure that multiple ServiceHandler threads do not interleave message dispatch.
+ private static final Object sServiceHandlerDispatchLock = new Object();
+
+ private class ServiceHandler extends Handler {
+ ServiceHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ synchronized (sServiceHandlerDispatchLock) {
+ dispatchMessageToListeners(message);
+ }
+ }
+
+ private void dispatchMessageToListeners(Message message) {
+ Object listener = removeListener(message.arg2);
+ switch (message.what) {
+ case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
+ if (message.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+ mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
+ } else {
+ Log.e(TAG, "Failed to set up channel connection");
+ // This will cause all further async API calls on the WifiManager
+ // to fail and throw an exception
+ mAsyncChannel = null;
+ }
+ mConnected.countDown();
+ break;
+ case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
+ // Ignore
+ break;
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+ Log.e(TAG, "Channel connection lost");
+ // This will cause all further async API calls on the WifiManager
+ // to fail and throw an exception
+ mAsyncChannel = null;
+ getLooper().quit();
+ break;
+ /* ActionListeners grouped together */
+ case WifiManager.CONNECT_NETWORK_FAILED:
+ case WifiManager.FORGET_NETWORK_FAILED:
+ case WifiManager.SAVE_NETWORK_FAILED:
+ case WifiManager.DISABLE_NETWORK_FAILED:
+ if (listener != null) {
+ ((ActionListener) listener).onFailure(message.arg1);
+ }
+ break;
+ /* ActionListeners grouped together */
+ case WifiManager.CONNECT_NETWORK_SUCCEEDED:
+ case WifiManager.FORGET_NETWORK_SUCCEEDED:
+ case WifiManager.SAVE_NETWORK_SUCCEEDED:
+ case WifiManager.DISABLE_NETWORK_SUCCEEDED:
+ if (listener != null) {
+ ((ActionListener) listener).onSuccess();
+ }
+ break;
+ case WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED:
+ if (listener != null) {
+ RssiPacketCountInfo info = (RssiPacketCountInfo) message.obj;
+ if (info != null)
+ ((TxPacketCountListener) listener).onSuccess(info.txgood + info.txbad);
+ else
+ ((TxPacketCountListener) listener).onFailure(ERROR);
+ }
+ break;
+ case WifiManager.RSSI_PKTCNT_FETCH_FAILED:
+ if (listener != null) {
+ ((TxPacketCountListener) listener).onFailure(message.arg1);
+ }
+ break;
+ default:
+ //ignore
+ break;
+ }
+ }
+ }
+
+ private int putListener(Object listener) {
+ if (listener == null) return INVALID_KEY;
+ int key;
+ synchronized (mListenerMapLock) {
+ do {
+ key = mListenerKey++;
+ } while (key == INVALID_KEY);
+ mListenerMap.put(key, listener);
+ }
+ return key;
+ }
+
+ private Object removeListener(int key) {
+ if (key == INVALID_KEY) return null;
+ synchronized (mListenerMapLock) {
+ Object listener = mListenerMap.get(key);
+ mListenerMap.remove(key);
+ return listener;
+ }
+ }
+
+ private synchronized AsyncChannel getChannel() {
+ if (mAsyncChannel == null) {
+ Messenger messenger = getWifiServiceMessenger();
+ if (messenger == null) {
+ throw new IllegalStateException(
+ "getWifiServiceMessenger() returned null! This is invalid.");
+ }
+
+ mAsyncChannel = new AsyncChannel();
+ mConnected = new CountDownLatch(1);
+
+ Handler handler = new ServiceHandler(mLooper);
+ mAsyncChannel.connect(mContext, handler, messenger);
+ try {
+ mConnected.await();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "interrupted wait at init");
+ }
+ }
+ return mAsyncChannel;
+ }
+
+ /**
+ * Connect to a network with the given configuration. The network also
+ * gets added to the list of configured networks for the foreground user.
+ *
+ * For a new network, this function is used instead of a
+ * sequence of addNetwork(), enableNetwork(), and reconnect()
+ *
+ * @param config the set of variables that describe the configuration,
+ * contained in a {@link WifiConfiguration} object.
+ * @param listener for callbacks on success or failure. Can be null.
+ * @throws IllegalStateException if the WifiManager instance needs to be
+ * initialized again
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD,
+ android.Manifest.permission.NETWORK_STACK
+ })
+ public void connect(@NonNull WifiConfiguration config, @Nullable ActionListener listener) {
+ if (config == null) throw new IllegalArgumentException("config cannot be null");
+ // Use INVALID_NETWORK_ID for arg1 when passing a config object
+ // arg1 is used to pass network id when the network already exists
+ getChannel().sendMessage(CONNECT_NETWORK, WifiConfiguration.INVALID_NETWORK_ID,
+ putListener(listener), config);
+ }
+
+ /**
+ * Connect to a network with the given networkId.
+ *
+ * This function is used instead of a enableNetwork() and reconnect()
+ *
+ * @param networkId the ID of the network as returned by {@link #addNetwork} or {@link
+ * getConfiguredNetworks}.
+ * @param listener for callbacks on success or failure. Can be null.
+ * @throws IllegalStateException if the WifiManager instance needs to be
+ * initialized again
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD,
+ android.Manifest.permission.NETWORK_STACK
+ })
+ public void connect(int networkId, @Nullable ActionListener listener) {
+ if (networkId < 0) throw new IllegalArgumentException("Network id cannot be negative");
+ getChannel().sendMessage(CONNECT_NETWORK, networkId, putListener(listener));
+ }
+
+ /**
+ * Save the given network to the list of configured networks for the
+ * foreground user. If the network already exists, the configuration
+ * is updated. Any new network is enabled by default.
+ *
+ * For a new network, this function is used instead of a
+ * sequence of addNetwork() and enableNetwork().
+ *
+ * For an existing network, it accomplishes the task of updateNetwork()
+ *
+ * This API will cause reconnect if the crecdentials of the current active
+ * connection has been changed.
+ *
+ * @param config the set of variables that describe the configuration,
+ * contained in a {@link WifiConfiguration} object.
+ * @param listener for callbacks on success or failure. Can be null.
+ * @throws IllegalStateException if the WifiManager instance needs to be
+ * initialized again
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD,
+ android.Manifest.permission.NETWORK_STACK
+ })
+ public void save(@NonNull WifiConfiguration config, @Nullable ActionListener listener) {
+ if (config == null) throw new IllegalArgumentException("config cannot be null");
+ getChannel().sendMessage(SAVE_NETWORK, 0, putListener(listener), config);
+ }
+
+ /**
+ * Delete the network from the list of configured networks for the
+ * foreground user.
+ *
+ * This function is used instead of a sequence of removeNetwork()
+ *
+ * @param config the set of variables that describe the configuration,
+ * contained in a {@link WifiConfiguration} object.
+ * @param listener for callbacks on success or failure. Can be null.
+ * @throws IllegalStateException if the WifiManager instance needs to be
+ * initialized again
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD,
+ android.Manifest.permission.NETWORK_STACK
+ })
+ public void forget(int netId, @Nullable ActionListener listener) {
+ if (netId < 0) throw new IllegalArgumentException("Network id cannot be negative");
+ getChannel().sendMessage(FORGET_NETWORK, netId, putListener(listener));
+ }
+
+ /**
+ * Disable network
+ *
+ * @param netId is the network Id
+ * @param listener for callbacks on success or failure. Can be null.
+ * @throws IllegalStateException if the WifiManager instance needs to be
+ * initialized again
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD,
+ android.Manifest.permission.NETWORK_STACK
+ })
+ public void disable(int netId, @Nullable ActionListener listener) {
+ if (netId < 0) throw new IllegalArgumentException("Network id cannot be negative");
+ getChannel().sendMessage(DISABLE_NETWORK, netId, putListener(listener));
+ }
+
+ /**
+ * Disable ephemeral Network
+ *
+ * @param SSID, in the format of WifiConfiguration's SSID.
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_STACK
+ })
+ public void disableEphemeralNetwork(String SSID) {
+ if (SSID == null) throw new IllegalArgumentException("SSID cannot be null");
+ try {
+ mService.disableEphemeralNetwork(SSID, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * WPS suport has been deprecated from Client mode and this method will immediately trigger
+ * {@link WpsCallback#onFailed(int)} with a generic error.
+ *
+ * @param config WPS configuration (does not support {@link WpsInfo#LABEL})
+ * @param listener for callbacks on success or failure. Can be null.
+ * @throws IllegalStateException if the WifiManager instance needs to be initialized again
+ * @deprecated This API is deprecated
+ */
+ public void startWps(WpsInfo config, WpsCallback listener) {
+ if (listener != null ) {
+ listener.onFailed(ERROR);
+ }
+ }
+
+ /**
+ * WPS support has been deprecated from Client mode and this method will immediately trigger
+ * {@link WpsCallback#onFailed(int)} with a generic error.
+ *
+ * @param listener for callbacks on success or failure. Can be null.
+ * @throws IllegalStateException if the WifiManager instance needs to be initialized again
+ * @deprecated This API is deprecated
+ */
+ public void cancelWps(WpsCallback listener) {
+ if (listener != null) {
+ listener.onFailed(ERROR);
+ }
+ }
+
+ /**
+ * Get a reference to WifiService handler. This is used by a client to establish
+ * an AsyncChannel communication with WifiService
+ *
+ * @return Messenger pointing to the WifiService handler
+ */
+ @UnsupportedAppUsage
+ private Messenger getWifiServiceMessenger() {
+ try {
+ return mService.getWifiServiceMessenger(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
+ * Allows an application to keep the Wi-Fi radio awake.
+ * Normally the Wi-Fi radio may turn off when the user has not used the device in a while.
+ * Acquiring a WifiLock will keep the radio on until the lock is released. Multiple
+ * applications may hold WifiLocks, and the radio will only be allowed to turn off when no
+ * WifiLocks are held in any application.
+ * <p>
+ * Before using a WifiLock, consider carefully if your application requires Wi-Fi access, or
+ * could function over a mobile network, if available. A program that needs to download large
+ * files should hold a WifiLock to ensure that the download will complete, but a program whose
+ * network usage is occasional or low-bandwidth should not hold a WifiLock to avoid adversely
+ * affecting battery life.
+ * <p>
+ * Note that WifiLocks cannot override the user-level "Wi-Fi Enabled" setting, nor Airplane
+ * Mode. They simply keep the radio from turning off when Wi-Fi is already on but the device
+ * is idle.
+ * <p>
+ * Any application using a WifiLock must request the {@code android.permission.WAKE_LOCK}
+ * permission in an {@code <uses-permission>} element of the application's manifest.
+ */
+ public class WifiLock {
+ private String mTag;
+ private final IBinder mBinder;
+ private int mRefCount;
+ int mLockType;
+ private boolean mRefCounted;
+ private boolean mHeld;
+ private WorkSource mWorkSource;
+
+ private WifiLock(int lockType, String tag) {
+ mTag = tag;
+ mLockType = lockType;
+ mBinder = new Binder();
+ mRefCount = 0;
+ mRefCounted = true;
+ mHeld = false;
+ }
+
+ /**
+ * Locks the Wi-Fi radio on until {@link #release} is called.
+ *
+ * If this WifiLock is reference-counted, each call to {@code acquire} will increment the
+ * reference count, and the radio will remain locked as long as the reference count is
+ * above zero.
+ *
+ * If this WifiLock is not reference-counted, the first call to {@code acquire} will lock
+ * the radio, but subsequent calls will be ignored. Only one call to {@link #release}
+ * will be required, regardless of the number of times that {@code acquire} is called.
+ */
+ public void acquire() {
+ synchronized (mBinder) {
+ if (mRefCounted ? (++mRefCount == 1) : (!mHeld)) {
+ try {
+ mService.acquireWifiLock(mBinder, mLockType, mTag, mWorkSource);
+ synchronized (WifiManager.this) {
+ if (mActiveLockCount >= MAX_ACTIVE_LOCKS) {
+ mService.releaseWifiLock(mBinder);
+ throw new UnsupportedOperationException(
+ "Exceeded maximum number of wifi locks");
+ }
+ mActiveLockCount++;
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mHeld = true;
+ }
+ }
+ }
+
+ /**
+ * Unlocks the Wi-Fi radio, allowing it to turn off when the device is idle.
+ *
+ * If this WifiLock is reference-counted, each call to {@code release} will decrement the
+ * reference count, and the radio will be unlocked only when the reference count reaches
+ * zero. If the reference count goes below zero (that is, if {@code release} is called
+ * a greater number of times than {@link #acquire}), an exception is thrown.
+ *
+ * If this WifiLock is not reference-counted, the first call to {@code release} (after
+ * the radio was locked using {@link #acquire}) will unlock the radio, and subsequent
+ * calls will be ignored.
+ */
+ public void release() {
+ synchronized (mBinder) {
+ if (mRefCounted ? (--mRefCount == 0) : (mHeld)) {
+ try {
+ mService.releaseWifiLock(mBinder);
+ synchronized (WifiManager.this) {
+ mActiveLockCount--;
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mHeld = false;
+ }
+ if (mRefCount < 0) {
+ throw new RuntimeException("WifiLock under-locked " + mTag);
+ }
+ }
+ }
+
+ /**
+ * Controls whether this is a reference-counted or non-reference-counted WifiLock.
+ *
+ * Reference-counted WifiLocks keep track of the number of calls to {@link #acquire} and
+ * {@link #release}, and only allow the radio to sleep when every call to {@link #acquire}
+ * has been balanced with a call to {@link #release}. Non-reference-counted WifiLocks
+ * lock the radio whenever {@link #acquire} is called and it is unlocked, and unlock the
+ * radio whenever {@link #release} is called and it is locked.
+ *
+ * @param refCounted true if this WifiLock should keep a reference count
+ */
+ public void setReferenceCounted(boolean refCounted) {
+ mRefCounted = refCounted;
+ }
+
+ /**
+ * Checks whether this WifiLock is currently held.
+ *
+ * @return true if this WifiLock is held, false otherwise
+ */
+ public boolean isHeld() {
+ synchronized (mBinder) {
+ return mHeld;
+ }
+ }
+
+ public void setWorkSource(WorkSource ws) {
+ synchronized (mBinder) {
+ if (ws != null && ws.isEmpty()) {
+ ws = null;
+ }
+ boolean changed = true;
+ if (ws == null) {
+ mWorkSource = null;
+ } else {
+ ws.clearNames();
+ if (mWorkSource == null) {
+ changed = mWorkSource != null;
+ mWorkSource = new WorkSource(ws);
+ } else {
+ changed = !mWorkSource.equals(ws);
+ if (changed) {
+ mWorkSource.set(ws);
+ }
+ }
+ }
+ if (changed && mHeld) {
+ try {
+ mService.updateWifiLockWorkSource(mBinder, mWorkSource);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+
+ public String toString() {
+ String s1, s2, s3;
+ synchronized (mBinder) {
+ s1 = Integer.toHexString(System.identityHashCode(this));
+ s2 = mHeld ? "held; " : "";
+ if (mRefCounted) {
+ s3 = "refcounted: refcount = " + mRefCount;
+ } else {
+ s3 = "not refcounted";
+ }
+ return "WifiLock{ " + s1 + "; " + s2 + s3 + " }";
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ synchronized (mBinder) {
+ if (mHeld) {
+ try {
+ mService.releaseWifiLock(mBinder);
+ synchronized (WifiManager.this) {
+ mActiveLockCount--;
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a new WifiLock.
+ *
+ * @param lockType the type of lock to create. See {@link #WIFI_MODE_FULL_HIGH_PERF}
+ * and {@link #WIFI_MODE_FULL_LOW_LATENCY} for descriptions of the types of Wi-Fi locks.
+ * @param tag a tag for the WifiLock to identify it in debugging messages. This string is
+ * never shown to the user under normal conditions, but should be descriptive
+ * enough to identify your application and the specific WifiLock within it, if it
+ * holds multiple WifiLocks.
+ *
+ * @return a new, unacquired WifiLock with the given tag.
+ *
+ * @see WifiLock
+ */
+ public WifiLock createWifiLock(int lockType, String tag) {
+ return new WifiLock(lockType, tag);
+ }
+
+ /**
+ * Creates a new WifiLock.
+ *
+ * @param tag a tag for the WifiLock to identify it in debugging messages. This string is
+ * never shown to the user under normal conditions, but should be descriptive
+ * enough to identify your application and the specific WifiLock within it, if it
+ * holds multiple WifiLocks.
+ *
+ * @return a new, unacquired WifiLock with the given tag.
+ *
+ * @see WifiLock
+ *
+ * @deprecated This API is non-functional.
+ */
+ @Deprecated
+ public WifiLock createWifiLock(String tag) {
+ return new WifiLock(WIFI_MODE_FULL, tag);
+ }
+
+ /**
+ * Create a new MulticastLock
+ *
+ * @param tag a tag for the MulticastLock to identify it in debugging
+ * messages. This string is never shown to the user under
+ * normal conditions, but should be descriptive enough to
+ * identify your application and the specific MulticastLock
+ * within it, if it holds multiple MulticastLocks.
+ *
+ * @return a new, unacquired MulticastLock with the given tag.
+ *
+ * @see MulticastLock
+ */
+ public MulticastLock createMulticastLock(String tag) {
+ return new MulticastLock(tag);
+ }
+
+ /**
+ * Allows an application to receive Wifi Multicast packets.
+ * Normally the Wifi stack filters out packets not explicitly
+ * addressed to this device. Acquring a MulticastLock will
+ * cause the stack to receive packets addressed to multicast
+ * addresses. Processing these extra packets can cause a noticeable
+ * battery drain and should be disabled when not needed.
+ */
+ public class MulticastLock {
+ private String mTag;
+ private final IBinder mBinder;
+ private int mRefCount;
+ private boolean mRefCounted;
+ private boolean mHeld;
+
+ private MulticastLock(String tag) {
+ mTag = tag;
+ mBinder = new Binder();
+ mRefCount = 0;
+ mRefCounted = true;
+ mHeld = false;
+ }
+
+ /**
+ * Locks Wifi Multicast on until {@link #release} is called.
+ *
+ * If this MulticastLock is reference-counted each call to
+ * {@code acquire} will increment the reference count, and the
+ * wifi interface will receive multicast packets as long as the
+ * reference count is above zero.
+ *
+ * If this MulticastLock is not reference-counted, the first call to
+ * {@code acquire} will turn on the multicast packets, but subsequent
+ * calls will be ignored. Only one call to {@link #release} will
+ * be required, regardless of the number of times that {@code acquire}
+ * is called.
+ *
+ * Note that other applications may also lock Wifi Multicast on.
+ * Only they can relinquish their lock.
+ *
+ * Also note that applications cannot leave Multicast locked on.
+ * When an app exits or crashes, any Multicast locks will be released.
+ */
+ public void acquire() {
+ synchronized (mBinder) {
+ if (mRefCounted ? (++mRefCount == 1) : (!mHeld)) {
+ try {
+ mService.acquireMulticastLock(mBinder, mTag);
+ synchronized (WifiManager.this) {
+ if (mActiveLockCount >= MAX_ACTIVE_LOCKS) {
+ mService.releaseMulticastLock(mTag);
+ throw new UnsupportedOperationException(
+ "Exceeded maximum number of wifi locks");
+ }
+ mActiveLockCount++;
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mHeld = true;
+ }
+ }
+ }
+
+ /**
+ * Unlocks Wifi Multicast, restoring the filter of packets
+ * not addressed specifically to this device and saving power.
+ *
+ * If this MulticastLock is reference-counted, each call to
+ * {@code release} will decrement the reference count, and the
+ * multicast packets will only stop being received when the reference
+ * count reaches zero. If the reference count goes below zero (that
+ * is, if {@code release} is called a greater number of times than
+ * {@link #acquire}), an exception is thrown.
+ *
+ * If this MulticastLock is not reference-counted, the first call to
+ * {@code release} (after the radio was multicast locked using
+ * {@link #acquire}) will unlock the multicast, and subsequent calls
+ * will be ignored.
+ *
+ * Note that if any other Wifi Multicast Locks are still outstanding
+ * this {@code release} call will not have an immediate effect. Only
+ * when all applications have released all their Multicast Locks will
+ * the Multicast filter be turned back on.
+ *
+ * Also note that when an app exits or crashes all of its Multicast
+ * Locks will be automatically released.
+ */
+ public void release() {
+ synchronized (mBinder) {
+ if (mRefCounted ? (--mRefCount == 0) : (mHeld)) {
+ try {
+ mService.releaseMulticastLock(mTag);
+ synchronized (WifiManager.this) {
+ mActiveLockCount--;
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mHeld = false;
+ }
+ if (mRefCount < 0) {
+ throw new RuntimeException("MulticastLock under-locked "
+ + mTag);
+ }
+ }
+ }
+
+ /**
+ * Controls whether this is a reference-counted or non-reference-
+ * counted MulticastLock.
+ *
+ * Reference-counted MulticastLocks keep track of the number of calls
+ * to {@link #acquire} and {@link #release}, and only stop the
+ * reception of multicast packets when every call to {@link #acquire}
+ * has been balanced with a call to {@link #release}. Non-reference-
+ * counted MulticastLocks allow the reception of multicast packets
+ * whenever {@link #acquire} is called and stop accepting multicast
+ * packets whenever {@link #release} is called.
+ *
+ * @param refCounted true if this MulticastLock should keep a reference
+ * count
+ */
+ public void setReferenceCounted(boolean refCounted) {
+ mRefCounted = refCounted;
+ }
+
+ /**
+ * Checks whether this MulticastLock is currently held.
+ *
+ * @return true if this MulticastLock is held, false otherwise
+ */
+ public boolean isHeld() {
+ synchronized (mBinder) {
+ return mHeld;
+ }
+ }
+
+ public String toString() {
+ String s1, s2, s3;
+ synchronized (mBinder) {
+ s1 = Integer.toHexString(System.identityHashCode(this));
+ s2 = mHeld ? "held; " : "";
+ if (mRefCounted) {
+ s3 = "refcounted: refcount = " + mRefCount;
+ } else {
+ s3 = "not refcounted";
+ }
+ return "MulticastLock{ " + s1 + "; " + s2 + s3 + " }";
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ setReferenceCounted(false);
+ release();
+ }
+ }
+
+ /**
+ * Check multicast filter status.
+ *
+ * @return true if multicast packets are allowed.
+ *
+ * @hide pending API council approval
+ */
+ public boolean isMulticastEnabled() {
+ try {
+ return mService.isMulticastEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Initialize the multicast filtering to 'on'
+ * @hide no intent to publish
+ */
+ @UnsupportedAppUsage
+ public boolean initializeMulticastFiltering() {
+ try {
+ mService.initializeMulticastFiltering();
+ return true;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ if (mAsyncChannel != null) {
+ mAsyncChannel.disconnect();
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Set wifi verbose log. Called from developer settings.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ @UnsupportedAppUsage
+ public void enableVerboseLogging (int verbose) {
+ try {
+ mService.enableVerboseLogging(verbose);
+ } catch (Exception e) {
+ //ignore any failure here
+ Log.e(TAG, "enableVerboseLogging " + e.toString());
+ }
+ }
+
+ /**
+ * Get the WiFi verbose logging level.This is used by settings
+ * to decide what to show within the picker.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public int getVerboseLoggingLevel() {
+ try {
+ return mService.getVerboseLoggingLevel();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes all saved wifi networks.
+ *
+ * @hide
+ */
+ public void factoryReset() {
+ try {
+ mService.factoryReset(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get Network object of current wifi network
+ * @return Get Network object of current wifi network
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public Network getCurrentNetwork() {
+ try {
+ return mService.getCurrentNetwork();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Deprecated
+ * returns false
+ * @hide
+ * @deprecated
+ */
+ public boolean setEnableAutoJoinWhenAssociated(boolean enabled) {
+ return false;
+ }
+
+ /**
+ * Deprecated
+ * returns false
+ * @hide
+ * @deprecated
+ */
+ public boolean getEnableAutoJoinWhenAssociated() {
+ return false;
+ }
+
+ /**
+ * Enable/disable WifiConnectivityManager
+ * @hide
+ */
+ public void enableWifiConnectivityManager(boolean enabled) {
+ try {
+ mService.enableWifiConnectivityManager(enabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Retrieve the data to be backed to save the current state.
+ * @hide
+ */
+ public byte[] retrieveBackupData() {
+ try {
+ return mService.retrieveBackupData();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Restore state from the backed up data.
+ * @hide
+ */
+ public void restoreBackupData(byte[] data) {
+ try {
+ mService.restoreBackupData(data);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Restore state from the older version of back up data.
+ * The old backup data was essentially a backup of wpa_supplicant.conf
+ * and ipconfig.txt file.
+ * @deprecated this is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public void restoreSupplicantBackupData(byte[] supplicantData, byte[] ipConfigData) {
+ try {
+ mService.restoreSupplicantBackupData(supplicantData, ipConfigData);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Start subscription provisioning flow
+ *
+ * @param provider {@link OsuProvider} to provision with
+ * @param executor the Executor on which to run the callback.
+ * @param callback {@link ProvisioningCallback} for updates regarding provisioning flow
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD
+ })
+ public void startSubscriptionProvisioning(@NonNull OsuProvider provider,
+ @NonNull @CallbackExecutor Executor executor, @NonNull ProvisioningCallback callback) {
+ // Verify arguments
+ if (executor == null) {
+ throw new IllegalArgumentException("executor must not be null");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("callback must not be null");
+ }
+ try {
+ mService.startSubscriptionProvisioning(provider,
+ new ProvisioningCallbackProxy(executor, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Helper class to support OSU Provisioning callbacks
+ */
+ private static class ProvisioningCallbackProxy extends IProvisioningCallback.Stub {
+ private final Executor mExecutor;
+ private final ProvisioningCallback mCallback;
+
+ ProvisioningCallbackProxy(Executor executor, ProvisioningCallback callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onProvisioningStatus(int status) {
+ mExecutor.execute(() -> mCallback.onProvisioningStatus(status));
+ }
+
+ @Override
+ public void onProvisioningFailure(int status) {
+ mExecutor.execute(() -> mCallback.onProvisioningFailure(status));
+ }
+
+ @Override
+ public void onProvisioningComplete() {
+ mExecutor.execute(() -> mCallback.onProvisioningComplete());
+ }
+ }
+
+ /**
+ * Base class for Traffic state callback. Should be extended by applications and set when
+ * calling {@link WifiManager#registerTrafficStateCallback(TrafficStateCallback, Handler)}.
+ * @hide
+ */
+ public interface TrafficStateCallback {
+ /**
+ * Lowest bit indicates data reception and the second lowest
+ * bit indicates data transmitted
+ */
+ /** @hide */
+ int DATA_ACTIVITY_NONE = 0x00;
+ /** @hide */
+ int DATA_ACTIVITY_IN = 0x01;
+ /** @hide */
+ int DATA_ACTIVITY_OUT = 0x02;
+ /** @hide */
+ int DATA_ACTIVITY_INOUT = 0x03;
+
+ /**
+ * Callback invoked to inform clients about the current traffic state.
+ *
+ * @param state One of the values: {@link #DATA_ACTIVITY_NONE}, {@link #DATA_ACTIVITY_IN},
+ * {@link #DATA_ACTIVITY_OUT} & {@link #DATA_ACTIVITY_INOUT}.
+ * @hide
+ */
+ void onStateChanged(int state);
+ }
+
+ /**
+ * Callback proxy for TrafficStateCallback objects.
+ *
+ * @hide
+ */
+ private class TrafficStateCallbackProxy extends ITrafficStateCallback.Stub {
+ private final Handler mHandler;
+ private final TrafficStateCallback mCallback;
+
+ TrafficStateCallbackProxy(Looper looper, TrafficStateCallback callback) {
+ mHandler = new Handler(looper);
+ mCallback = callback;
+ }
+
+ @Override
+ public void onStateChanged(int state) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "TrafficStateCallbackProxy: onStateChanged state=" + state);
+ }
+ mHandler.post(() -> {
+ mCallback.onStateChanged(state);
+ });
+ }
+ }
+
+ /**
+ * Registers a callback for monitoring traffic state. See {@link TrafficStateCallback}. These
+ * callbacks will be invoked periodically by platform to inform clients about the current
+ * traffic state. Caller can unregister a previously registered callback using
+ * {@link #unregisterTrafficStateCallback(TrafficStateCallback)}
+ * <p>
+ * Applications should have the
+ * {@link android.Manifest.permission#NETWORK_SETTINGS NETWORK_SETTINGS} permission. Callers
+ * without the permission will trigger a {@link java.lang.SecurityException}.
+ * <p>
+ *
+ * @param callback Callback for traffic state events
+ * @param handler The Handler on whose thread to execute the callbacks of the {@code callback}
+ * object. If null, then the application's main thread will be used.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public void registerTrafficStateCallback(@NonNull TrafficStateCallback callback,
+ @Nullable Handler handler) {
+ if (callback == null) throw new IllegalArgumentException("callback cannot be null");
+ Log.v(TAG, "registerTrafficStateCallback: callback=" + callback + ", handler=" + handler);
+
+ Looper looper = (handler == null) ? mContext.getMainLooper() : handler.getLooper();
+ Binder binder = new Binder();
+ try {
+ mService.registerTrafficStateCallback(
+ binder, new TrafficStateCallbackProxy(looper, callback), callback.hashCode());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Allow callers to unregister a previously registered callback. After calling this method,
+ * applications will no longer receive traffic state notifications.
+ *
+ * @param callback Callback to unregister for traffic state events
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public void unregisterTrafficStateCallback(@NonNull TrafficStateCallback callback) {
+ if (callback == null) throw new IllegalArgumentException("callback cannot be null");
+ Log.v(TAG, "unregisterTrafficStateCallback: callback=" + callback);
+
+ try {
+ mService.unregisterTrafficStateCallback(callback.hashCode());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Helper method to update the local verbose logging flag based on the verbose logging
+ * level from wifi service.
+ */
+ private void updateVerboseLoggingEnabledFromService() {
+ mVerboseLoggingEnabled = getVerboseLoggingLevel() > 0;
+ }
+
+ /**
+ * @return true if this device supports WPA3-Personal SAE
+ */
+ public boolean isWpa3SaeSupported() {
+ return isFeatureSupported(WIFI_FEATURE_WPA3_SAE);
+ }
+
+ /**
+ * @return true if this device supports WPA3-Enterprise Suite-B-192
+ */
+ public boolean isWpa3SuiteBSupported() {
+ return isFeatureSupported(WIFI_FEATURE_WPA3_SUITE_B);
+ }
+
+ /**
+ * @return true if this device supports Wi-Fi Enhanced Open (OWE)
+ */
+ public boolean isEnhancedOpenSupported() {
+ return isFeatureSupported(WIFI_FEATURE_OWE);
+ }
+
+ /**
+ * Wi-Fi Easy Connect (DPP) introduces standardized mechanisms to simplify the provisioning and
+ * configuration of Wi-Fi devices.
+ * For more details, visit <a href="https://www.wi-fi.org/">https://www.wi-fi.org/</a> and
+ * search for "Easy Connect" or "Device Provisioning Protocol specification".
+ *
+ * @return true if this device supports Wi-Fi Easy-connect (Device Provisioning Protocol)
+ */
+ public boolean isEasyConnectSupported() {
+ return isFeatureSupported(WIFI_FEATURE_DPP);
+ }
+
+ /**
+ * Gets the factory Wi-Fi MAC addresses.
+ * @return Array of String representing Wi-Fi MAC addresses sorted lexically or an empty Array
+ * if failed.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public String[] getFactoryMacAddresses() {
+ try {
+ return mService.getFactoryMacAddresses();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"DEVICE_MOBILITY_STATE_"}, value = {
+ DEVICE_MOBILITY_STATE_UNKNOWN,
+ DEVICE_MOBILITY_STATE_HIGH_MVMT,
+ DEVICE_MOBILITY_STATE_LOW_MVMT,
+ DEVICE_MOBILITY_STATE_STATIONARY})
+ public @interface DeviceMobilityState {}
+
+ /**
+ * Unknown device mobility state
+ *
+ * @see #setDeviceMobilityState(int)
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int DEVICE_MOBILITY_STATE_UNKNOWN = 0;
+
+ /**
+ * High movement device mobility state.
+ * e.g. on a bike, in a motor vehicle
+ *
+ * @see #setDeviceMobilityState(int)
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int DEVICE_MOBILITY_STATE_HIGH_MVMT = 1;
+
+ /**
+ * Low movement device mobility state.
+ * e.g. walking, running
+ *
+ * @see #setDeviceMobilityState(int)
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int DEVICE_MOBILITY_STATE_LOW_MVMT = 2;
+
+ /**
+ * Stationary device mobility state
+ *
+ * @see #setDeviceMobilityState(int)
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int DEVICE_MOBILITY_STATE_STATIONARY = 3;
+
+ /**
+ * Updates the device mobility state. Wifi uses this information to adjust the interval between
+ * Wifi scans in order to balance power consumption with scan accuracy.
+ * The default mobility state when the device boots is {@link #DEVICE_MOBILITY_STATE_UNKNOWN}.
+ * This API should be called whenever there is a change in the mobility state.
+ * @param state the updated device mobility state
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE)
+ public void setDeviceMobilityState(@DeviceMobilityState int state) {
+ try {
+ mService.setDeviceMobilityState(state);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /* Easy Connect - AKA Device Provisioning Protocol (DPP) */
+
+ /**
+ * Easy Connect Network role: Station.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int EASY_CONNECT_NETWORK_ROLE_STA = 0;
+
+ /**
+ * Easy Connect Network role: Access Point.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int EASY_CONNECT_NETWORK_ROLE_AP = 1;
+
+ /** @hide */
+ @IntDef(prefix = {"EASY_CONNECT_NETWORK_ROLE_"}, value = {
+ EASY_CONNECT_NETWORK_ROLE_STA,
+ EASY_CONNECT_NETWORK_ROLE_AP,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EasyConnectNetworkRole {
+ }
+
+ /**
+ * Start Easy Connect (DPP) in Configurator-Initiator role. The current device will initiate
+ * Easy Connect bootstrapping with a peer, and configure the peer with the SSID and password of
+ * the specified network using the Easy Connect protocol on an encrypted link.
+ *
+ * @param enrolleeUri URI of the Enrollee obtained separately (e.g. QR code scanning)
+ * @param selectedNetworkId Selected network ID to be sent to the peer
+ * @param enrolleeNetworkRole The network role of the enrollee
+ * @param callback Callback for status updates
+ * @param executor The Executor on which to run the callback.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ public void startEasyConnectAsConfiguratorInitiator(@NonNull String enrolleeUri,
+ int selectedNetworkId, @EasyConnectNetworkRole int enrolleeNetworkRole,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull EasyConnectStatusCallback callback) {
+ Binder binder = new Binder();
+ try {
+ mService.startDppAsConfiguratorInitiator(binder, enrolleeUri, selectedNetworkId,
+ enrolleeNetworkRole, new EasyConnectCallbackProxy(executor, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Start Easy Connect (DPP) in Enrollee-Initiator role. The current device will initiate Easy
+ * Connect bootstrapping with a peer, and receive the SSID and password from the peer
+ * configurator.
+ *
+ * @param configuratorUri URI of the Configurator obtained separately (e.g. QR code scanning)
+ * @param callback Callback for status updates
+ * @param executor The Executor on which to run the callback.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ public void startEasyConnectAsEnrolleeInitiator(@NonNull String configuratorUri,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull EasyConnectStatusCallback callback) {
+ Binder binder = new Binder();
+ try {
+ mService.startDppAsEnrolleeInitiator(binder, configuratorUri,
+ new EasyConnectCallbackProxy(executor, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Stop or abort a current Easy Connect (DPP) session. This call, once processed, will
+ * terminate any ongoing transaction, and clean up all associated resources. Caller should not
+ * expect any callbacks once this call is made. However, due to the asynchronous nature of
+ * this call, a callback may be fired if it was already pending in the queue.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ public void stopEasyConnectSession() {
+ try {
+ /* Request lower layers to stop/abort and clear resources */
+ mService.stopDppSession();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Helper class to support Easy Connect (DPP) callbacks
+ *
+ * @hide
+ */
+ private static class EasyConnectCallbackProxy extends IDppCallback.Stub {
+ private final Executor mExecutor;
+ private final EasyConnectStatusCallback mEasyConnectStatusCallback;
+
+ EasyConnectCallbackProxy(Executor executor,
+ EasyConnectStatusCallback easyConnectStatusCallback) {
+ mExecutor = executor;
+ mEasyConnectStatusCallback = easyConnectStatusCallback;
+ }
+
+ @Override
+ public void onSuccessConfigReceived(int newNetworkId) {
+ Log.d(TAG, "Easy Connect onSuccessConfigReceived callback");
+ mExecutor.execute(() -> {
+ mEasyConnectStatusCallback.onEnrolleeSuccess(newNetworkId);
+ });
+ }
+
+ @Override
+ public void onSuccess(int status) {
+ Log.d(TAG, "Easy Connect onSuccess callback");
+ mExecutor.execute(() -> {
+ mEasyConnectStatusCallback.onConfiguratorSuccess(status);
+ });
+ }
+
+ @Override
+ public void onFailure(int status) {
+ Log.d(TAG, "Easy Connect onFailure callback");
+ mExecutor.execute(() -> {
+ mEasyConnectStatusCallback.onFailure(status);
+ });
+ }
+
+ @Override
+ public void onProgress(int status) {
+ Log.d(TAG, "Easy Connect onProgress callback");
+ mExecutor.execute(() -> {
+ mEasyConnectStatusCallback.onProgress(status);
+ });
+ }
+ }
+
+ /**
+ * Interface for Wi-Fi usability statistics listener. Should be implemented by applications and
+ * set when calling {@link WifiManager#addOnWifiUsabilityStatsListener(Executor,
+ * OnWifiUsabilityStatsListener)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public interface OnWifiUsabilityStatsListener {
+ /**
+ * Called when Wi-Fi usability statistics is updated.
+ *
+ * @param seqNum The sequence number of statistics, used to derive the timing of updated
+ * Wi-Fi usability statistics, set by framework and incremented by one after
+ * each update.
+ * @param isSameBssidAndFreq The flag to indicate whether the BSSID and the frequency of
+ * network stays the same or not relative to the last update of
+ * Wi-Fi usability stats.
+ * @param stats The updated Wi-Fi usability statistics.
+ */
+ void onWifiUsabilityStats(int seqNum, boolean isSameBssidAndFreq,
+ @NonNull WifiUsabilityStatsEntry stats);
+ }
+
+ /**
+ * Adds a listener for Wi-Fi usability statistics. See {@link OnWifiUsabilityStatsListener}.
+ * Multiple listeners can be added. Callers will be invoked periodically by framework to
+ * inform clients about the current Wi-Fi usability statistics. Callers can remove a previously
+ * added listener using {@link removeOnWifiUsabilityStatsListener}.
+ *
+ * @param executor The executor on which callback will be invoked.
+ * @param listener Listener for Wifi usability statistics.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE)
+ public void addOnWifiUsabilityStatsListener(@NonNull @CallbackExecutor Executor executor,
+ @NonNull OnWifiUsabilityStatsListener listener) {
+ if (executor == null) throw new IllegalArgumentException("executor cannot be null");
+ if (listener == null) throw new IllegalArgumentException("listener cannot be null");
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "addOnWifiUsabilityStatsListener: listener=" + listener);
+ }
+ try {
+ mService.addOnWifiUsabilityStatsListener(new Binder(),
+ new IOnWifiUsabilityStatsListener.Stub() {
+ @Override
+ public void onWifiUsabilityStats(int seqNum, boolean isSameBssidAndFreq,
+ WifiUsabilityStatsEntry stats) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "OnWifiUsabilityStatsListener: "
+ + "onWifiUsabilityStats: seqNum=" + seqNum);
+ }
+ Binder.withCleanCallingIdentity(() ->
+ executor.execute(() -> listener.onWifiUsabilityStats(seqNum,
+ isSameBssidAndFreq, stats)));
+ }
+ },
+ listener.hashCode()
+ );
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Allow callers to remove a previously registered listener. After calling this method,
+ * applications will no longer receive Wi-Fi usability statistics.
+ *
+ * @param listener Listener to remove the Wi-Fi usability statistics.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE)
+ public void removeOnWifiUsabilityStatsListener(@NonNull OnWifiUsabilityStatsListener listener) {
+ if (listener == null) throw new IllegalArgumentException("listener cannot be null");
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "removeOnWifiUsabilityStatsListener: listener=" + listener);
+ }
+ try {
+ mService.removeOnWifiUsabilityStatsListener(listener.hashCode());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Provide a Wi-Fi usability score information to be recorded (but not acted upon) by the
+ * framework. The Wi-Fi usability score is derived from {@link OnWifiUsabilityStatsListener}
+ * where a score is matched to Wi-Fi usability statistics using the sequence number. The score
+ * is used to quantify whether Wi-Fi is usable in a future time.
+ *
+ * @param seqNum Sequence number of the Wi-Fi usability score.
+ * @param score The Wi-Fi usability score, expected range: [0, 100].
+ * @param predictionHorizonSec Prediction horizon of the Wi-Fi usability score in second,
+ * expected range: [0, 30].
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE)
+ public void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec) {
+ try {
+ mService.updateWifiUsabilityScore(seqNum, score, predictionHorizonSec);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/android/net/wifi/WifiNetworkAgentSpecifier.java b/android/net/wifi/WifiNetworkAgentSpecifier.java
new file mode 100644
index 0000000..24aa23a
--- /dev/null
+++ b/android/net/wifi/WifiNetworkAgentSpecifier.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.internal.util.Preconditions.checkState;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.MacAddress;
+import android.net.MatchAllNetworkSpecifier;
+import android.net.NetworkAgent;
+import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Network specifier object used by wifi's {@link android.net.NetworkAgent}.
+ * @hide
+ */
+public final class WifiNetworkAgentSpecifier extends NetworkSpecifier implements Parcelable {
+ /**
+ * Security credentials for the currently connected network.
+ */
+ private final WifiConfiguration mWifiConfiguration;
+
+ /**
+ * The UID of the app that requested a specific wifi network using {@link WifiNetworkSpecifier}.
+ *
+ * Will only be filled when the device connects to a wifi network as a result of a
+ * {@link NetworkRequest} with {@link WifiNetworkSpecifier}. Will be set to -1 if the device
+ * auto-connected to a wifi network.
+ */
+ private final int mOriginalRequestorUid;
+
+ /**
+ * The package name of the app that requested a specific wifi network using
+ * {@link WifiNetworkSpecifier}.
+ *
+ * Will only be filled when the device connects to a wifi network as a result of a
+ * {@link NetworkRequest} with {@link WifiNetworkSpecifier}. Will be set to null if the device
+ * auto-connected to a wifi network.
+ */
+ private final String mOriginalRequestorPackageName;
+
+ public WifiNetworkAgentSpecifier(@NonNull WifiConfiguration wifiConfiguration,
+ int originalRequestorUid,
+ @Nullable String originalRequestorPackageName) {
+ checkNotNull(wifiConfiguration);
+
+ mWifiConfiguration = wifiConfiguration;
+ mOriginalRequestorUid = originalRequestorUid;
+ mOriginalRequestorPackageName = originalRequestorPackageName;
+ }
+
+ /**
+ * @hide
+ */
+ public static final @android.annotation.NonNull Creator<WifiNetworkAgentSpecifier> CREATOR =
+ new Creator<WifiNetworkAgentSpecifier>() {
+ @Override
+ public WifiNetworkAgentSpecifier createFromParcel(@NonNull Parcel in) {
+ WifiConfiguration wifiConfiguration = in.readParcelable(null);
+ int originalRequestorUid = in.readInt();
+ String originalRequestorPackageName = in.readString();
+ return new WifiNetworkAgentSpecifier(
+ wifiConfiguration, originalRequestorUid, originalRequestorPackageName);
+ }
+
+ @Override
+ public WifiNetworkAgentSpecifier[] newArray(int size) {
+ return new WifiNetworkAgentSpecifier[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mWifiConfiguration, flags);
+ dest.writeInt(mOriginalRequestorUid);
+ dest.writeString(mOriginalRequestorPackageName);
+ }
+
+ @Override
+ public boolean satisfiedBy(@Nullable NetworkSpecifier other) {
+ if (this == other) {
+ return true;
+ }
+ // Any generic requests should be satisifed by a specific wifi network.
+ if (other == null || other instanceof MatchAllNetworkSpecifier) {
+ return true;
+ }
+ if (other instanceof WifiNetworkSpecifier) {
+ return satisfiesNetworkSpecifier((WifiNetworkSpecifier) other);
+ }
+ return equals(other);
+ }
+
+ /**
+ * Match {@link WifiNetworkSpecifier} in app's {@link NetworkRequest} with the
+ * {@link WifiNetworkAgentSpecifier} in wifi platform's {@link NetworkAgent}.
+ */
+ public boolean satisfiesNetworkSpecifier(@NonNull WifiNetworkSpecifier ns) {
+ // None of these should be null by construction.
+ // {@link WifiNetworkSpecifier.Builder} enforces non-null in {@link WifiNetworkSpecifier}.
+ // {@link WifiNetworkFactory} ensures non-null in {@link WifiNetworkAgentSpecifier}.
+ checkNotNull(ns);
+ checkNotNull(ns.ssidPatternMatcher);
+ checkNotNull(ns.bssidPatternMatcher);
+ checkNotNull(ns.wifiConfiguration.allowedKeyManagement);
+ checkNotNull(this.mWifiConfiguration.SSID);
+ checkNotNull(this.mWifiConfiguration.BSSID);
+ checkNotNull(this.mWifiConfiguration.allowedKeyManagement);
+
+ final String ssidWithQuotes = this.mWifiConfiguration.SSID;
+ checkState(ssidWithQuotes.startsWith("\"") && ssidWithQuotes.endsWith("\""));
+ final String ssidWithoutQuotes = ssidWithQuotes.substring(1, ssidWithQuotes.length() - 1);
+ if (!ns.ssidPatternMatcher.match(ssidWithoutQuotes)) {
+ return false;
+ }
+ final MacAddress bssid = MacAddress.fromString(this.mWifiConfiguration.BSSID);
+ final MacAddress matchBaseAddress = ns.bssidPatternMatcher.first;
+ final MacAddress matchMask = ns.bssidPatternMatcher.second;
+ if (!bssid.matches(matchBaseAddress, matchMask)) {
+ return false;
+ }
+ if (!ns.wifiConfiguration.allowedKeyManagement.equals(
+ this.mWifiConfiguration.allowedKeyManagement)) {
+ return false;
+ }
+ if (ns.requestorUid != this.mOriginalRequestorUid) {
+ return false;
+ }
+ if (!TextUtils.equals(ns.requestorPackageName, this.mOriginalRequestorPackageName)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mWifiConfiguration.SSID,
+ mWifiConfiguration.BSSID,
+ mWifiConfiguration.allowedKeyManagement,
+ mOriginalRequestorUid,
+ mOriginalRequestorPackageName);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof WifiNetworkAgentSpecifier)) {
+ return false;
+ }
+ WifiNetworkAgentSpecifier lhs = (WifiNetworkAgentSpecifier) obj;
+ return Objects.equals(this.mWifiConfiguration.SSID, lhs.mWifiConfiguration.SSID)
+ && Objects.equals(this.mWifiConfiguration.BSSID, lhs.mWifiConfiguration.BSSID)
+ && Objects.equals(this.mWifiConfiguration.allowedKeyManagement,
+ lhs.mWifiConfiguration.allowedKeyManagement)
+ && mOriginalRequestorUid == lhs.mOriginalRequestorUid
+ && TextUtils.equals(mOriginalRequestorPackageName,
+ lhs.mOriginalRequestorPackageName);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("WifiNetworkAgentSpecifier [");
+ sb.append("WifiConfiguration=")
+ .append(", SSID=").append(mWifiConfiguration.SSID)
+ .append(", BSSID=").append(mWifiConfiguration.BSSID)
+ .append(", mOriginalRequestorUid=").append(mOriginalRequestorUid)
+ .append(", mOriginalRequestorPackageName=").append(mOriginalRequestorPackageName)
+ .append("]");
+ return sb.toString();
+ }
+
+ @Override
+ public void assertValidFromUid(int requestorUid) {
+ throw new IllegalStateException("WifiNetworkAgentSpecifier should never be used "
+ + "for requests.");
+ }
+
+ @Override
+ public NetworkSpecifier redact() {
+ return null;
+ }
+}
diff --git a/android/net/wifi/WifiNetworkConnectionStatistics.java b/android/net/wifi/WifiNetworkConnectionStatistics.java
new file mode 100644
index 0000000..8262a7a
--- /dev/null
+++ b/android/net/wifi/WifiNetworkConnectionStatistics.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2014 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.SystemApi;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Connection Statistics For a WiFi Network.
+ * @hide
+ */
+@SystemApi
+public class WifiNetworkConnectionStatistics implements Parcelable {
+ private static final String TAG = "WifiNetworkConnnectionStatistics";
+
+ public int numConnection;
+ public int numUsage;
+
+ public WifiNetworkConnectionStatistics(int connection, int usage) {
+ numConnection = connection;
+ numUsage = usage;
+ }
+
+ public WifiNetworkConnectionStatistics() { }
+
+
+ @Override
+ public String toString() {
+ StringBuilder sbuf = new StringBuilder();
+ sbuf.append("c=").append(numConnection);
+ sbuf.append(" u=").append(numUsage);
+ return sbuf.toString();
+ }
+
+
+ /** copy constructor*/
+ public WifiNetworkConnectionStatistics(WifiNetworkConnectionStatistics source) {
+ numConnection = source.numConnection;
+ numUsage = source.numUsage;
+ }
+
+ /** Implement the Parcelable interface */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(numConnection);
+ dest.writeInt(numUsage);
+ }
+
+ /** Implement the Parcelable interface */
+ public static final @android.annotation.NonNull Creator<WifiNetworkConnectionStatistics> CREATOR =
+ new Creator<WifiNetworkConnectionStatistics>() {
+ public WifiNetworkConnectionStatistics createFromParcel(Parcel in) {
+ int numConnection = in.readInt();
+ int numUsage = in.readInt();
+ WifiNetworkConnectionStatistics stats =
+ new WifiNetworkConnectionStatistics(numConnection, numUsage);
+ return stats;
+ }
+
+ public WifiNetworkConnectionStatistics[] newArray(int size) {
+ return new WifiNetworkConnectionStatistics[size];
+ }
+ };
+}
diff --git a/android/net/wifi/WifiNetworkScoreCache.java b/android/net/wifi/WifiNetworkScoreCache.java
new file mode 100644
index 0000000..b22ae07
--- /dev/null
+++ b/android/net/wifi/WifiNetworkScoreCache.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2014 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.Manifest.permission;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.INetworkScoreCache;
+import android.net.NetworkKey;
+import android.net.ScoredNetwork;
+import android.os.Handler;
+import android.os.Process;
+import android.util.Log;
+import android.util.LruCache;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * {@link INetworkScoreCache} implementation for Wifi Networks.
+ *
+ * @hide
+ */
+public class WifiNetworkScoreCache extends INetworkScoreCache.Stub {
+ private static final String TAG = "WifiNetworkScoreCache";
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+
+ // A Network scorer returns a score in the range [-128, +127]
+ // We treat the lowest possible score as though there were no score, effectively allowing the
+ // scorer to provide an RSSI threshold below which a network should not be used.
+ public static final int INVALID_NETWORK_SCORE = Byte.MIN_VALUE;
+
+ /** Default number entries to be stored in the {@link LruCache}. */
+ private static final int DEFAULT_MAX_CACHE_SIZE = 100;
+
+ // See {@link #CacheListener}.
+ @Nullable
+ @GuardedBy("mLock")
+ private CacheListener mListener;
+
+ private final Context mContext;
+ private final Object mLock = new Object();
+
+ // The key is of the form "<ssid>"<bssid>
+ // TODO: What about SSIDs that can't be encoded as UTF-8?
+ @GuardedBy("mLock")
+ private final LruCache<String, ScoredNetwork> mCache;
+
+ public WifiNetworkScoreCache(Context context) {
+ this(context, null /* listener */);
+ }
+
+ /**
+ * Instantiates a WifiNetworkScoreCache.
+ *
+ * @param context Application context
+ * @param listener CacheListener for cache updates
+ */
+ public WifiNetworkScoreCache(Context context, @Nullable CacheListener listener) {
+ this(context, listener, DEFAULT_MAX_CACHE_SIZE);
+ }
+
+ public WifiNetworkScoreCache(
+ Context context, @Nullable CacheListener listener, int maxCacheSize) {
+ mContext = context.getApplicationContext();
+ mListener = listener;
+ mCache = new LruCache<>(maxCacheSize);
+ }
+
+ @Override public final void updateScores(List<ScoredNetwork> networks) {
+ if (networks == null || networks.isEmpty()) {
+ return;
+ }
+ if (DBG) {
+ Log.d(TAG, "updateScores list size=" + networks.size());
+ }
+
+ boolean changed = false;
+
+ synchronized(mLock) {
+ for (ScoredNetwork network : networks) {
+ String networkKey = buildNetworkKey(network);
+ if (networkKey == null) {
+ if (DBG) {
+ Log.d(TAG, "Failed to build network key for ScoredNetwork" + network);
+ }
+ continue;
+ }
+ mCache.put(networkKey, network);
+ changed = true;
+ }
+
+ if (mListener != null && changed) {
+ mListener.post(networks);
+ }
+ }
+ }
+
+ @Override public final void clearScores() {
+ synchronized (mLock) {
+ mCache.evictAll();
+ }
+ }
+
+ /**
+ * Returns whether there is any score info for the given ScanResult.
+ *
+ * This includes null-score info, so it should only be used when determining whether to request
+ * scores from the network scorer.
+ */
+ public boolean isScoredNetwork(ScanResult result) {
+ return getScoredNetwork(result) != null;
+ }
+
+ /**
+ * Returns whether there is a non-null score curve for the given ScanResult.
+ *
+ * A null score curve has special meaning - we should never connect to an ephemeral network if
+ * the score curve is null.
+ */
+ public boolean hasScoreCurve(ScanResult result) {
+ ScoredNetwork network = getScoredNetwork(result);
+ return network != null && network.rssiCurve != null;
+ }
+
+ public int getNetworkScore(ScanResult result) {
+ int score = INVALID_NETWORK_SCORE;
+
+ ScoredNetwork network = getScoredNetwork(result);
+ if (network != null && network.rssiCurve != null) {
+ score = network.rssiCurve.lookupScore(result.level);
+ if (DBG) {
+ Log.d(TAG, "getNetworkScore found scored network " + network.networkKey
+ + " score " + Integer.toString(score)
+ + " RSSI " + result.level);
+ }
+ }
+ return score;
+ }
+
+ /**
+ * Returns the ScoredNetwork metered hint for a given ScanResult.
+ *
+ * If there is no ScoredNetwork associated with the ScanResult then false will be returned.
+ */
+ public boolean getMeteredHint(ScanResult result) {
+ ScoredNetwork network = getScoredNetwork(result);
+ return network != null && network.meteredHint;
+ }
+
+ public int getNetworkScore(ScanResult result, boolean isActiveNetwork) {
+ int score = INVALID_NETWORK_SCORE;
+
+ ScoredNetwork network = getScoredNetwork(result);
+ if (network != null && network.rssiCurve != null) {
+ score = network.rssiCurve.lookupScore(result.level, isActiveNetwork);
+ if (DBG) {
+ Log.d(TAG, "getNetworkScore found scored network " + network.networkKey
+ + " score " + Integer.toString(score)
+ + " RSSI " + result.level
+ + " isActiveNetwork " + isActiveNetwork);
+ }
+ }
+ return score;
+ }
+
+ @Nullable
+ public ScoredNetwork getScoredNetwork(ScanResult result) {
+ String key = buildNetworkKey(result);
+ if (key == null) return null;
+
+ synchronized(mLock) {
+ ScoredNetwork network = mCache.get(key);
+ return network;
+ }
+ }
+
+ /** Returns the ScoredNetwork for the given key. */
+ @Nullable
+ public ScoredNetwork getScoredNetwork(NetworkKey networkKey) {
+ String key = buildNetworkKey(networkKey);
+ if (key == null) {
+ if (DBG) {
+ Log.d(TAG, "Could not build key string for Network Key: " + networkKey);
+ }
+ return null;
+ }
+ synchronized (mLock) {
+ return mCache.get(key);
+ }
+ }
+
+ private String buildNetworkKey(ScoredNetwork network) {
+ if (network == null) {
+ return null;
+ }
+ return buildNetworkKey(network.networkKey);
+ }
+
+ private String buildNetworkKey(NetworkKey networkKey) {
+ if (networkKey == null) {
+ return null;
+ }
+ if (networkKey.wifiKey == null) return null;
+ if (networkKey.type == NetworkKey.TYPE_WIFI) {
+ String key = networkKey.wifiKey.ssid;
+ if (key == null) return null;
+ if (networkKey.wifiKey.bssid != null) {
+ key = key + networkKey.wifiKey.bssid;
+ }
+ return key;
+ }
+ return null;
+ }
+
+ private String buildNetworkKey(ScanResult result) {
+ if (result == null || result.SSID == null) {
+ return null;
+ }
+ StringBuilder key = new StringBuilder("\"");
+ key.append(result.SSID);
+ key.append("\"");
+ if (result.BSSID != null) {
+ key.append(result.BSSID);
+ }
+ return key.toString();
+ }
+
+ @Override protected final void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
+ String header = String.format("WifiNetworkScoreCache (%s/%d)",
+ mContext.getPackageName(), Process.myUid());
+ writer.println(header);
+ writer.println(" All score curves:");
+ synchronized (mLock) {
+ for (ScoredNetwork score : mCache.snapshot().values()) {
+ writer.println(" " + score);
+ }
+ writer.println(" Network scores for latest ScanResults:");
+ WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ for (ScanResult scanResult : wifiManager.getScanResults()) {
+ writer.println(
+ " " + buildNetworkKey(scanResult) + ": " + getNetworkScore(scanResult));
+ }
+ }
+ }
+
+ /** Registers a CacheListener instance, replacing the previous listener if it existed. */
+ public void registerListener(CacheListener listener) {
+ synchronized (mLock) {
+ mListener = listener;
+ }
+ }
+
+ /** Removes the registered CacheListener. */
+ public void unregisterListener() {
+ synchronized (mLock) {
+ mListener = null;
+ }
+ }
+
+ /** Listener for updates to the cache inside WifiNetworkScoreCache. */
+ public abstract static class CacheListener {
+ private Handler mHandler;
+
+ /**
+ * Constructor for CacheListener.
+ *
+ * @param handler the Handler on which to invoke the {@link #networkCacheUpdated} method.
+ * This cannot be null.
+ */
+ public CacheListener(@NonNull Handler handler) {
+ Preconditions.checkNotNull(handler);
+ mHandler = handler;
+ }
+
+ /** Invokes the {@link #networkCacheUpdated(List<ScoredNetwork>)} method on the handler. */
+ void post(List<ScoredNetwork> updatedNetworks) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ networkCacheUpdated(updatedNetworks);
+ }
+ });
+ }
+
+ /**
+ * Invoked whenever the cache is updated.
+ *
+ * <p>Clearing the cache does not invoke this method.
+ *
+ * @param updatedNetworks the networks that were updated
+ */
+ public abstract void networkCacheUpdated(List<ScoredNetwork> updatedNetworks);
+ }
+}
diff --git a/android/net/wifi/WifiNetworkSpecifier.java b/android/net/wifi/WifiNetworkSpecifier.java
new file mode 100644
index 0000000..6c2d7ff
--- /dev/null
+++ b/android/net/wifi/WifiNetworkSpecifier.java
@@ -0,0 +1,609 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.net.MacAddress;
+import android.net.MatchAllNetworkSpecifier;
+import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PatternMatcher;
+import android.os.Process;
+import android.text.TextUtils;
+import android.util.Pair;
+
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+
+/**
+ * Network specifier object used to request a local Wi-Fi network. Apps should use the
+ * {@link WifiNetworkSpecifier.Builder} class to create an instance.
+ */
+public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+
+ /**
+ * Builder used to create {@link WifiNetworkSpecifier} objects.
+ */
+ public static final class Builder {
+ private static final String MATCH_ALL_SSID_PATTERN_PATH = ".*";
+ private static final String MATCH_EMPTY_SSID_PATTERN_PATH = "";
+ private static final Pair<MacAddress, MacAddress> MATCH_NO_BSSID_PATTERN1 =
+ new Pair(MacAddress.BROADCAST_ADDRESS, MacAddress.BROADCAST_ADDRESS);
+ private static final Pair<MacAddress, MacAddress> MATCH_NO_BSSID_PATTERN2 =
+ new Pair(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.BROADCAST_ADDRESS);
+ private static final Pair<MacAddress, MacAddress> MATCH_ALL_BSSID_PATTERN =
+ new Pair(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS);
+ private static final MacAddress MATCH_EXACT_BSSID_PATTERN_MASK =
+ MacAddress.BROADCAST_ADDRESS;
+
+ /**
+ * SSID pattern match specified by the app.
+ */
+ private @Nullable PatternMatcher mSsidPatternMatcher;
+ /**
+ * BSSID pattern match specified by the app.
+ * Pair of <BaseAddress, Mask>.
+ */
+ private @Nullable Pair<MacAddress, MacAddress> mBssidPatternMatcher;
+ /**
+ * Whether this is an OWE network or not.
+ */
+ private boolean mIsEnhancedOpen;
+ /**
+ * Pre-shared key for use with WPA-PSK networks.
+ */
+ private @Nullable String mWpa2PskPassphrase;
+ /**
+ * Pre-shared key for use with WPA3-SAE networks.
+ */
+ private @Nullable String mWpa3SaePassphrase;
+ /**
+ * The enterprise configuration details specifying the EAP method,
+ * certificates and other settings associated with the WPA-EAP networks.
+ */
+ private @Nullable WifiEnterpriseConfig mWpa2EnterpriseConfig;
+ /**
+ * The enterprise configuration details specifying the EAP method,
+ * certificates and other settings associated with the SuiteB networks.
+ */
+ private @Nullable WifiEnterpriseConfig mWpa3EnterpriseConfig;
+ /**
+ * This is a network that does not broadcast its SSID, so an
+ * SSID-specific probe request must be used for scans.
+ */
+ private boolean mIsHiddenSSID;
+
+ public Builder() {
+ mSsidPatternMatcher = null;
+ mBssidPatternMatcher = null;
+ mIsEnhancedOpen = false;
+ mWpa2PskPassphrase = null;
+ mWpa3SaePassphrase = null;
+ mWpa2EnterpriseConfig = null;
+ mWpa3EnterpriseConfig = null;
+ mIsHiddenSSID = false;
+ }
+
+ /**
+ * Set the unicode SSID match pattern to use for filtering networks from scan results.
+ * <p>
+ * <li>Overrides any previous value set using {@link #setSsid(String)} or
+ * {@link #setSsidPattern(PatternMatcher)}.</li>
+ *
+ * @param ssidPattern Instance of {@link PatternMatcher} containing the UTF-8 encoded
+ * string pattern to use for matching the network's SSID.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ */
+ public @NonNull Builder setSsidPattern(@NonNull PatternMatcher ssidPattern) {
+ checkNotNull(ssidPattern);
+ mSsidPatternMatcher = ssidPattern;
+ return this;
+ }
+
+ /**
+ * Set the unicode SSID for the network.
+ * <p>
+ * <li>Sets the SSID to use for filtering networks from scan results. Will only match
+ * networks whose SSID is identical to the UTF-8 encoding of the specified value.</li>
+ * <li>Overrides any previous value set using {@link #setSsid(String)} or
+ * {@link #setSsidPattern(PatternMatcher)}.</li>
+ *
+ * @param ssid The SSID of the network. It must be valid Unicode.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ * @throws IllegalArgumentException if the SSID is not valid unicode.
+ */
+ public @NonNull Builder setSsid(@NonNull String ssid) {
+ checkNotNull(ssid);
+ final CharsetEncoder unicodeEncoder = StandardCharsets.UTF_8.newEncoder();
+ if (!unicodeEncoder.canEncode(ssid)) {
+ throw new IllegalArgumentException("SSID is not a valid unicode string");
+ }
+ mSsidPatternMatcher = new PatternMatcher(ssid, PatternMatcher.PATTERN_LITERAL);
+ return this;
+ }
+
+ /**
+ * Set the BSSID match pattern to use for filtering networks from scan results.
+ * Will match all networks with BSSID which satisfies the following:
+ * {@code BSSID & mask == baseAddress}.
+ * <p>
+ * <li>Overrides any previous value set using {@link #setBssid(MacAddress)} or
+ * {@link #setBssidPattern(MacAddress, MacAddress)}.</li>
+ *
+ * @param baseAddress Base address for BSSID pattern.
+ * @param mask Mask for BSSID pattern.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ */
+ public @NonNull Builder setBssidPattern(
+ @NonNull MacAddress baseAddress, @NonNull MacAddress mask) {
+ checkNotNull(baseAddress, mask);
+ mBssidPatternMatcher = Pair.create(baseAddress, mask);
+ return this;
+ }
+
+ /**
+ * Set the BSSID to use for filtering networks from scan results. Will only match network
+ * whose BSSID is identical to the specified value.
+ * <p>
+ * <li>Sets the BSSID to use for filtering networks from scan results. Will only match
+ * networks whose BSSID is identical to specified value.</li>
+ * <li>Overrides any previous value set using {@link #setBssid(MacAddress)} or
+ * {@link #setBssidPattern(MacAddress, MacAddress)}.</li>
+ *
+ * @param bssid BSSID of the network.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ */
+ public @NonNull Builder setBssid(@NonNull MacAddress bssid) {
+ checkNotNull(bssid);
+ mBssidPatternMatcher = Pair.create(bssid, MATCH_EXACT_BSSID_PATTERN_MASK);
+ return this;
+ }
+
+ /**
+ * Specifies whether this represents an Enhanced Open (OWE) network.
+ *
+ * @param isEnhancedOpen {@code true} to indicate that the network uses enhanced open,
+ * {@code false} otherwise.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ */
+ public @NonNull Builder setIsEnhancedOpen(boolean isEnhancedOpen) {
+ mIsEnhancedOpen = isEnhancedOpen;
+ return this;
+ }
+
+ /**
+ * Set the ASCII WPA2 passphrase for this network. Needed for authenticating to
+ * WPA2-PSK networks.
+ *
+ * @param passphrase passphrase of the network.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ * @throws IllegalArgumentException if the passphrase is not ASCII encodable.
+ */
+ public @NonNull Builder setWpa2Passphrase(@NonNull String passphrase) {
+ checkNotNull(passphrase);
+ final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
+ if (!asciiEncoder.canEncode(passphrase)) {
+ throw new IllegalArgumentException("passphrase not ASCII encodable");
+ }
+ mWpa2PskPassphrase = passphrase;
+ return this;
+ }
+
+ /**
+ * Set the ASCII WPA3 passphrase for this network. Needed for authenticating to WPA3-SAE
+ * networks.
+ *
+ * @param passphrase passphrase of the network.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ * @throws IllegalArgumentException if the passphrase is not ASCII encodable.
+ */
+ public @NonNull Builder setWpa3Passphrase(@NonNull String passphrase) {
+ checkNotNull(passphrase);
+ final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
+ if (!asciiEncoder.canEncode(passphrase)) {
+ throw new IllegalArgumentException("passphrase not ASCII encodable");
+ }
+ mWpa3SaePassphrase = passphrase;
+ return this;
+ }
+
+ /**
+ * Set the associated enterprise configuration for this network. Needed for authenticating
+ * to WPA2-EAP networks. See {@link WifiEnterpriseConfig} for description.
+ *
+ * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ */
+ public @NonNull Builder setWpa2EnterpriseConfig(
+ @NonNull WifiEnterpriseConfig enterpriseConfig) {
+ checkNotNull(enterpriseConfig);
+ mWpa2EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
+ return this;
+ }
+
+ /**
+ * Set the associated enterprise configuration for this network. Needed for authenticating
+ * to WPA3-SuiteB networks. See {@link WifiEnterpriseConfig} for description.
+ *
+ * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ */
+ public @NonNull Builder setWpa3EnterpriseConfig(
+ @NonNull WifiEnterpriseConfig enterpriseConfig) {
+ checkNotNull(enterpriseConfig);
+ mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
+ return this;
+ }
+
+ /**
+ * Specifies whether this represents a hidden network.
+ * <p>
+ * <li>Setting this disallows the usage of {@link #setSsidPattern(PatternMatcher)} since
+ * hidden networks need to be explicitly probed for.</li>
+ * <li>If not set, defaults to false (i.e not a hidden network).</li>
+ *
+ * @param isHiddenSsid {@code true} to indicate that the network is hidden, {@code false}
+ * otherwise.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ */
+ public @NonNull Builder setIsHiddenSsid(boolean isHiddenSsid) {
+ mIsHiddenSSID = isHiddenSsid;
+ return this;
+ }
+
+ private void setSecurityParamsInWifiConfiguration(
+ @NonNull WifiConfiguration configuration) {
+ if (!TextUtils.isEmpty(mWpa2PskPassphrase)) { // WPA-PSK network.
+ configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+ // WifiConfiguration.preSharedKey needs quotes around ASCII password.
+ configuration.preSharedKey = "\"" + mWpa2PskPassphrase + "\"";
+ } else if (!TextUtils.isEmpty(mWpa3SaePassphrase)) { // WPA3-SAE network.
+ configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
+ // WifiConfiguration.preSharedKey needs quotes around ASCII password.
+ configuration.preSharedKey = "\"" + mWpa3SaePassphrase + "\"";
+ } else if (mWpa2EnterpriseConfig != null) { // WPA-EAP network
+ configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
+ configuration.enterpriseConfig = mWpa2EnterpriseConfig;
+ } else if (mWpa3EnterpriseConfig != null) { // WPA3-SuiteB network
+ configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B);
+ configuration.enterpriseConfig = mWpa3EnterpriseConfig;
+ } else if (mIsEnhancedOpen) { // OWE network
+ configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE);
+ } else { // Open network
+ configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
+ }
+ }
+
+ /**
+ * Helper method to build WifiConfiguration object from the builder.
+ * @return Instance of {@link WifiConfiguration}.
+ */
+ private WifiConfiguration buildWifiConfiguration() {
+ final WifiConfiguration wifiConfiguration = new WifiConfiguration();
+ // WifiConfiguration.SSID needs quotes around unicode SSID.
+ if (mSsidPatternMatcher.getType() == PatternMatcher.PATTERN_LITERAL) {
+ wifiConfiguration.SSID = "\"" + mSsidPatternMatcher.getPath() + "\"";
+ }
+ if (mBssidPatternMatcher.second == MATCH_EXACT_BSSID_PATTERN_MASK) {
+ wifiConfiguration.BSSID = mBssidPatternMatcher.first.toString();
+ }
+ setSecurityParamsInWifiConfiguration(wifiConfiguration);
+ wifiConfiguration.hiddenSSID = mIsHiddenSSID;
+ return wifiConfiguration;
+ }
+
+ private boolean hasSetAnyPattern() {
+ return mSsidPatternMatcher != null || mBssidPatternMatcher != null;
+ }
+
+ private void setMatchAnyPatternIfUnset() {
+ if (mSsidPatternMatcher == null) {
+ mSsidPatternMatcher = new PatternMatcher(MATCH_ALL_SSID_PATTERN_PATH,
+ PatternMatcher.PATTERN_SIMPLE_GLOB);
+ }
+ if (mBssidPatternMatcher == null) {
+ mBssidPatternMatcher = MATCH_ALL_BSSID_PATTERN;
+ }
+ }
+
+ private boolean hasSetMatchNonePattern() {
+ if (mSsidPatternMatcher.getType() != PatternMatcher.PATTERN_PREFIX
+ && mSsidPatternMatcher.getPath().equals(MATCH_EMPTY_SSID_PATTERN_PATH)) {
+ return true;
+ }
+ if (mBssidPatternMatcher.equals(MATCH_NO_BSSID_PATTERN1)) {
+ return true;
+ }
+ if (mBssidPatternMatcher.equals(MATCH_NO_BSSID_PATTERN2)) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean hasSetMatchAllPattern() {
+ if ((mSsidPatternMatcher.match(MATCH_EMPTY_SSID_PATTERN_PATH))
+ && mBssidPatternMatcher.equals(MATCH_ALL_BSSID_PATTERN)) {
+ return true;
+ }
+ return false;
+ }
+
+ private void validateSecurityParams() {
+ int numSecurityTypes = 0;
+ numSecurityTypes += mIsEnhancedOpen ? 1 : 0;
+ numSecurityTypes += !TextUtils.isEmpty(mWpa2PskPassphrase) ? 1 : 0;
+ numSecurityTypes += !TextUtils.isEmpty(mWpa3SaePassphrase) ? 1 : 0;
+ numSecurityTypes += mWpa2EnterpriseConfig != null ? 1 : 0;
+ numSecurityTypes += mWpa3EnterpriseConfig != null ? 1 : 0;
+ if (numSecurityTypes > 1) {
+ throw new IllegalStateException("only one of setIsEnhancedOpen, setWpa2Passphrase,"
+ + "setWpa3Passphrase, setWpa2EnterpriseConfig or setWpa3EnterpriseConfig"
+ + " can be invoked for network specifier");
+ }
+ }
+
+ /**
+ * Create a specifier object used to request a local Wi-Fi network. The generated
+ * {@link NetworkSpecifier} should be used in
+ * {@link NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} when building
+ * the {@link NetworkRequest}. These specifiers can only be used to request a local wifi
+ * network (i.e no internet capability). So, the device will not switch it's default route
+ * to wifi if there are other transports (cellular for example) available.
+ *<p>
+ * Note: Apps can set a combination of network match params:
+ * <li> SSID Pattern using {@link #setSsidPattern(PatternMatcher)} OR Specific SSID using
+ * {@link #setSsid(String)}. </li>
+ * AND/OR
+ * <li> BSSID Pattern using {@link #setBssidPattern(MacAddress, MacAddress)} OR Specific
+ * BSSID using {@link #setBssid(MacAddress)} </li>
+ * to trigger connection to a network that matches the set params.
+ * The system will find the set of networks matching the request and present the user
+ * with a system dialog which will allow the user to select a specific Wi-Fi network to
+ * connect to or to deny the request.
+ *</p>
+ *
+ * For example:
+ * To connect to an open network with a SSID prefix of "test" and a BSSID OUI of "10:03:23":
+ * {@code
+ * final NetworkSpecifier specifier =
+ * new Builder()
+ * .setSsidPattern(new PatternMatcher("test", PatterMatcher.PATTERN_PREFIX))
+ * .setBssidPattern(MacAddress.fromString("10:03:23:00:00:00"),
+ * MacAddress.fromString("ff:ff:ff:00:00:00"))
+ * .build()
+ * final NetworkRequest request =
+ * new NetworkRequest.Builder()
+ * .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ * .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ * .setNetworkSpecifier(specifier)
+ * .build();
+ * final ConnectivityManager connectivityManager =
+ * context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ * final NetworkCallback networkCallback = new NetworkCallback() {
+ * ...
+ * {@literal @}Override
+ * void onAvailable(...) {}
+ * // etc.
+ * };
+ * connectivityManager.requestNetwork(request, networkCallback);
+ * }
+ *
+ * @return Instance of {@link NetworkSpecifier}.
+ * @throws IllegalStateException on invalid params set.
+ */
+ public @NonNull WifiNetworkSpecifier build() {
+ if (!hasSetAnyPattern()) {
+ throw new IllegalStateException("one of setSsidPattern/setSsid/setBssidPattern/"
+ + "setBssid should be invoked for specifier");
+ }
+ setMatchAnyPatternIfUnset();
+ if (hasSetMatchNonePattern()) {
+ throw new IllegalStateException("cannot set match-none pattern for specifier");
+ }
+ if (hasSetMatchAllPattern()) {
+ throw new IllegalStateException("cannot set match-all pattern for specifier");
+ }
+ if (mIsHiddenSSID && mSsidPatternMatcher.getType() != PatternMatcher.PATTERN_LITERAL) {
+ throw new IllegalStateException("setSsid should also be invoked when "
+ + "setIsHiddenSsid is invoked for network specifier");
+ }
+ validateSecurityParams();
+
+ return new WifiNetworkSpecifier(
+ mSsidPatternMatcher,
+ mBssidPatternMatcher,
+ buildWifiConfiguration(),
+ Process.myUid(),
+ ActivityThread.currentApplication().getApplicationContext().getOpPackageName());
+ }
+ }
+
+ /**
+ * SSID pattern match specified by the app.
+ * @hide
+ */
+ public final PatternMatcher ssidPatternMatcher;
+
+ /**
+ * BSSID pattern match specified by the app.
+ * Pair of <BaseAddress, Mask>.
+ * @hide
+ */
+ public final Pair<MacAddress, MacAddress> bssidPatternMatcher;
+
+ /**
+ * Security credentials for the network.
+ * <p>
+ * Note: {@link WifiConfiguration#SSID} & {@link WifiConfiguration#BSSID} fields from
+ * WifiConfiguration are not used. Instead we use the {@link #ssidPatternMatcher} &
+ * {@link #bssidPatternMatcher} fields embedded directly
+ * within {@link WifiNetworkSpecifier}.
+ * @hide
+ */
+ public final WifiConfiguration wifiConfiguration;
+
+ /**
+ * The UID of the process initializing this network specifier. Validated by receiver using
+ * checkUidIfNecessary() and is used by satisfiedBy() to determine whether the specifier
+ * matches the offered network.
+ * @hide
+ */
+ public final int requestorUid;
+
+ /**
+ * The package name of the app initializing this network specifier.
+ * @hide
+ */
+ public final String requestorPackageName;
+
+ /** @hide */
+ public WifiNetworkSpecifier() throws IllegalAccessException {
+ throw new IllegalAccessException("Use the builder to create an instance");
+ }
+
+ /** @hide */
+ public WifiNetworkSpecifier(@NonNull PatternMatcher ssidPatternMatcher,
+ @NonNull Pair<MacAddress, MacAddress> bssidPatternMatcher,
+ @NonNull WifiConfiguration wifiConfiguration,
+ int requestorUid, @NonNull String requestorPackageName) {
+ checkNotNull(ssidPatternMatcher);
+ checkNotNull(bssidPatternMatcher);
+ checkNotNull(wifiConfiguration);
+ checkNotNull(requestorPackageName);
+
+ this.ssidPatternMatcher = ssidPatternMatcher;
+ this.bssidPatternMatcher = bssidPatternMatcher;
+ this.wifiConfiguration = wifiConfiguration;
+ this.requestorUid = requestorUid;
+ this.requestorPackageName = requestorPackageName;
+ }
+
+ public static final @NonNull Creator<WifiNetworkSpecifier> CREATOR =
+ new Creator<WifiNetworkSpecifier>() {
+ @Override
+ public WifiNetworkSpecifier createFromParcel(Parcel in) {
+ PatternMatcher ssidPatternMatcher = in.readParcelable(/* classLoader */null);
+ MacAddress baseAddress = in.readParcelable(null);
+ MacAddress mask = in.readParcelable(null);
+ Pair<MacAddress, MacAddress> bssidPatternMatcher =
+ Pair.create(baseAddress, mask);
+ WifiConfiguration wifiConfiguration = in.readParcelable(null);
+ int requestorUid = in.readInt();
+ String requestorPackageName = in.readString();
+ return new WifiNetworkSpecifier(ssidPatternMatcher, bssidPatternMatcher,
+ wifiConfiguration, requestorUid, requestorPackageName);
+ }
+
+ @Override
+ public WifiNetworkSpecifier[] newArray(int size) {
+ return new WifiNetworkSpecifier[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(ssidPatternMatcher, flags);
+ dest.writeParcelable(bssidPatternMatcher.first, flags);
+ dest.writeParcelable(bssidPatternMatcher.second, flags);
+ dest.writeParcelable(wifiConfiguration, flags);
+ dest.writeInt(requestorUid);
+ dest.writeString(requestorPackageName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ ssidPatternMatcher.getPath(),
+ ssidPatternMatcher.getType(),
+ bssidPatternMatcher,
+ wifiConfiguration.allowedKeyManagement,
+ requestorUid, requestorPackageName);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof WifiNetworkSpecifier)) {
+ return false;
+ }
+ WifiNetworkSpecifier lhs = (WifiNetworkSpecifier) obj;
+ return Objects.equals(this.ssidPatternMatcher.getPath(),
+ lhs.ssidPatternMatcher.getPath())
+ && Objects.equals(this.ssidPatternMatcher.getType(),
+ lhs.ssidPatternMatcher.getType())
+ && Objects.equals(this.bssidPatternMatcher,
+ lhs.bssidPatternMatcher)
+ && Objects.equals(this.wifiConfiguration.allowedKeyManagement,
+ lhs.wifiConfiguration.allowedKeyManagement)
+ && requestorUid == lhs.requestorUid
+ && TextUtils.equals(requestorPackageName, lhs.requestorPackageName);
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("WifiNetworkSpecifier [")
+ .append(", SSID Match pattern=").append(ssidPatternMatcher)
+ .append(", BSSID Match pattern=").append(bssidPatternMatcher)
+ .append(", SSID=").append(wifiConfiguration.SSID)
+ .append(", BSSID=").append(wifiConfiguration.BSSID)
+ .append(", requestorUid=").append(requestorUid)
+ .append(", requestorPackageName=").append(requestorPackageName)
+ .append("]")
+ .toString();
+ }
+
+ /** @hide */
+ @Override
+ public boolean satisfiedBy(NetworkSpecifier other) {
+ if (this == other) {
+ return true;
+ }
+ // Any generic requests should be satisifed by a specific wifi network.
+ if (other == null || other instanceof MatchAllNetworkSpecifier) {
+ return true;
+ }
+ if (other instanceof WifiNetworkAgentSpecifier) {
+ return ((WifiNetworkAgentSpecifier) other).satisfiesNetworkSpecifier(this);
+ }
+ // Specific requests are checked for equality although testing for equality of 2 patterns do
+ // not make much sense!
+ return equals(other);
+ }
+
+ /** @hide */
+ @Override
+ public void assertValidFromUid(int requestorUid) {
+ if (this.requestorUid != requestorUid) {
+ throw new SecurityException("mismatched UIDs");
+ }
+ }
+}
diff --git a/android/net/wifi/WifiNetworkSuggestion.java b/android/net/wifi/WifiNetworkSuggestion.java
new file mode 100644
index 0000000..4262017
--- /dev/null
+++ b/android/net/wifi/WifiNetworkSuggestion.java
@@ -0,0 +1,569 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.net.MacAddress;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Process;
+import android.text.TextUtils;
+
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * The Network Suggestion object is used to provide a Wi-Fi network for consideration when
+ * auto-connecting to networks. Apps cannot directly create this object, they must use
+ * {@link WifiNetworkSuggestion.Builder#build()} to obtain an instance of this object.
+ *<p>
+ * Apps can provide a list of such networks to the platform using
+ * {@link WifiManager#addNetworkSuggestions(List)}.
+ */
+public final class WifiNetworkSuggestion implements Parcelable {
+
+ /**
+ * Builder used to create {@link WifiNetworkSuggestion} objects.
+ */
+ public static final class Builder {
+ private static final int UNASSIGNED_PRIORITY = -1;
+
+ /**
+ * SSID of the network.
+ */
+ private String mSsid;
+ /**
+ * Optional BSSID within the network.
+ */
+ private MacAddress mBssid;
+ /**
+ * Whether this is an OWE network or not.
+ */
+ private boolean mIsEnhancedOpen;
+ /**
+ * Pre-shared key for use with WPA-PSK networks.
+ */
+ private @Nullable String mWpa2PskPassphrase;
+ /**
+ * Pre-shared key for use with WPA3-SAE networks.
+ */
+ private @Nullable String mWpa3SaePassphrase;
+ /**
+ * The enterprise configuration details specifying the EAP method,
+ * certificates and other settings associated with the WPA-EAP networks.
+ */
+ private @Nullable WifiEnterpriseConfig mWpa2EnterpriseConfig;
+ /**
+ * The enterprise configuration details specifying the EAP method,
+ * certificates and other settings associated with the SuiteB networks.
+ */
+ private @Nullable WifiEnterpriseConfig mWpa3EnterpriseConfig;
+ /**
+ * This is a network that does not broadcast its SSID, so an
+ * SSID-specific probe request must be used for scans.
+ */
+ private boolean mIsHiddenSSID;
+ /**
+ * Whether app needs to log in to captive portal to obtain Internet access.
+ */
+ private boolean mIsAppInteractionRequired;
+ /**
+ * Whether user needs to log in to captive portal to obtain Internet access.
+ */
+ private boolean mIsUserInteractionRequired;
+ /**
+ * Whether this network is metered or not.
+ */
+ private boolean mIsMetered;
+ /**
+ * Priority of this network among other network suggestions provided by the app.
+ * The lower the number, the higher the priority (i.e value of 0 = highest priority).
+ */
+ private int mPriority;
+
+ public Builder() {
+ mSsid = null;
+ mBssid = null;
+ mIsEnhancedOpen = false;
+ mWpa2PskPassphrase = null;
+ mWpa3SaePassphrase = null;
+ mWpa2EnterpriseConfig = null;
+ mWpa3EnterpriseConfig = null;
+ mIsHiddenSSID = false;
+ mIsAppInteractionRequired = false;
+ mIsUserInteractionRequired = false;
+ mIsMetered = false;
+ mPriority = UNASSIGNED_PRIORITY;
+ }
+
+ /**
+ * Set the unicode SSID for the network.
+ * <p>
+ * <li>Overrides any previous value set using {@link #setSsid(String)}.</li>
+ *
+ * @param ssid The SSID of the network. It must be valid Unicode.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ * @throws IllegalArgumentException if the SSID is not valid unicode.
+ */
+ public @NonNull Builder setSsid(@NonNull String ssid) {
+ checkNotNull(ssid);
+ final CharsetEncoder unicodeEncoder = StandardCharsets.UTF_8.newEncoder();
+ if (!unicodeEncoder.canEncode(ssid)) {
+ throw new IllegalArgumentException("SSID is not a valid unicode string");
+ }
+ mSsid = new String(ssid);
+ return this;
+ }
+
+ /**
+ * Set the BSSID to use for filtering networks from scan results. Will only match network
+ * whose BSSID is identical to the specified value.
+ * <p>
+ * <li Sets a specific BSSID for the network suggestion. If set, only the specified BSSID
+ * with the specified SSID will be considered for connection.
+ * <li>If set, only the specified BSSID with the specified SSID will be considered for
+ * connection.</li>
+ * <li>If not set, all BSSIDs with the specified SSID will be considered for connection.
+ * </li>
+ * <li>Overrides any previous value set using {@link #setBssid(MacAddress)}.</li>
+ *
+ * @param bssid BSSID of the network.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ */
+ public @NonNull Builder setBssid(@NonNull MacAddress bssid) {
+ checkNotNull(bssid);
+ mBssid = MacAddress.fromBytes(bssid.toByteArray());
+ return this;
+ }
+
+ /**
+ * Specifies whether this represents an Enhanced Open (OWE) network.
+ *
+ * @param isEnhancedOpen {@code true} to indicate that the network used enhanced open,
+ * {@code false} otherwise.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ */
+ public @NonNull Builder setIsEnhancedOpen(boolean isEnhancedOpen) {
+ mIsEnhancedOpen = isEnhancedOpen;
+ return this;
+ }
+
+ /**
+ * Set the ASCII WPA2 passphrase for this network. Needed for authenticating to
+ * WPA2-PSK networks.
+ *
+ * @param passphrase passphrase of the network.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ * @throws IllegalArgumentException if the passphrase is not ASCII encodable.
+ */
+ public @NonNull Builder setWpa2Passphrase(@NonNull String passphrase) {
+ checkNotNull(passphrase);
+ final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
+ if (!asciiEncoder.canEncode(passphrase)) {
+ throw new IllegalArgumentException("passphrase not ASCII encodable");
+ }
+ mWpa2PskPassphrase = passphrase;
+ return this;
+ }
+
+ /**
+ * Set the ASCII WPA3 passphrase for this network. Needed for authenticating to WPA3-SAE
+ * networks.
+ *
+ * @param passphrase passphrase of the network.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ * @throws IllegalArgumentException if the passphrase is not ASCII encodable.
+ */
+ public @NonNull Builder setWpa3Passphrase(@NonNull String passphrase) {
+ checkNotNull(passphrase);
+ final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
+ if (!asciiEncoder.canEncode(passphrase)) {
+ throw new IllegalArgumentException("passphrase not ASCII encodable");
+ }
+ mWpa3SaePassphrase = passphrase;
+ return this;
+ }
+
+ /**
+ * Set the associated enterprise configuration for this network. Needed for authenticating
+ * to WPA2-EAP networks. See {@link WifiEnterpriseConfig} for description.
+ *
+ * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ */
+ public @NonNull Builder setWpa2EnterpriseConfig(
+ @NonNull WifiEnterpriseConfig enterpriseConfig) {
+ checkNotNull(enterpriseConfig);
+ mWpa2EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
+ return this;
+ }
+
+ /**
+ * Set the associated enterprise configuration for this network. Needed for authenticating
+ * to WPA3-SuiteB networks. See {@link WifiEnterpriseConfig} for description.
+ *
+ * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ */
+ public @NonNull Builder setWpa3EnterpriseConfig(
+ @NonNull WifiEnterpriseConfig enterpriseConfig) {
+ checkNotNull(enterpriseConfig);
+ mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
+ return this;
+ }
+
+ /**
+ * Specifies whether this represents a hidden network.
+ * <p>
+ * <li>If not set, defaults to false (i.e not a hidden network).</li>
+ *
+ * @param isHiddenSsid {@code true} to indicate that the network is hidden, {@code false}
+ * otherwise.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ */
+ public @NonNull Builder setIsHiddenSsid(boolean isHiddenSsid) {
+ mIsHiddenSSID = isHiddenSsid;
+ return this;
+ }
+
+ /**
+ * Specifies whether the app needs to log in to a captive portal to obtain Internet access.
+ * <p>
+ * This will dictate if the directed broadcast
+ * {@link WifiManager#ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} will be sent to the
+ * app after successfully connecting to the network.
+ * Use this for captive portal type networks where the app needs to authenticate the user
+ * before the device can access the network.
+ * <p>
+ * <li>If not set, defaults to false (i.e no app interaction required).</li>
+ *
+ * @param isAppInteractionRequired {@code true} to indicate that app interaction is
+ * required, {@code false} otherwise.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ */
+ public @NonNull Builder setIsAppInteractionRequired(boolean isAppInteractionRequired) {
+ mIsAppInteractionRequired = isAppInteractionRequired;
+ return this;
+ }
+
+ /**
+ * Specifies whether the user needs to log in to a captive portal to obtain Internet access.
+ * <p>
+ * <li>If not set, defaults to false (i.e no user interaction required).</li>
+ *
+ * @param isUserInteractionRequired {@code true} to indicate that user interaction is
+ * required, {@code false} otherwise.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ */
+ public @NonNull Builder setIsUserInteractionRequired(boolean isUserInteractionRequired) {
+ mIsUserInteractionRequired = isUserInteractionRequired;
+ return this;
+ }
+
+ /**
+ * Specify the priority of this network among other network suggestions provided by the same
+ * app (priorities have no impact on suggestions by different apps). The higher the number,
+ * the higher the priority (i.e value of 0 = lowest priority).
+ * <p>
+ * <li>If not set, defaults a lower priority than any assigned priority.</li>
+ *
+ * @param priority Integer number representing the priority among suggestions by the app.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ * @throws IllegalArgumentException if the priority value is negative.
+ */
+ public @NonNull Builder setPriority(@IntRange(from = 0) int priority) {
+ if (priority < 0) {
+ throw new IllegalArgumentException("Invalid priority value " + priority);
+ }
+ mPriority = priority;
+ return this;
+ }
+
+ /**
+ * Specifies whether this network is metered.
+ * <p>
+ * <li>If not set, defaults to false (i.e not metered).</li>
+ *
+ * @param isMetered {@code true} to indicate that the network is metered, {@code false}
+ * otherwise.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ */
+ public @NonNull Builder setIsMetered(boolean isMetered) {
+ mIsMetered = isMetered;
+ return this;
+ }
+
+ private void setSecurityParamsInWifiConfiguration(
+ @NonNull WifiConfiguration configuration) {
+ if (!TextUtils.isEmpty(mWpa2PskPassphrase)) { // WPA-PSK network.
+ configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
+ // WifiConfiguration.preSharedKey needs quotes around ASCII password.
+ configuration.preSharedKey = "\"" + mWpa2PskPassphrase + "\"";
+ } else if (!TextUtils.isEmpty(mWpa3SaePassphrase)) { // WPA3-SAE network.
+ configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
+ // WifiConfiguration.preSharedKey needs quotes around ASCII password.
+ configuration.preSharedKey = "\"" + mWpa3SaePassphrase + "\"";
+ } else if (mWpa2EnterpriseConfig != null) { // WPA-EAP network
+ configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
+ configuration.enterpriseConfig = mWpa2EnterpriseConfig;
+ } else if (mWpa3EnterpriseConfig != null) { // WPA3-SuiteB network
+ configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B);
+ configuration.enterpriseConfig = mWpa3EnterpriseConfig;
+ } else if (mIsEnhancedOpen) { // OWE network
+ configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE);
+ } else { // Open network
+ configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
+ }
+ }
+
+ /**
+ * Helper method to build WifiConfiguration object from the builder.
+ * @return Instance of {@link WifiConfiguration}.
+ */
+ private WifiConfiguration buildWifiConfiguration() {
+ final WifiConfiguration wifiConfiguration = new WifiConfiguration();
+ // WifiConfiguration.SSID needs quotes around unicode SSID.
+ wifiConfiguration.SSID = "\"" + mSsid + "\"";
+ if (mBssid != null) {
+ wifiConfiguration.BSSID = mBssid.toString();
+ }
+
+ setSecurityParamsInWifiConfiguration(wifiConfiguration);
+
+ wifiConfiguration.hiddenSSID = mIsHiddenSSID;
+ wifiConfiguration.priority = mPriority;
+ wifiConfiguration.meteredOverride =
+ mIsMetered ? WifiConfiguration.METERED_OVERRIDE_METERED
+ : WifiConfiguration.METERED_OVERRIDE_NONE;
+ return wifiConfiguration;
+ }
+
+ private void validateSecurityParams() {
+ int numSecurityTypes = 0;
+ numSecurityTypes += mIsEnhancedOpen ? 1 : 0;
+ numSecurityTypes += !TextUtils.isEmpty(mWpa2PskPassphrase) ? 1 : 0;
+ numSecurityTypes += !TextUtils.isEmpty(mWpa3SaePassphrase) ? 1 : 0;
+ numSecurityTypes += mWpa2EnterpriseConfig != null ? 1 : 0;
+ numSecurityTypes += mWpa3EnterpriseConfig != null ? 1 : 0;
+ if (numSecurityTypes > 1) {
+ throw new IllegalStateException("only one of setIsEnhancedOpen, setWpa2Passphrase,"
+ + "setWpa3Passphrase, setWpa2EnterpriseConfig or setWpa3EnterpriseConfig"
+ + " can be invoked for network specifier");
+ }
+ }
+
+ /**
+ * Create a network suggestion object for use in
+ * {@link WifiManager#addNetworkSuggestions(List)}.
+ *
+ *<p class="note">
+ * <b>Note:</b> Apps can set a combination of SSID using {@link #setSsid(String)} and BSSID
+ * using {@link #setBssid(MacAddress)} to provide more fine grained network suggestions to
+ * the platform.
+ * </p>
+ *
+ * For example:
+ * To provide credentials for one open, one WPA2 and one WPA3 network with their
+ * corresponding SSID's:
+ *
+ * <pre>{@code
+ * final WifiNetworkSuggestion suggestion1 =
+ * new Builder()
+ * .setSsid("test111111")
+ * .build()
+ * final WifiNetworkSuggestion suggestion2 =
+ * new Builder()
+ * .setSsid("test222222")
+ * .setWpa2Passphrase("test123456")
+ * .build()
+ * final WifiNetworkSuggestion suggestion3 =
+ * new Builder()
+ * .setSsid("test333333")
+ * .setWpa3Passphrase("test6789")
+ * .build()
+ * final List<WifiNetworkSuggestion> suggestionsList =
+ * new ArrayList<WifiNetworkSuggestion> { {
+ * add(suggestion1);
+ * add(suggestion2);
+ * add(suggestion3);
+ * } };
+ * final WifiManager wifiManager =
+ * context.getSystemService(Context.WIFI_SERVICE);
+ * wifiManager.addNetworkSuggestions(suggestionsList);
+ * // ...
+ * }</pre>
+ *
+ * @return Instance of {@link WifiNetworkSuggestion}
+ * @throws IllegalStateException on invalid params set
+ * @see WifiNetworkSuggestion
+ */
+ public @NonNull WifiNetworkSuggestion build() {
+ if (mSsid == null) {
+ throw new IllegalStateException("setSsid should be invoked for suggestion");
+ }
+ if (TextUtils.isEmpty(mSsid)) {
+ throw new IllegalStateException("invalid ssid for suggestion");
+ }
+ if (mBssid != null
+ && (mBssid.equals(MacAddress.BROADCAST_ADDRESS)
+ || mBssid.equals(MacAddress.ALL_ZEROS_ADDRESS))) {
+ throw new IllegalStateException("invalid bssid for suggestion");
+ }
+ validateSecurityParams();
+
+ return new WifiNetworkSuggestion(
+ buildWifiConfiguration(),
+ mIsAppInteractionRequired,
+ mIsUserInteractionRequired,
+ Process.myUid(),
+ ActivityThread.currentApplication().getApplicationContext().getOpPackageName());
+ }
+ }
+
+ /**
+ * Network configuration for the provided network.
+ * @hide
+ */
+ public final WifiConfiguration wifiConfiguration;
+
+ /**
+ * Whether app needs to log in to captive portal to obtain Internet access.
+ * @hide
+ */
+ public final boolean isAppInteractionRequired;
+
+ /**
+ * Whether user needs to log in to captive portal to obtain Internet access.
+ * @hide
+ */
+ public final boolean isUserInteractionRequired;
+
+ /**
+ * The UID of the process initializing this network suggestion.
+ * @hide
+ */
+ public final int suggestorUid;
+
+ /**
+ * The package name of the process initializing this network suggestion.
+ * @hide
+ */
+ public final String suggestorPackageName;
+
+ /** @hide */
+ public WifiNetworkSuggestion() {
+ this.wifiConfiguration = null;
+ this.isAppInteractionRequired = false;
+ this.isUserInteractionRequired = false;
+ this.suggestorUid = -1;
+ this.suggestorPackageName = null;
+ }
+
+ /** @hide */
+ public WifiNetworkSuggestion(@NonNull WifiConfiguration wifiConfiguration,
+ boolean isAppInteractionRequired,
+ boolean isUserInteractionRequired,
+ int suggestorUid, @NonNull String suggestorPackageName) {
+ checkNotNull(wifiConfiguration);
+ checkNotNull(suggestorPackageName);
+
+ this.wifiConfiguration = wifiConfiguration;
+ this.isAppInteractionRequired = isAppInteractionRequired;
+ this.isUserInteractionRequired = isUserInteractionRequired;
+ this.suggestorUid = suggestorUid;
+ this.suggestorPackageName = suggestorPackageName;
+ }
+
+ public static final @NonNull Creator<WifiNetworkSuggestion> CREATOR =
+ new Creator<WifiNetworkSuggestion>() {
+ @Override
+ public WifiNetworkSuggestion createFromParcel(Parcel in) {
+ return new WifiNetworkSuggestion(
+ in.readParcelable(null), // wifiConfiguration
+ in.readBoolean(), // isAppInteractionRequired
+ in.readBoolean(), // isUserInteractionRequired
+ in.readInt(), // suggestorUid
+ in.readString() // suggestorPackageName
+ );
+ }
+
+ @Override
+ public WifiNetworkSuggestion[] newArray(int size) {
+ return new WifiNetworkSuggestion[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(wifiConfiguration, flags);
+ dest.writeBoolean(isAppInteractionRequired);
+ dest.writeBoolean(isUserInteractionRequired);
+ dest.writeInt(suggestorUid);
+ dest.writeString(suggestorPackageName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.BSSID,
+ wifiConfiguration.allowedKeyManagement, suggestorUid, suggestorPackageName);
+ }
+
+ /**
+ * Equals for network suggestions.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof WifiNetworkSuggestion)) {
+ return false;
+ }
+ WifiNetworkSuggestion lhs = (WifiNetworkSuggestion) obj;
+ return Objects.equals(this.wifiConfiguration.SSID, lhs.wifiConfiguration.SSID)
+ && Objects.equals(this.wifiConfiguration.BSSID, lhs.wifiConfiguration.BSSID)
+ && Objects.equals(this.wifiConfiguration.allowedKeyManagement,
+ lhs.wifiConfiguration.allowedKeyManagement)
+ && suggestorUid == lhs.suggestorUid
+ && TextUtils.equals(suggestorPackageName, lhs.suggestorPackageName);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("WifiNetworkSuggestion [")
+ .append(", SSID=").append(wifiConfiguration.SSID)
+ .append(", BSSID=").append(wifiConfiguration.BSSID)
+ .append(", isAppInteractionRequired=").append(isAppInteractionRequired)
+ .append(", isUserInteractionRequired=").append(isUserInteractionRequired)
+ .append(", suggestorUid=").append(suggestorUid)
+ .append(", suggestorPackageName=").append(suggestorPackageName)
+ .append("]");
+ return sb.toString();
+ }
+}
diff --git a/android/net/wifi/WifiScanner.java b/android/net/wifi/WifiScanner.java
new file mode 100644
index 0000000..66dc992
--- /dev/null
+++ b/android/net/wifi/WifiScanner.java
@@ -0,0 +1,1450 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.Manifest;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.WorkSource;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.Protocol;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This class provides a way to scan the Wifi universe around the device
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.WIFI_SCANNING_SERVICE)
+public class WifiScanner {
+
+ /** no band specified; use channel list instead */
+ public static final int WIFI_BAND_UNSPECIFIED = 0; /* not specified */
+
+ /** 2.4 GHz band */
+ public static final int WIFI_BAND_24_GHZ = 1; /* 2.4 GHz band */
+ /** 5 GHz band excluding DFS channels */
+ public static final int WIFI_BAND_5_GHZ = 2; /* 5 GHz band without DFS channels */
+ /** DFS channels from 5 GHz band only */
+ public static final int WIFI_BAND_5_GHZ_DFS_ONLY = 4; /* 5 GHz band with DFS channels */
+ /** 5 GHz band including DFS channels */
+ public static final int WIFI_BAND_5_GHZ_WITH_DFS = 6; /* 5 GHz band with DFS channels */
+ /** Both 2.4 GHz band and 5 GHz band; no DFS channels */
+ public static final int WIFI_BAND_BOTH = 3; /* both bands without DFS channels */
+ /** Both 2.4 GHz band and 5 GHz band; with DFS channels */
+ public static final int WIFI_BAND_BOTH_WITH_DFS = 7; /* both bands with DFS channels */
+
+ /** Minimum supported scanning period */
+ public static final int MIN_SCAN_PERIOD_MS = 1000; /* minimum supported period */
+ /** Maximum supported scanning period */
+ public static final int MAX_SCAN_PERIOD_MS = 1024000; /* maximum supported period */
+
+ /** No Error */
+ public static final int REASON_SUCCEEDED = 0;
+ /** Unknown error */
+ public static final int REASON_UNSPECIFIED = -1;
+ /** Invalid listener */
+ public static final int REASON_INVALID_LISTENER = -2;
+ /** Invalid request */
+ public static final int REASON_INVALID_REQUEST = -3;
+ /** Invalid request */
+ public static final int REASON_NOT_AUTHORIZED = -4;
+ /** An outstanding request with the same listener hasn't finished yet. */
+ public static final int REASON_DUPLICATE_REQEUST = -5;
+
+ /** @hide */
+ public static final String GET_AVAILABLE_CHANNELS_EXTRA = "Channels";
+
+ /**
+ * Generic action callback invocation interface
+ * @hide
+ */
+ @SystemApi
+ public static interface ActionListener {
+ public void onSuccess();
+ public void onFailure(int reason, String description);
+ }
+
+ /**
+ * gives you all the possible channels; channel is specified as an
+ * integer with frequency in MHz i.e. channel 1 is 2412
+ * @hide
+ */
+ public List<Integer> getAvailableChannels(int band) {
+ try {
+ Bundle bundle = mService.getAvailableChannels(band);
+ return bundle.getIntegerArrayList(GET_AVAILABLE_CHANNELS_EXTRA);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * provides channel specification for scanning
+ */
+ public static class ChannelSpec {
+ /**
+ * channel frequency in MHz; for example channel 1 is specified as 2412
+ */
+ public int frequency;
+ /**
+ * if true, scan this channel in passive fashion.
+ * This flag is ignored on DFS channel specification.
+ * @hide
+ */
+ public boolean passive; /* ignored on DFS channels */
+ /**
+ * how long to dwell on this channel
+ * @hide
+ */
+ public int dwellTimeMS; /* not supported for now */
+
+ /**
+ * default constructor for channel spec
+ */
+ public ChannelSpec(int frequency) {
+ this.frequency = frequency;
+ passive = false;
+ dwellTimeMS = 0;
+ }
+ }
+
+ /**
+ * reports {@link ScanListener#onResults} when underlying buffers are full
+ * this is simply the lack of the {@link #REPORT_EVENT_AFTER_EACH_SCAN} flag
+ * @deprecated It is not supported anymore.
+ */
+ @Deprecated
+ public static final int REPORT_EVENT_AFTER_BUFFER_FULL = 0;
+ /**
+ * reports {@link ScanListener#onResults} after each scan
+ */
+ public static final int REPORT_EVENT_AFTER_EACH_SCAN = (1 << 0);
+ /**
+ * reports {@link ScanListener#onFullResult} whenever each beacon is discovered
+ */
+ public static final int REPORT_EVENT_FULL_SCAN_RESULT = (1 << 1);
+ /**
+ * Do not place scans in the chip's scan history buffer
+ */
+ public static final int REPORT_EVENT_NO_BATCH = (1 << 2);
+
+ /**
+ * This is used to indicate the purpose of the scan to the wifi chip in
+ * {@link ScanSettings#type}.
+ * On devices with multiple hardware radio chains (and hence different modes of scan),
+ * this type serves as an indication to the hardware on what mode of scan to perform.
+ * Only apps holding android.Manifest.permission.NETWORK_STACK permission can set this value.
+ *
+ * Note: This serves as an intent and not as a stipulation, the wifi chip
+ * might honor or ignore the indication based on the current radio conditions. Always
+ * use the {@link ScanResult#radioChainInfos} to figure out the radio chain configuration used
+ * to receive the corresponding scan result.
+ */
+ /** {@hide} */
+ public static final int TYPE_LOW_LATENCY = 0;
+ /** {@hide} */
+ public static final int TYPE_LOW_POWER = 1;
+ /** {@hide} */
+ public static final int TYPE_HIGH_ACCURACY = 2;
+
+ /** {@hide} */
+ public static final String SCAN_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings";
+ /** {@hide} */
+ public static final String SCAN_PARAMS_WORK_SOURCE_KEY = "WorkSource";
+ /** {@hide} */
+ public static final String REQUEST_PACKAGE_NAME_KEY = "PackageName";
+
+ /**
+ * scan configuration parameters to be sent to {@link #startBackgroundScan}
+ */
+ public static class ScanSettings implements Parcelable {
+ /**
+ * Hidden network to be scanned for.
+ * {@hide}
+ */
+ public static class HiddenNetwork {
+ /** SSID of the network */
+ public String ssid;
+
+ /**
+ * Default constructor for HiddenNetwork.
+ */
+ public HiddenNetwork(String ssid) {
+ this.ssid = ssid;
+ }
+ }
+
+ /** one of the WIFI_BAND values */
+ public int band;
+ /** list of channels; used when band is set to WIFI_BAND_UNSPECIFIED */
+ public ChannelSpec[] channels;
+ /**
+ * list of hidden networks to scan for. Explicit probe requests are sent out for such
+ * networks during scan. Only valid for single scan requests.
+ * {@hide}
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
+ public HiddenNetwork[] hiddenNetworks;
+ /** period of background scan; in millisecond, 0 => single shot scan */
+ public int periodInMs;
+ /** must have a valid REPORT_EVENT value */
+ public int reportEvents;
+ /** defines number of bssids to cache from each scan */
+ public int numBssidsPerScan;
+ /**
+ * defines number of scans to cache; use it with REPORT_EVENT_AFTER_BUFFER_FULL
+ * to wake up at fixed interval
+ */
+ public int maxScansToCache;
+ /**
+ * if maxPeriodInMs is non zero or different than period, then this bucket is
+ * a truncated binary exponential backoff bucket and the scan period will grow
+ * exponentially as per formula: actual_period(N) = period * (2 ^ (N/stepCount))
+ * to maxPeriodInMs
+ */
+ public int maxPeriodInMs;
+ /**
+ * for truncated binary exponential back off bucket, number of scans to perform
+ * for a given period
+ */
+ public int stepCount;
+ /**
+ * Flag to indicate if the scan settings are targeted for PNO scan.
+ * {@hide}
+ */
+ public boolean isPnoScan;
+ /**
+ * Indicate the type of scan to be performed by the wifi chip.
+ * Default value: {@link #TYPE_LOW_LATENCY}.
+ * {@hide}
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
+ public int type = TYPE_LOW_LATENCY;
+ /**
+ * This scan request may ignore location settings while receiving scans. This should only
+ * be used in emergency situations.
+ * {@hide}
+ */
+ @SystemApi
+ public boolean ignoreLocationSettings;
+ /**
+ * This scan request will be hidden from app-ops noting for location information. This
+ * should only be used by FLP/NLP module on the device which is using the scan results to
+ * compute results for behalf on their clients. FLP/NLP module using this flag should ensure
+ * that they note in app-ops the eventual delivery of location information computed using
+ * these results to their client .
+ * {@hide}
+ */
+ @SystemApi
+ public boolean hideFromAppOps;
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(band);
+ dest.writeInt(periodInMs);
+ dest.writeInt(reportEvents);
+ dest.writeInt(numBssidsPerScan);
+ dest.writeInt(maxScansToCache);
+ dest.writeInt(maxPeriodInMs);
+ dest.writeInt(stepCount);
+ dest.writeInt(isPnoScan ? 1 : 0);
+ dest.writeInt(type);
+ dest.writeInt(ignoreLocationSettings ? 1 : 0);
+ dest.writeInt(hideFromAppOps ? 1 : 0);
+ if (channels != null) {
+ dest.writeInt(channels.length);
+ for (int i = 0; i < channels.length; i++) {
+ dest.writeInt(channels[i].frequency);
+ dest.writeInt(channels[i].dwellTimeMS);
+ dest.writeInt(channels[i].passive ? 1 : 0);
+ }
+ } else {
+ dest.writeInt(0);
+ }
+ if (hiddenNetworks != null) {
+ dest.writeInt(hiddenNetworks.length);
+ for (int i = 0; i < hiddenNetworks.length; i++) {
+ dest.writeString(hiddenNetworks[i].ssid);
+ }
+ } else {
+ dest.writeInt(0);
+ }
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final @android.annotation.NonNull Creator<ScanSettings> CREATOR =
+ new Creator<ScanSettings>() {
+ public ScanSettings createFromParcel(Parcel in) {
+ ScanSettings settings = new ScanSettings();
+ settings.band = in.readInt();
+ settings.periodInMs = in.readInt();
+ settings.reportEvents = in.readInt();
+ settings.numBssidsPerScan = in.readInt();
+ settings.maxScansToCache = in.readInt();
+ settings.maxPeriodInMs = in.readInt();
+ settings.stepCount = in.readInt();
+ settings.isPnoScan = in.readInt() == 1;
+ settings.type = in.readInt();
+ settings.ignoreLocationSettings = in.readInt() == 1;
+ settings.hideFromAppOps = in.readInt() == 1;
+ int num_channels = in.readInt();
+ settings.channels = new ChannelSpec[num_channels];
+ for (int i = 0; i < num_channels; i++) {
+ int frequency = in.readInt();
+ ChannelSpec spec = new ChannelSpec(frequency);
+ spec.dwellTimeMS = in.readInt();
+ spec.passive = in.readInt() == 1;
+ settings.channels[i] = spec;
+ }
+ int numNetworks = in.readInt();
+ settings.hiddenNetworks = new HiddenNetwork[numNetworks];
+ for (int i = 0; i < numNetworks; i++) {
+ String ssid = in.readString();
+ settings.hiddenNetworks[i] = new HiddenNetwork(ssid);;
+ }
+ return settings;
+ }
+
+ public ScanSettings[] newArray(int size) {
+ return new ScanSettings[size];
+ }
+ };
+
+ }
+
+ /**
+ * all the information garnered from a single scan
+ */
+ public static class ScanData implements Parcelable {
+ /** scan identifier */
+ private int mId;
+ /** additional information about scan
+ * 0 => no special issues encountered in the scan
+ * non-zero => scan was truncated, so results may not be complete
+ */
+ private int mFlags;
+ /**
+ * Indicates the buckets that were scanned to generate these results.
+ * This is not relevant to WifiScanner API users and is used internally.
+ * {@hide}
+ */
+ private int mBucketsScanned;
+ /**
+ * Bands scanned. One of the WIFI_BAND values.
+ * Will be {@link #WIFI_BAND_UNSPECIFIED} if the list of channels do not fully cover
+ * any of the bands.
+ * {@hide}
+ */
+ private int mBandScanned;
+ /** all scan results discovered in this scan, sorted by timestamp in ascending order */
+ private ScanResult mResults[];
+
+ ScanData() {}
+
+ public ScanData(int id, int flags, ScanResult[] results) {
+ mId = id;
+ mFlags = flags;
+ mResults = results;
+ }
+
+ /** {@hide} */
+ public ScanData(int id, int flags, int bucketsScanned, int bandScanned,
+ ScanResult[] results) {
+ mId = id;
+ mFlags = flags;
+ mBucketsScanned = bucketsScanned;
+ mBandScanned = bandScanned;
+ mResults = results;
+ }
+
+ public ScanData(ScanData s) {
+ mId = s.mId;
+ mFlags = s.mFlags;
+ mBucketsScanned = s.mBucketsScanned;
+ mBandScanned = s.mBandScanned;
+ mResults = new ScanResult[s.mResults.length];
+ for (int i = 0; i < s.mResults.length; i++) {
+ ScanResult result = s.mResults[i];
+ ScanResult newResult = new ScanResult(result);
+ mResults[i] = newResult;
+ }
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /** {@hide} */
+ public int getBucketsScanned() {
+ return mBucketsScanned;
+ }
+
+ /** {@hide} */
+ public int getBandScanned() {
+ return mBandScanned;
+ }
+
+ public ScanResult[] getResults() {
+ return mResults;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ if (mResults != null) {
+ dest.writeInt(mId);
+ dest.writeInt(mFlags);
+ dest.writeInt(mBucketsScanned);
+ dest.writeInt(mBandScanned);
+ dest.writeInt(mResults.length);
+ for (int i = 0; i < mResults.length; i++) {
+ ScanResult result = mResults[i];
+ result.writeToParcel(dest, flags);
+ }
+ } else {
+ dest.writeInt(0);
+ }
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final @android.annotation.NonNull Creator<ScanData> CREATOR =
+ new Creator<ScanData>() {
+ public ScanData createFromParcel(Parcel in) {
+ int id = in.readInt();
+ int flags = in.readInt();
+ int bucketsScanned = in.readInt();
+ int bandScanned = in.readInt();
+ int n = in.readInt();
+ ScanResult results[] = new ScanResult[n];
+ for (int i = 0; i < n; i++) {
+ results[i] = ScanResult.CREATOR.createFromParcel(in);
+ }
+ return new ScanData(id, flags, bucketsScanned, bandScanned, results);
+ }
+
+ public ScanData[] newArray(int size) {
+ return new ScanData[size];
+ }
+ };
+ }
+
+ public static class ParcelableScanData implements Parcelable {
+
+ public ScanData mResults[];
+
+ public ParcelableScanData(ScanData[] results) {
+ mResults = results;
+ }
+
+ public ScanData[] getResults() {
+ return mResults;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ if (mResults != null) {
+ dest.writeInt(mResults.length);
+ for (int i = 0; i < mResults.length; i++) {
+ ScanData result = mResults[i];
+ result.writeToParcel(dest, flags);
+ }
+ } else {
+ dest.writeInt(0);
+ }
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final @android.annotation.NonNull Creator<ParcelableScanData> CREATOR =
+ new Creator<ParcelableScanData>() {
+ public ParcelableScanData createFromParcel(Parcel in) {
+ int n = in.readInt();
+ ScanData results[] = new ScanData[n];
+ for (int i = 0; i < n; i++) {
+ results[i] = ScanData.CREATOR.createFromParcel(in);
+ }
+ return new ParcelableScanData(results);
+ }
+
+ public ParcelableScanData[] newArray(int size) {
+ return new ParcelableScanData[size];
+ }
+ };
+ }
+
+ public static class ParcelableScanResults implements Parcelable {
+
+ public ScanResult mResults[];
+
+ public ParcelableScanResults(ScanResult[] results) {
+ mResults = results;
+ }
+
+ public ScanResult[] getResults() {
+ return mResults;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ if (mResults != null) {
+ dest.writeInt(mResults.length);
+ for (int i = 0; i < mResults.length; i++) {
+ ScanResult result = mResults[i];
+ result.writeToParcel(dest, flags);
+ }
+ } else {
+ dest.writeInt(0);
+ }
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final @android.annotation.NonNull Creator<ParcelableScanResults> CREATOR =
+ new Creator<ParcelableScanResults>() {
+ public ParcelableScanResults createFromParcel(Parcel in) {
+ int n = in.readInt();
+ ScanResult results[] = new ScanResult[n];
+ for (int i = 0; i < n; i++) {
+ results[i] = ScanResult.CREATOR.createFromParcel(in);
+ }
+ return new ParcelableScanResults(results);
+ }
+
+ public ParcelableScanResults[] newArray(int size) {
+ return new ParcelableScanResults[size];
+ }
+ };
+ }
+
+ /** {@hide} */
+ public static final String PNO_PARAMS_PNO_SETTINGS_KEY = "PnoSettings";
+ /** {@hide} */
+ public static final String PNO_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings";
+ /**
+ * PNO scan configuration parameters to be sent to {@link #startPnoScan}.
+ * Note: This structure needs to be in sync with |wifi_epno_params| struct in gscan HAL API.
+ * {@hide}
+ */
+ public static class PnoSettings implements Parcelable {
+ /**
+ * Pno network to be added to the PNO scan filtering.
+ * {@hide}
+ */
+ public static class PnoNetwork {
+ /*
+ * Pno flags bitmask to be set in {@link #PnoNetwork.flags}
+ */
+ /** Whether directed scan needs to be performed (for hidden SSIDs) */
+ public static final byte FLAG_DIRECTED_SCAN = (1 << 0);
+ /** Whether PNO event shall be triggered if the network is found on A band */
+ public static final byte FLAG_A_BAND = (1 << 1);
+ /** Whether PNO event shall be triggered if the network is found on G band */
+ public static final byte FLAG_G_BAND = (1 << 2);
+ /**
+ * Whether strict matching is required
+ * If required then the firmware must store the network's SSID and not just a hash
+ */
+ public static final byte FLAG_STRICT_MATCH = (1 << 3);
+ /**
+ * If this SSID should be considered the same network as the currently connected
+ * one for scoring.
+ */
+ public static final byte FLAG_SAME_NETWORK = (1 << 4);
+
+ /*
+ * Code for matching the beacon AUTH IE - additional codes. Bitmask to be set in
+ * {@link #PnoNetwork.authBitField}
+ */
+ /** Open Network */
+ public static final byte AUTH_CODE_OPEN = (1 << 0);
+ /** WPA_PSK or WPA2PSK */
+ public static final byte AUTH_CODE_PSK = (1 << 1);
+ /** any EAPOL */
+ public static final byte AUTH_CODE_EAPOL = (1 << 2);
+
+ /** SSID of the network */
+ public String ssid;
+ /** Bitmask of the FLAG_XXX */
+ public byte flags = 0;
+ /** Bitmask of the ATUH_XXX */
+ public byte authBitField = 0;
+ /** frequencies on which the particular network needs to be scanned for */
+ public int[] frequencies = {};
+
+ /**
+ * default constructor for PnoNetwork
+ */
+ public PnoNetwork(String ssid) {
+ this.ssid = ssid;
+ }
+ }
+
+ /** Connected vs Disconnected PNO flag {@hide} */
+ public boolean isConnected;
+ /** Minimum 5GHz RSSI for a BSSID to be considered */
+ public int min5GHzRssi;
+ /** Minimum 2.4GHz RSSI for a BSSID to be considered */
+ public int min24GHzRssi;
+ /** Maximum score that a network can have before bonuses */
+ public int initialScoreMax;
+ /**
+ * Only report when there is a network's score this much higher
+ * than the current connection.
+ */
+ public int currentConnectionBonus;
+ /** score bonus for all networks with the same network flag */
+ public int sameNetworkBonus;
+ /** score bonus for networks that are not open */
+ public int secureBonus;
+ /** 5GHz RSSI score bonus (applied to all 5GHz networks) */
+ public int band5GHzBonus;
+ /** Pno Network filter list */
+ public PnoNetwork[] networkList;
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(isConnected ? 1 : 0);
+ dest.writeInt(min5GHzRssi);
+ dest.writeInt(min24GHzRssi);
+ dest.writeInt(initialScoreMax);
+ dest.writeInt(currentConnectionBonus);
+ dest.writeInt(sameNetworkBonus);
+ dest.writeInt(secureBonus);
+ dest.writeInt(band5GHzBonus);
+ if (networkList != null) {
+ dest.writeInt(networkList.length);
+ for (int i = 0; i < networkList.length; i++) {
+ dest.writeString(networkList[i].ssid);
+ dest.writeByte(networkList[i].flags);
+ dest.writeByte(networkList[i].authBitField);
+ dest.writeIntArray(networkList[i].frequencies);
+ }
+ } else {
+ dest.writeInt(0);
+ }
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final @android.annotation.NonNull Creator<PnoSettings> CREATOR =
+ new Creator<PnoSettings>() {
+ public PnoSettings createFromParcel(Parcel in) {
+ PnoSettings settings = new PnoSettings();
+ settings.isConnected = in.readInt() == 1;
+ settings.min5GHzRssi = in.readInt();
+ settings.min24GHzRssi = in.readInt();
+ settings.initialScoreMax = in.readInt();
+ settings.currentConnectionBonus = in.readInt();
+ settings.sameNetworkBonus = in.readInt();
+ settings.secureBonus = in.readInt();
+ settings.band5GHzBonus = in.readInt();
+ int numNetworks = in.readInt();
+ settings.networkList = new PnoNetwork[numNetworks];
+ for (int i = 0; i < numNetworks; i++) {
+ String ssid = in.readString();
+ PnoNetwork network = new PnoNetwork(ssid);
+ network.flags = in.readByte();
+ network.authBitField = in.readByte();
+ network.frequencies = in.createIntArray();
+ settings.networkList[i] = network;
+ }
+ return settings;
+ }
+
+ public PnoSettings[] newArray(int size) {
+ return new PnoSettings[size];
+ }
+ };
+
+ }
+
+ /**
+ * interface to get scan events on; specify this on {@link #startBackgroundScan} or
+ * {@link #startScan}
+ */
+ public interface ScanListener extends ActionListener {
+ /**
+ * Framework co-ordinates scans across multiple apps; so it may not give exactly the
+ * same period requested. If period of a scan is changed; it is reported by this event.
+ */
+ public void onPeriodChanged(int periodInMs);
+ /**
+ * reports results retrieved from background scan and single shot scans
+ */
+ public void onResults(ScanData[] results);
+ /**
+ * reports full scan result for each access point found in scan
+ */
+ public void onFullResult(ScanResult fullScanResult);
+ }
+
+ /**
+ * interface to get PNO scan events on; specify this on {@link #startDisconnectedPnoScan} and
+ * {@link #startConnectedPnoScan}.
+ * {@hide}
+ */
+ public interface PnoScanListener extends ScanListener {
+ /**
+ * Invoked when one of the PNO networks are found in scan results.
+ */
+ void onPnoNetworkFound(ScanResult[] results);
+ }
+
+ /**
+ * Enable/Disable wifi scanning.
+ *
+ * {@hide}
+ */
+ @RequiresPermission(Manifest.permission.NETWORK_STACK)
+ public void setScanningEnabled(boolean enable) {
+ validateChannel();
+ mAsyncChannel.sendMessage(enable ? CMD_ENABLE : CMD_DISABLE);
+ }
+
+ /**
+ * Register a listener that will receive results from all single scans
+ * Either the onSuccess/onFailure will be called once when the listener is registered. After
+ * (assuming onSuccess was called) all subsequent single scan results will be delivered to the
+ * listener. It is possible that onFullResult will not be called for all results of the first
+ * scan if the listener was registered during the scan.
+ *
+ * @param listener specifies the object to report events to. This object is also treated as a
+ * key for this request, and must also be specified to cancel the request.
+ * Multiple requests should also not share this object.
+ * {@hide}
+ */
+ @RequiresPermission(Manifest.permission.NETWORK_STACK)
+ public void registerScanListener(ScanListener listener) {
+ Preconditions.checkNotNull(listener, "listener cannot be null");
+ int key = addListener(listener);
+ if (key == INVALID_KEY) return;
+ validateChannel();
+ mAsyncChannel.sendMessage(CMD_REGISTER_SCAN_LISTENER, 0, key);
+ }
+
+ /**
+ * Deregister a listener for ongoing single scans
+ * @param listener specifies which scan to cancel; must be same object as passed in {@link
+ * #registerScanListener}
+ * {@hide}
+ */
+ public void deregisterScanListener(ScanListener listener) {
+ Preconditions.checkNotNull(listener, "listener cannot be null");
+ int key = removeListener(listener);
+ if (key == INVALID_KEY) return;
+ validateChannel();
+ mAsyncChannel.sendMessage(CMD_DEREGISTER_SCAN_LISTENER, 0, key);
+ }
+
+ /** start wifi scan in background
+ * @param settings specifies various parameters for the scan; for more information look at
+ * {@link ScanSettings}
+ * @param listener specifies the object to report events to. This object is also treated as a
+ * key for this scan, and must also be specified to cancel the scan. Multiple
+ * scans should also not share this object.
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public void startBackgroundScan(ScanSettings settings, ScanListener listener) {
+ startBackgroundScan(settings, listener, null);
+ }
+
+ /** start wifi scan in background
+ * @param settings specifies various parameters for the scan; for more information look at
+ * {@link ScanSettings}
+ * @param workSource WorkSource to blame for power usage
+ * @param listener specifies the object to report events to. This object is also treated as a
+ * key for this scan, and must also be specified to cancel the scan. Multiple
+ * scans should also not share this object.
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public void startBackgroundScan(ScanSettings settings, ScanListener listener,
+ WorkSource workSource) {
+ Preconditions.checkNotNull(listener, "listener cannot be null");
+ int key = addListener(listener);
+ if (key == INVALID_KEY) return;
+ validateChannel();
+ Bundle scanParams = new Bundle();
+ scanParams.putParcelable(SCAN_PARAMS_SCAN_SETTINGS_KEY, settings);
+ scanParams.putParcelable(SCAN_PARAMS_WORK_SOURCE_KEY, workSource);
+ scanParams.putString(REQUEST_PACKAGE_NAME_KEY, mContext.getOpPackageName());
+ mAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, key, scanParams);
+ }
+
+ /**
+ * stop an ongoing wifi scan
+ * @param listener specifies which scan to cancel; must be same object as passed in {@link
+ * #startBackgroundScan}
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public void stopBackgroundScan(ScanListener listener) {
+ Preconditions.checkNotNull(listener, "listener cannot be null");
+ int key = removeListener(listener);
+ if (key == INVALID_KEY) return;
+ validateChannel();
+ Bundle scanParams = new Bundle();
+ scanParams.putString(REQUEST_PACKAGE_NAME_KEY, mContext.getOpPackageName());
+ mAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, key, scanParams);
+ }
+
+ /**
+ * reports currently available scan results on appropriate listeners
+ * @return true if all scan results were reported correctly
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public boolean getScanResults() {
+ validateChannel();
+ Bundle scanParams = new Bundle();
+ scanParams.putString(REQUEST_PACKAGE_NAME_KEY, mContext.getOpPackageName());
+ Message reply =
+ mAsyncChannel.sendMessageSynchronously(CMD_GET_SCAN_RESULTS, 0, 0, scanParams);
+ return reply.what == CMD_OP_SUCCEEDED;
+ }
+
+ /**
+ * starts a single scan and reports results asynchronously
+ * @param settings specifies various parameters for the scan; for more information look at
+ * {@link ScanSettings}
+ * @param listener specifies the object to report events to. This object is also treated as a
+ * key for this scan, and must also be specified to cancel the scan. Multiple
+ * scans should also not share this object.
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public void startScan(ScanSettings settings, ScanListener listener) {
+ startScan(settings, listener, null);
+ }
+
+ /**
+ * starts a single scan and reports results asynchronously
+ * @param settings specifies various parameters for the scan; for more information look at
+ * {@link ScanSettings}
+ * @param workSource WorkSource to blame for power usage
+ * @param listener specifies the object to report events to. This object is also treated as a
+ * key for this scan, and must also be specified to cancel the scan. Multiple
+ * scans should also not share this object.
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public void startScan(ScanSettings settings, ScanListener listener, WorkSource workSource) {
+ Preconditions.checkNotNull(listener, "listener cannot be null");
+ int key = addListener(listener);
+ if (key == INVALID_KEY) return;
+ validateChannel();
+ Bundle scanParams = new Bundle();
+ scanParams.putParcelable(SCAN_PARAMS_SCAN_SETTINGS_KEY, settings);
+ scanParams.putParcelable(SCAN_PARAMS_WORK_SOURCE_KEY, workSource);
+ scanParams.putString(REQUEST_PACKAGE_NAME_KEY, mContext.getOpPackageName());
+ mAsyncChannel.sendMessage(CMD_START_SINGLE_SCAN, 0, key, scanParams);
+ }
+
+ /**
+ * stops an ongoing single shot scan; only useful after {@link #startScan} if onResults()
+ * hasn't been called on the listener, ignored otherwise
+ * @param listener
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public void stopScan(ScanListener listener) {
+ Preconditions.checkNotNull(listener, "listener cannot be null");
+ int key = removeListener(listener);
+ if (key == INVALID_KEY) return;
+ validateChannel();
+ Bundle scanParams = new Bundle();
+ scanParams.putString(REQUEST_PACKAGE_NAME_KEY, mContext.getOpPackageName());
+ mAsyncChannel.sendMessage(CMD_STOP_SINGLE_SCAN, 0, key, scanParams);
+ }
+
+ /**
+ * Retrieve the most recent scan results from a single scan request.
+ * {@hide}
+ */
+ public List<ScanResult> getSingleScanResults() {
+ validateChannel();
+ Bundle scanParams = new Bundle();
+ scanParams.putString(REQUEST_PACKAGE_NAME_KEY, mContext.getOpPackageName());
+ Message reply = mAsyncChannel.sendMessageSynchronously(CMD_GET_SINGLE_SCAN_RESULTS, 0, 0,
+ scanParams);
+ if (reply.what == WifiScanner.CMD_OP_SUCCEEDED) {
+ return Arrays.asList(((ParcelableScanResults) reply.obj).getResults());
+ }
+ OperationResult result = (OperationResult) reply.obj;
+ Log.e(TAG, "Error retrieving SingleScan results reason: " + result.reason
+ + " description: " + result.description);
+ return new ArrayList<ScanResult>();
+ }
+
+ private void startPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, int key) {
+ // Bundle up both the settings and send it across.
+ Bundle pnoParams = new Bundle();
+ // Set the PNO scan flag.
+ scanSettings.isPnoScan = true;
+ pnoParams.putParcelable(PNO_PARAMS_SCAN_SETTINGS_KEY, scanSettings);
+ pnoParams.putParcelable(PNO_PARAMS_PNO_SETTINGS_KEY, pnoSettings);
+ mAsyncChannel.sendMessage(CMD_START_PNO_SCAN, 0, key, pnoParams);
+ }
+ /**
+ * Start wifi connected PNO scan
+ * @param scanSettings specifies various parameters for the scan; for more information look at
+ * {@link ScanSettings}
+ * @param pnoSettings specifies various parameters for PNO; for more information look at
+ * {@link PnoSettings}
+ * @param listener specifies the object to report events to. This object is also treated as a
+ * key for this scan, and must also be specified to cancel the scan. Multiple
+ * scans should also not share this object.
+ * {@hide}
+ */
+ public void startConnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
+ PnoScanListener listener) {
+ Preconditions.checkNotNull(listener, "listener cannot be null");
+ Preconditions.checkNotNull(pnoSettings, "pnoSettings cannot be null");
+ int key = addListener(listener);
+ if (key == INVALID_KEY) return;
+ validateChannel();
+ pnoSettings.isConnected = true;
+ startPnoScan(scanSettings, pnoSettings, key);
+ }
+ /**
+ * Start wifi disconnected PNO scan
+ * @param scanSettings specifies various parameters for the scan; for more information look at
+ * {@link ScanSettings}
+ * @param pnoSettings specifies various parameters for PNO; for more information look at
+ * {@link PnoSettings}
+ * @param listener specifies the object to report events to. This object is also treated as a
+ * key for this scan, and must also be specified to cancel the scan. Multiple
+ * scans should also not share this object.
+ * {@hide}
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
+ public void startDisconnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
+ PnoScanListener listener) {
+ Preconditions.checkNotNull(listener, "listener cannot be null");
+ Preconditions.checkNotNull(pnoSettings, "pnoSettings cannot be null");
+ int key = addListener(listener);
+ if (key == INVALID_KEY) return;
+ validateChannel();
+ pnoSettings.isConnected = false;
+ startPnoScan(scanSettings, pnoSettings, key);
+ }
+ /**
+ * Stop an ongoing wifi PNO scan
+ * @param listener specifies which scan to cancel; must be same object as passed in {@link
+ * #startPnoScan}
+ * {@hide}
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
+ public void stopPnoScan(ScanListener listener) {
+ Preconditions.checkNotNull(listener, "listener cannot be null");
+ int key = removeListener(listener);
+ if (key == INVALID_KEY) return;
+ validateChannel();
+ mAsyncChannel.sendMessage(CMD_STOP_PNO_SCAN, 0, key);
+ }
+
+ /** specifies information about an access point of interest */
+ @Deprecated
+ public static class BssidInfo {
+ /** bssid of the access point; in XX:XX:XX:XX:XX:XX format */
+ public String bssid;
+ /** low signal strength threshold; more information at {@link ScanResult#level} */
+ public int low; /* minimum RSSI */
+ /** high signal threshold; more information at {@link ScanResult#level} */
+ public int high; /* maximum RSSI */
+ /** channel frequency (in KHz) where you may find this BSSID */
+ public int frequencyHint;
+ }
+
+ /** @hide */
+ @SystemApi
+ @Deprecated
+ public static class WifiChangeSettings implements Parcelable {
+ public int rssiSampleSize; /* sample size for RSSI averaging */
+ public int lostApSampleSize; /* samples to confirm AP's loss */
+ public int unchangedSampleSize; /* samples to confirm no change */
+ public int minApsBreachingThreshold; /* change threshold to trigger event */
+ public int periodInMs; /* scan period in millisecond */
+ public BssidInfo[] bssidInfos;
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final @android.annotation.NonNull Creator<WifiChangeSettings> CREATOR =
+ new Creator<WifiChangeSettings>() {
+ public WifiChangeSettings createFromParcel(Parcel in) {
+ return new WifiChangeSettings();
+ }
+
+ public WifiChangeSettings[] newArray(int size) {
+ return new WifiChangeSettings[size];
+ }
+ };
+
+ }
+
+ /** configure WifiChange detection
+ * @param rssiSampleSize number of samples used for RSSI averaging
+ * @param lostApSampleSize number of samples to confirm an access point's loss
+ * @param unchangedSampleSize number of samples to confirm there are no changes
+ * @param minApsBreachingThreshold minimum number of access points that need to be
+ * out of range to detect WifiChange
+ * @param periodInMs indicates period of scan to find changes
+ * @param bssidInfos access points to watch
+ */
+ @Deprecated
+ @SuppressLint("Doclava125")
+ public void configureWifiChange(
+ int rssiSampleSize, /* sample size for RSSI averaging */
+ int lostApSampleSize, /* samples to confirm AP's loss */
+ int unchangedSampleSize, /* samples to confirm no change */
+ int minApsBreachingThreshold, /* change threshold to trigger event */
+ int periodInMs, /* period of scan */
+ BssidInfo[] bssidInfos /* signal thresholds to cross */
+ )
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * interface to get wifi change events on; use this on {@link #startTrackingWifiChange}
+ */
+ @Deprecated
+ public interface WifiChangeListener extends ActionListener {
+ /** indicates that changes were detected in wifi environment
+ * @param results indicate the access points that exhibited change
+ */
+ public void onChanging(ScanResult[] results); /* changes are found */
+ /** indicates that no wifi changes are being detected for a while
+ * @param results indicate the access points that are bing monitored for change
+ */
+ public void onQuiescence(ScanResult[] results); /* changes settled down */
+ }
+
+ /**
+ * track changes in wifi environment
+ * @param listener object to report events on; this object must be unique and must also be
+ * provided on {@link #stopTrackingWifiChange}
+ */
+ @Deprecated
+ @SuppressLint("Doclava125")
+ public void startTrackingWifiChange(WifiChangeListener listener) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * stop tracking changes in wifi environment
+ * @param listener object that was provided to report events on {@link
+ * #stopTrackingWifiChange}
+ */
+ @Deprecated
+ @SuppressLint("Doclava125")
+ public void stopTrackingWifiChange(WifiChangeListener listener) {
+ throw new UnsupportedOperationException();
+ }
+
+ /** @hide */
+ @SystemApi
+ @Deprecated
+ @SuppressLint("Doclava125")
+ public void configureWifiChange(WifiChangeSettings settings) {
+ throw new UnsupportedOperationException();
+ }
+
+ /** interface to receive hotlist events on; use this on {@link #setHotlist} */
+ @Deprecated
+ public static interface BssidListener extends ActionListener {
+ /** indicates that access points were found by on going scans
+ * @param results list of scan results, one for each access point visible currently
+ */
+ public void onFound(ScanResult[] results);
+ /** indicates that access points were missed by on going scans
+ * @param results list of scan results, for each access point that is not visible anymore
+ */
+ public void onLost(ScanResult[] results);
+ }
+
+ /** @hide */
+ @SystemApi
+ @Deprecated
+ public static class HotlistSettings implements Parcelable {
+ public BssidInfo[] bssidInfos;
+ public int apLostThreshold;
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final @android.annotation.NonNull Creator<HotlistSettings> CREATOR =
+ new Creator<HotlistSettings>() {
+ public HotlistSettings createFromParcel(Parcel in) {
+ HotlistSettings settings = new HotlistSettings();
+ return settings;
+ }
+
+ public HotlistSettings[] newArray(int size) {
+ return new HotlistSettings[size];
+ }
+ };
+ }
+
+ /**
+ * set interesting access points to find
+ * @param bssidInfos access points of interest
+ * @param apLostThreshold number of scans needed to indicate that AP is lost
+ * @param listener object provided to report events on; this object must be unique and must
+ * also be provided on {@link #stopTrackingBssids}
+ */
+ @Deprecated
+ @SuppressLint("Doclava125")
+ public void startTrackingBssids(BssidInfo[] bssidInfos,
+ int apLostThreshold, BssidListener listener) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * remove tracking of interesting access points
+ * @param listener same object provided in {@link #startTrackingBssids}
+ */
+ @Deprecated
+ @SuppressLint("Doclava125")
+ public void stopTrackingBssids(BssidListener listener) {
+ throw new UnsupportedOperationException();
+ }
+
+
+ /* private members and methods */
+
+ private static final String TAG = "WifiScanner";
+ private static final boolean DBG = false;
+
+ /* commands for Wifi Service */
+ private static final int BASE = Protocol.BASE_WIFI_SCANNER;
+
+ /** @hide */
+ public static final int CMD_START_BACKGROUND_SCAN = BASE + 2;
+ /** @hide */
+ public static final int CMD_STOP_BACKGROUND_SCAN = BASE + 3;
+ /** @hide */
+ public static final int CMD_GET_SCAN_RESULTS = BASE + 4;
+ /** @hide */
+ public static final int CMD_SCAN_RESULT = BASE + 5;
+ /** @hide */
+ public static final int CMD_OP_SUCCEEDED = BASE + 17;
+ /** @hide */
+ public static final int CMD_OP_FAILED = BASE + 18;
+ /** @hide */
+ public static final int CMD_FULL_SCAN_RESULT = BASE + 20;
+ /** @hide */
+ public static final int CMD_START_SINGLE_SCAN = BASE + 21;
+ /** @hide */
+ public static final int CMD_STOP_SINGLE_SCAN = BASE + 22;
+ /** @hide */
+ public static final int CMD_SINGLE_SCAN_COMPLETED = BASE + 23;
+ /** @hide */
+ public static final int CMD_START_PNO_SCAN = BASE + 24;
+ /** @hide */
+ public static final int CMD_STOP_PNO_SCAN = BASE + 25;
+ /** @hide */
+ public static final int CMD_PNO_NETWORK_FOUND = BASE + 26;
+ /** @hide */
+ public static final int CMD_REGISTER_SCAN_LISTENER = BASE + 27;
+ /** @hide */
+ public static final int CMD_DEREGISTER_SCAN_LISTENER = BASE + 28;
+ /** @hide */
+ public static final int CMD_GET_SINGLE_SCAN_RESULTS = BASE + 29;
+ /** @hide */
+ public static final int CMD_ENABLE = BASE + 30;
+ /** @hide */
+ public static final int CMD_DISABLE = BASE + 31;
+
+ private Context mContext;
+ private IWifiScanner mService;
+
+ private static final int INVALID_KEY = 0;
+ private int mListenerKey = 1;
+
+ private final SparseArray mListenerMap = new SparseArray();
+ private final Object mListenerMapLock = new Object();
+
+ private AsyncChannel mAsyncChannel;
+ private final Handler mInternalHandler;
+
+ /**
+ * Create a new WifiScanner instance.
+ * Applications will almost always want to use
+ * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
+ * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}.
+ * @param context the application context
+ * @param service the Binder interface
+ * @param looper the Looper used to deliver callbacks
+ * @hide
+ */
+ public WifiScanner(Context context, IWifiScanner service, Looper looper) {
+ mContext = context;
+ mService = service;
+
+ Messenger messenger = null;
+ try {
+ messenger = mService.getMessenger();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ if (messenger == null) {
+ throw new IllegalStateException("getMessenger() returned null! This is invalid.");
+ }
+
+ mAsyncChannel = new AsyncChannel();
+
+ mInternalHandler = new ServiceHandler(looper);
+ mAsyncChannel.connectSync(mContext, mInternalHandler, messenger);
+ // We cannot use fullyConnectSync because it sends the FULL_CONNECTION message
+ // synchronously, which causes WifiScanningService to receive the wrong replyTo value.
+ mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
+ }
+
+ private void validateChannel() {
+ if (mAsyncChannel == null) throw new IllegalStateException(
+ "No permission to access and change wifi or a bad initialization");
+ }
+
+ // Add a listener into listener map. If the listener already exists, return INVALID_KEY and
+ // send an error message to internal handler; Otherwise add the listener to the listener map and
+ // return the key of the listener.
+ private int addListener(ActionListener listener) {
+ synchronized (mListenerMapLock) {
+ boolean keyExists = (getListenerKey(listener) != INVALID_KEY);
+ // Note we need to put the listener into listener map even if it's a duplicate as the
+ // internal handler will need the key to find the listener. In case of duplicates,
+ // removing duplicate key logic will be handled in internal handler.
+ int key = putListener(listener);
+ if (keyExists) {
+ if (DBG) Log.d(TAG, "listener key already exists");
+ OperationResult operationResult = new OperationResult(REASON_DUPLICATE_REQEUST,
+ "Outstanding request with same key not stopped yet");
+ Message message = Message.obtain(mInternalHandler, CMD_OP_FAILED, 0, key,
+ operationResult);
+ message.sendToTarget();
+ return INVALID_KEY;
+ } else {
+ return key;
+ }
+ }
+ }
+
+ private int putListener(Object listener) {
+ if (listener == null) return INVALID_KEY;
+ int key;
+ synchronized (mListenerMapLock) {
+ do {
+ key = mListenerKey++;
+ } while (key == INVALID_KEY);
+ mListenerMap.put(key, listener);
+ }
+ return key;
+ }
+
+ private Object getListener(int key) {
+ if (key == INVALID_KEY) return null;
+ synchronized (mListenerMapLock) {
+ Object listener = mListenerMap.get(key);
+ return listener;
+ }
+ }
+
+ private int getListenerKey(Object listener) {
+ if (listener == null) return INVALID_KEY;
+ synchronized (mListenerMapLock) {
+ int index = mListenerMap.indexOfValue(listener);
+ if (index == -1) {
+ return INVALID_KEY;
+ } else {
+ return mListenerMap.keyAt(index);
+ }
+ }
+ }
+
+ private Object removeListener(int key) {
+ if (key == INVALID_KEY) return null;
+ synchronized (mListenerMapLock) {
+ Object listener = mListenerMap.get(key);
+ mListenerMap.remove(key);
+ return listener;
+ }
+ }
+
+ private int removeListener(Object listener) {
+ int key = getListenerKey(listener);
+ if (key == INVALID_KEY) {
+ Log.e(TAG, "listener cannot be found");
+ return key;
+ }
+ synchronized (mListenerMapLock) {
+ mListenerMap.remove(key);
+ return key;
+ }
+ }
+
+ /** @hide */
+ public static class OperationResult implements Parcelable {
+ public int reason;
+ public String description;
+
+ public OperationResult(int reason, String description) {
+ this.reason = reason;
+ this.description = description;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(reason);
+ dest.writeString(description);
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final @android.annotation.NonNull Creator<OperationResult> CREATOR =
+ new Creator<OperationResult>() {
+ public OperationResult createFromParcel(Parcel in) {
+ int reason = in.readInt();
+ String description = in.readString();
+ return new OperationResult(reason, description);
+ }
+
+ public OperationResult[] newArray(int size) {
+ return new OperationResult[size];
+ }
+ };
+ }
+
+ private class ServiceHandler extends Handler {
+ ServiceHandler(Looper looper) {
+ super(looper);
+ }
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
+ return;
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+ Log.e(TAG, "Channel connection lost");
+ // This will cause all further async API calls on the WifiManager
+ // to fail and throw an exception
+ mAsyncChannel = null;
+ getLooper().quit();
+ return;
+ }
+
+ Object listener = getListener(msg.arg2);
+
+ if (listener == null) {
+ if (DBG) Log.d(TAG, "invalid listener key = " + msg.arg2);
+ return;
+ } else {
+ if (DBG) Log.d(TAG, "listener key = " + msg.arg2);
+ }
+
+ switch (msg.what) {
+ /* ActionListeners grouped together */
+ case CMD_OP_SUCCEEDED :
+ ((ActionListener) listener).onSuccess();
+ break;
+ case CMD_OP_FAILED : {
+ OperationResult result = (OperationResult)msg.obj;
+ ((ActionListener) listener).onFailure(result.reason, result.description);
+ removeListener(msg.arg2);
+ }
+ break;
+ case CMD_SCAN_RESULT :
+ ((ScanListener) listener).onResults(
+ ((ParcelableScanData) msg.obj).getResults());
+ return;
+ case CMD_FULL_SCAN_RESULT :
+ ScanResult result = (ScanResult) msg.obj;
+ ((ScanListener) listener).onFullResult(result);
+ return;
+ case CMD_SINGLE_SCAN_COMPLETED:
+ if (DBG) Log.d(TAG, "removing listener for single scan");
+ removeListener(msg.arg2);
+ break;
+ case CMD_PNO_NETWORK_FOUND:
+ ((PnoScanListener) listener).onPnoNetworkFound(
+ ((ParcelableScanResults) msg.obj).getResults());
+ return;
+ default:
+ if (DBG) Log.d(TAG, "Ignoring message " + msg.what);
+ return;
+ }
+ }
+ }
+}
diff --git a/android/net/wifi/WifiSsid.java b/android/net/wifi/WifiSsid.java
new file mode 100644
index 0000000..70ca088
--- /dev/null
+++ b/android/net/wifi/WifiSsid.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2012 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.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * Stores SSID octets and handles conversion.
+ *
+ * For Ascii encoded string, any octet < 32 or > 127 is encoded as
+ * a "\x" followed by the hex representation of the octet.
+ * Exception chars are ", \, \e, \n, \r, \t which are escaped by a \
+ * See src/utils/common.c for the implementation in the supplicant.
+ *
+ * @hide
+ */
+public class WifiSsid implements Parcelable {
+ private static final String TAG = "WifiSsid";
+
+ @UnsupportedAppUsage
+ public final ByteArrayOutputStream octets = new ByteArrayOutputStream(32);
+
+ private static final int HEX_RADIX = 16;
+ @UnsupportedAppUsage
+ public static final String NONE = "<unknown ssid>";
+
+ private WifiSsid() {
+ }
+
+ public static WifiSsid createFromByteArray(byte ssid[]) {
+ WifiSsid wifiSsid = new WifiSsid();
+ if (ssid != null) {
+ wifiSsid.octets.write(ssid, 0/* the start offset */, ssid.length);;
+ }
+ return wifiSsid;
+ }
+
+ @UnsupportedAppUsage
+ public static WifiSsid createFromAsciiEncoded(String asciiEncoded) {
+ WifiSsid a = new WifiSsid();
+ a.convertToBytes(asciiEncoded);
+ return a;
+ }
+
+ public static WifiSsid createFromHex(String hexStr) {
+ WifiSsid a = new WifiSsid();
+ if (hexStr == null) return a;
+
+ if (hexStr.startsWith("0x") || hexStr.startsWith("0X")) {
+ hexStr = hexStr.substring(2);
+ }
+
+ for (int i = 0; i < hexStr.length()-1; i += 2) {
+ int val;
+ try {
+ val = Integer.parseInt(hexStr.substring(i, i + 2), HEX_RADIX);
+ } catch(NumberFormatException e) {
+ val = 0;
+ }
+ a.octets.write(val);
+ }
+ return a;
+ }
+
+ /* This function is equivalent to printf_decode() at src/utils/common.c in
+ * the supplicant */
+ private void convertToBytes(String asciiEncoded) {
+ int i = 0;
+ int val = 0;
+ while (i< asciiEncoded.length()) {
+ char c = asciiEncoded.charAt(i);
+ switch (c) {
+ case '\\':
+ i++;
+ switch(asciiEncoded.charAt(i)) {
+ case '\\':
+ octets.write('\\');
+ i++;
+ break;
+ case '"':
+ octets.write('"');
+ i++;
+ break;
+ case 'n':
+ octets.write('\n');
+ i++;
+ break;
+ case 'r':
+ octets.write('\r');
+ i++;
+ break;
+ case 't':
+ octets.write('\t');
+ i++;
+ break;
+ case 'e':
+ octets.write(27); //escape char
+ i++;
+ break;
+ case 'x':
+ i++;
+ try {
+ val = Integer.parseInt(asciiEncoded.substring(i, i + 2), HEX_RADIX);
+ } catch (NumberFormatException e) {
+ val = -1;
+ } catch (StringIndexOutOfBoundsException e) {
+ val = -1;
+ }
+ if (val < 0) {
+ val = Character.digit(asciiEncoded.charAt(i), HEX_RADIX);
+ if (val < 0) break;
+ octets.write(val);
+ i++;
+ } else {
+ octets.write(val);
+ i += 2;
+ }
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ val = asciiEncoded.charAt(i) - '0';
+ i++;
+ if (asciiEncoded.charAt(i) >= '0' && asciiEncoded.charAt(i) <= '7') {
+ val = val * 8 + asciiEncoded.charAt(i) - '0';
+ i++;
+ }
+ if (asciiEncoded.charAt(i) >= '0' && asciiEncoded.charAt(i) <= '7') {
+ val = val * 8 + asciiEncoded.charAt(i) - '0';
+ i++;
+ }
+ octets.write(val);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ octets.write(c);
+ i++;
+ break;
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ byte[] ssidBytes = octets.toByteArray();
+ // Supplicant returns \x00\x00\x00\x00\x00\x00\x00\x00 hex string
+ // for a hidden access point. Make sure we maintain the previous
+ // behavior of returning empty string for this case.
+ if (octets.size() <= 0 || isArrayAllZeroes(ssidBytes)) return "";
+ // TODO: Handle conversion to other charsets upon failure
+ Charset charset = Charset.forName("UTF-8");
+ CharsetDecoder decoder = charset.newDecoder()
+ .onMalformedInput(CodingErrorAction.REPLACE)
+ .onUnmappableCharacter(CodingErrorAction.REPLACE);
+ CharBuffer out = CharBuffer.allocate(32);
+
+ CoderResult result = decoder.decode(ByteBuffer.wrap(ssidBytes), out, true);
+ out.flip();
+ if (result.isError()) {
+ return NONE;
+ }
+ return out.toString();
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof WifiSsid)) {
+ return false;
+ }
+ WifiSsid that = (WifiSsid) thatObject;
+ return Arrays.equals(octets.toByteArray(), that.octets.toByteArray());
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(octets.toByteArray());
+ }
+
+ private boolean isArrayAllZeroes(byte[] ssidBytes) {
+ for (int i = 0; i< ssidBytes.length; i++) {
+ if (ssidBytes[i] != 0) return false;
+ }
+ return true;
+ }
+
+ /** @hide */
+ public boolean isHidden() {
+ return isArrayAllZeroes(octets.toByteArray());
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public byte[] getOctets() {
+ return octets.toByteArray();
+ }
+
+ /** @hide */
+ public String getHexString() {
+ String out = "0x";
+ byte[] ssidbytes = getOctets();
+ for (int i = 0; i < octets.size(); i++) {
+ out += String.format(Locale.US, "%02x", ssidbytes[i]);
+ }
+ return (octets.size() > 0) ? out : null;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(octets.size());
+ dest.writeByteArray(octets.toByteArray());
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ @UnsupportedAppUsage
+ public static final @android.annotation.NonNull Creator<WifiSsid> CREATOR =
+ new Creator<WifiSsid>() {
+ public WifiSsid createFromParcel(Parcel in) {
+ WifiSsid ssid = new WifiSsid();
+ int length = in.readInt();
+ byte b[] = new byte[length];
+ in.readByteArray(b);
+ ssid.octets.write(b, 0, length);
+ return ssid;
+ }
+
+ public WifiSsid[] newArray(int size) {
+ return new WifiSsid[size];
+ }
+ };
+}
diff --git a/android/net/wifi/WifiUsabilityStatsEntry.java b/android/net/wifi/WifiUsabilityStatsEntry.java
new file mode 100644
index 0000000..e595164
--- /dev/null
+++ b/android/net/wifi/WifiUsabilityStatsEntry.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.TelephonyManager.NetworkType;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class makes a subset of
+ * com.android.server.wifi.nano.WifiMetricsProto.WifiUsabilityStatsEntry parcelable.
+ *
+ * @hide
+ */
+@SystemApi
+public final class WifiUsabilityStatsEntry implements Parcelable {
+ /** {@hide} */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"PROBE_STATUS_"}, value = {
+ PROBE_STATUS_UNKNOWN,
+ PROBE_STATUS_NO_PROBE,
+ PROBE_STATUS_SUCCESS,
+ PROBE_STATUS_FAILURE})
+ public @interface ProbeStatus {}
+
+ /** Link probe status is unknown */
+ public static final int PROBE_STATUS_UNKNOWN = 0;
+ /** Link probe is not triggered */
+ public static final int PROBE_STATUS_NO_PROBE = 1;
+ /** Link probe is triggered and the result is success */
+ public static final int PROBE_STATUS_SUCCESS = 2;
+ /** Link probe is triggered and the result is failure */
+ public static final int PROBE_STATUS_FAILURE = 3;
+
+ /** Absolute milliseconds from device boot when these stats were sampled */
+ private final long mTimeStampMillis;
+ /** The RSSI (in dBm) at the sample time */
+ private final int mRssi;
+ /** Link speed at the sample time in Mbps */
+ private final int mLinkSpeedMbps;
+ /** The total number of tx success counted from the last radio chip reset */
+ private final long mTotalTxSuccess;
+ /** The total number of MPDU data packet retries counted from the last radio chip reset */
+ private final long mTotalTxRetries;
+ /** The total number of tx bad counted from the last radio chip reset */
+ private final long mTotalTxBad;
+ /** The total number of rx success counted from the last radio chip reset */
+ private final long mTotalRxSuccess;
+ /** The total time the wifi radio is on in ms counted from the last radio chip reset */
+ private final long mTotalRadioOnTimeMillis;
+ /** The total time the wifi radio is doing tx in ms counted from the last radio chip reset */
+ private final long mTotalRadioTxTimeMillis;
+ /** The total time the wifi radio is doing rx in ms counted from the last radio chip reset */
+ private final long mTotalRadioRxTimeMillis;
+ /** The total time spent on all types of scans in ms counted from the last radio chip reset */
+ private final long mTotalScanTimeMillis;
+ /** The total time spent on nan scans in ms counted from the last radio chip reset */
+ private final long mTotalNanScanTimeMillis;
+ /** The total time spent on background scans in ms counted from the last radio chip reset */
+ private final long mTotalBackgroundScanTimeMillis;
+ /** The total time spent on roam scans in ms counted from the last radio chip reset */
+ private final long mTotalRoamScanTimeMillis;
+ /** The total time spent on pno scans in ms counted from the last radio chip reset */
+ private final long mTotalPnoScanTimeMillis;
+ /** The total time spent on hotspot2.0 scans and GAS exchange in ms counted from the last radio
+ * chip reset */
+ private final long mTotalHotspot2ScanTimeMillis;
+ /** The total time CCA is on busy status on the current frequency in ms counted from the last
+ * radio chip reset */
+ private final long mTotalCcaBusyFreqTimeMillis;
+ /** The total radio on time on the current frequency from the last radio chip reset */
+ private final long mTotalRadioOnFreqTimeMillis;
+ /** The total number of beacons received from the last radio chip reset */
+ private final long mTotalBeaconRx;
+ /** The status of link probe since last stats update */
+ @ProbeStatus private final int mProbeStatusSinceLastUpdate;
+ /** The elapsed time of the most recent link probe since last stats update */
+ private final int mProbeElapsedTimeSinceLastUpdateMillis;
+ /** The MCS rate of the most recent link probe since last stats update */
+ private final int mProbeMcsRateSinceLastUpdate;
+ /** Rx link speed at the sample time in Mbps */
+ private final int mRxLinkSpeedMbps;
+ private final @NetworkType int mCellularDataNetworkType;
+ private final int mCellularSignalStrengthDbm;
+ private final int mCellularSignalStrengthDb;
+ private final boolean mIsSameRegisteredCell;
+
+ /** Constructor function {@hide} */
+ public WifiUsabilityStatsEntry(long timeStampMillis, int rssi, int linkSpeedMbps,
+ long totalTxSuccess, long totalTxRetries, long totalTxBad, long totalRxSuccess,
+ long totalRadioOnTimeMillis, long totalRadioTxTimeMillis, long totalRadioRxTimeMillis,
+ long totalScanTimeMillis, long totalNanScanTimeMillis,
+ long totalBackgroundScanTimeMillis,
+ long totalRoamScanTimeMillis, long totalPnoScanTimeMillis,
+ long totalHotspot2ScanTimeMillis,
+ long totalCcaBusyFreqTimeMillis, long totalRadioOnFreqTimeMillis, long totalBeaconRx,
+ @ProbeStatus int probeStatusSinceLastUpdate, int probeElapsedTimeSinceLastUpdateMillis,
+ int probeMcsRateSinceLastUpdate, int rxLinkSpeedMbps,
+ @NetworkType int cellularDataNetworkType,
+ int cellularSignalStrengthDbm, int cellularSignalStrengthDb,
+ boolean isSameRegisteredCell) {
+ mTimeStampMillis = timeStampMillis;
+ mRssi = rssi;
+ mLinkSpeedMbps = linkSpeedMbps;
+ mTotalTxSuccess = totalTxSuccess;
+ mTotalTxRetries = totalTxRetries;
+ mTotalTxBad = totalTxBad;
+ mTotalRxSuccess = totalRxSuccess;
+ mTotalRadioOnTimeMillis = totalRadioOnTimeMillis;
+ mTotalRadioTxTimeMillis = totalRadioTxTimeMillis;
+ mTotalRadioRxTimeMillis = totalRadioRxTimeMillis;
+ mTotalScanTimeMillis = totalScanTimeMillis;
+ mTotalNanScanTimeMillis = totalNanScanTimeMillis;
+ mTotalBackgroundScanTimeMillis = totalBackgroundScanTimeMillis;
+ mTotalRoamScanTimeMillis = totalRoamScanTimeMillis;
+ mTotalPnoScanTimeMillis = totalPnoScanTimeMillis;
+ mTotalHotspot2ScanTimeMillis = totalHotspot2ScanTimeMillis;
+ mTotalCcaBusyFreqTimeMillis = totalCcaBusyFreqTimeMillis;
+ mTotalRadioOnFreqTimeMillis = totalRadioOnFreqTimeMillis;
+ mTotalBeaconRx = totalBeaconRx;
+ mProbeStatusSinceLastUpdate = probeStatusSinceLastUpdate;
+ mProbeElapsedTimeSinceLastUpdateMillis = probeElapsedTimeSinceLastUpdateMillis;
+ mProbeMcsRateSinceLastUpdate = probeMcsRateSinceLastUpdate;
+ mRxLinkSpeedMbps = rxLinkSpeedMbps;
+ mCellularDataNetworkType = cellularDataNetworkType;
+ mCellularSignalStrengthDbm = cellularSignalStrengthDbm;
+ mCellularSignalStrengthDb = cellularSignalStrengthDb;
+ mIsSameRegisteredCell = isSameRegisteredCell;
+ }
+
+ /** Implement the Parcelable interface */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mTimeStampMillis);
+ dest.writeInt(mRssi);
+ dest.writeInt(mLinkSpeedMbps);
+ dest.writeLong(mTotalTxSuccess);
+ dest.writeLong(mTotalTxRetries);
+ dest.writeLong(mTotalTxBad);
+ dest.writeLong(mTotalRxSuccess);
+ dest.writeLong(mTotalRadioOnTimeMillis);
+ dest.writeLong(mTotalRadioTxTimeMillis);
+ dest.writeLong(mTotalRadioRxTimeMillis);
+ dest.writeLong(mTotalScanTimeMillis);
+ dest.writeLong(mTotalNanScanTimeMillis);
+ dest.writeLong(mTotalBackgroundScanTimeMillis);
+ dest.writeLong(mTotalRoamScanTimeMillis);
+ dest.writeLong(mTotalPnoScanTimeMillis);
+ dest.writeLong(mTotalHotspot2ScanTimeMillis);
+ dest.writeLong(mTotalCcaBusyFreqTimeMillis);
+ dest.writeLong(mTotalRadioOnFreqTimeMillis);
+ dest.writeLong(mTotalBeaconRx);
+ dest.writeInt(mProbeStatusSinceLastUpdate);
+ dest.writeInt(mProbeElapsedTimeSinceLastUpdateMillis);
+ dest.writeInt(mProbeMcsRateSinceLastUpdate);
+ dest.writeInt(mRxLinkSpeedMbps);
+ dest.writeInt(mCellularDataNetworkType);
+ dest.writeInt(mCellularSignalStrengthDbm);
+ dest.writeInt(mCellularSignalStrengthDb);
+ dest.writeBoolean(mIsSameRegisteredCell);
+ }
+
+ /** Implement the Parcelable interface */
+ public static final @android.annotation.NonNull Creator<WifiUsabilityStatsEntry> CREATOR =
+ new Creator<WifiUsabilityStatsEntry>() {
+ public WifiUsabilityStatsEntry createFromParcel(Parcel in) {
+ return new WifiUsabilityStatsEntry(
+ in.readLong(), in.readInt(),
+ in.readInt(), in.readLong(), in.readLong(),
+ in.readLong(), in.readLong(), in.readLong(),
+ in.readLong(), in.readLong(), in.readLong(),
+ in.readLong(), in.readLong(), in.readLong(),
+ in.readLong(), in.readLong(), in.readLong(),
+ in.readLong(), in.readLong(), in.readInt(),
+ in.readInt(), in.readInt(), in.readInt(),
+ in.readInt(), in.readInt(), in.readInt(),
+ in.readBoolean()
+ );
+ }
+
+ public WifiUsabilityStatsEntry[] newArray(int size) {
+ return new WifiUsabilityStatsEntry[size];
+ }
+ };
+
+ /** Absolute milliseconds from device boot when these stats were sampled */
+ public long getTimeStampMillis() {
+ return mTimeStampMillis;
+ }
+
+ /** The RSSI (in dBm) at the sample time */
+ public int getRssi() {
+ return mRssi;
+ }
+
+ /** Link speed at the sample time in Mbps */
+ public int getLinkSpeedMbps() {
+ return mLinkSpeedMbps;
+ }
+
+ /** The total number of tx success counted from the last radio chip reset */
+ public long getTotalTxSuccess() {
+ return mTotalTxSuccess;
+ }
+
+ /** The total number of MPDU data packet retries counted from the last radio chip reset */
+ public long getTotalTxRetries() {
+ return mTotalTxRetries;
+ }
+
+ /** The total number of tx bad counted from the last radio chip reset */
+ public long getTotalTxBad() {
+ return mTotalTxBad;
+ }
+
+ /** The total number of rx success counted from the last radio chip reset */
+ public long getTotalRxSuccess() {
+ return mTotalRxSuccess;
+ }
+
+ /** The total time the wifi radio is on in ms counted from the last radio chip reset */
+ public long getTotalRadioOnTimeMillis() {
+ return mTotalRadioOnTimeMillis;
+ }
+
+ /** The total time the wifi radio is doing tx in ms counted from the last radio chip reset */
+ public long getTotalRadioTxTimeMillis() {
+ return mTotalRadioTxTimeMillis;
+ }
+
+ /** The total time the wifi radio is doing rx in ms counted from the last radio chip reset */
+ public long getTotalRadioRxTimeMillis() {
+ return mTotalRadioRxTimeMillis;
+ }
+
+ /** The total time spent on all types of scans in ms counted from the last radio chip reset */
+ public long getTotalScanTimeMillis() {
+ return mTotalScanTimeMillis;
+ }
+
+ /** The total time spent on nan scans in ms counted from the last radio chip reset */
+ public long getTotalNanScanTimeMillis() {
+ return mTotalNanScanTimeMillis;
+ }
+
+ /** The total time spent on background scans in ms counted from the last radio chip reset */
+ public long getTotalBackgroundScanTimeMillis() {
+ return mTotalBackgroundScanTimeMillis;
+ }
+
+ /** The total time spent on roam scans in ms counted from the last radio chip reset */
+ public long getTotalRoamScanTimeMillis() {
+ return mTotalRoamScanTimeMillis;
+ }
+
+ /** The total time spent on pno scans in ms counted from the last radio chip reset */
+ public long getTotalPnoScanTimeMillis() {
+ return mTotalPnoScanTimeMillis;
+ }
+
+ /** The total time spent on hotspot2.0 scans and GAS exchange in ms counted from the last radio
+ * chip reset */
+ public long getTotalHotspot2ScanTimeMillis() {
+ return mTotalHotspot2ScanTimeMillis;
+ }
+
+ /** The total time CCA is on busy status on the current frequency in ms counted from the last
+ * radio chip reset */
+ public long getTotalCcaBusyFreqTimeMillis() {
+ return mTotalCcaBusyFreqTimeMillis;
+ }
+
+ /** The total radio on time on the current frequency from the last radio chip reset */
+ public long getTotalRadioOnFreqTimeMillis() {
+ return mTotalRadioOnFreqTimeMillis;
+ }
+
+ /** The total number of beacons received from the last radio chip reset */
+ public long getTotalBeaconRx() {
+ return mTotalBeaconRx;
+ }
+
+ /** The status of link probe since last stats update */
+ @ProbeStatus public int getProbeStatusSinceLastUpdate() {
+ return mProbeStatusSinceLastUpdate;
+ }
+
+ /** The elapsed time of the most recent link probe since last stats update */
+ public int getProbeElapsedTimeSinceLastUpdateMillis() {
+ return mProbeElapsedTimeSinceLastUpdateMillis;
+ }
+
+ /** The MCS rate of the most recent link probe since last stats update */
+ public int getProbeMcsRateSinceLastUpdate() {
+ return mProbeMcsRateSinceLastUpdate;
+ }
+
+ /** Rx link speed at the sample time in Mbps */
+ public int getRxLinkSpeedMbps() {
+ return mRxLinkSpeedMbps;
+ }
+
+ /** Cellular data network type currently in use on the device for data transmission */
+ @NetworkType public int getCellularDataNetworkType() {
+ return mCellularDataNetworkType;
+ }
+
+ /**
+ * Cellular signal strength in dBm, NR: CsiRsrp, LTE: Rsrp, WCDMA/TDSCDMA: Rscp,
+ * CDMA: Rssi, EVDO: Rssi, GSM: Rssi
+ */
+ public int getCellularSignalStrengthDbm() {
+ return mCellularSignalStrengthDbm;
+ }
+
+ /**
+ * Cellular signal strength in dB, NR: CsiSinr, LTE: Rsrq, WCDMA: EcNo, TDSCDMA: invalid,
+ * CDMA: Ecio, EVDO: SNR, GSM: invalid
+ */
+ public int getCellularSignalStrengthDb() {
+ return mCellularSignalStrengthDb;
+ }
+
+ /** Whether the primary registered cell of current entry is same as that of previous entry */
+ public boolean isSameRegisteredCell() {
+ return mIsSameRegisteredCell;
+ }
+}
diff --git a/android/net/wifi/WpsInfo.java b/android/net/wifi/WpsInfo.java
new file mode 100644
index 0000000..00cb243
--- /dev/null
+++ b/android/net/wifi/WpsInfo.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2011 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class representing Wi-Fi Protected Setup
+ *
+ * {@see WifiP2pConfig}
+ */
+public class WpsInfo implements Parcelable {
+
+ /** Push button configuration */
+ public static final int PBC = 0;
+ /** Display pin method configuration - pin is generated and displayed on device */
+ public static final int DISPLAY = 1;
+ /** Keypad pin method configuration - pin is entered on device */
+ public static final int KEYPAD = 2;
+ /** Label pin method configuration - pin is labelled on device */
+ public static final int LABEL = 3;
+ /** Invalid configuration */
+ public static final int INVALID = 4;
+
+ /** Wi-Fi Protected Setup. www.wi-fi.org/wifi-protected-setup has details */
+ public int setup;
+
+ /** Passed with pin method KEYPAD */
+ public String BSSID;
+
+ /** Passed with pin method configuration */
+ public String pin;
+
+ public WpsInfo() {
+ setup = INVALID;
+ BSSID = null;
+ pin = null;
+ }
+
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append(" setup: ").append(setup);
+ sbuf.append('\n');
+ sbuf.append(" BSSID: ").append(BSSID);
+ sbuf.append('\n');
+ sbuf.append(" pin: ").append(pin);
+ sbuf.append('\n');
+ return sbuf.toString();
+ }
+
+ /** Implement the Parcelable interface */
+ public int describeContents() {
+ return 0;
+ }
+
+ /* Copy constructor */
+ public WpsInfo(WpsInfo source) {
+ if (source != null) {
+ setup = source.setup;
+ BSSID = source.BSSID;
+ pin = source.pin;
+ }
+ }
+
+ /** Implement the Parcelable interface */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(setup);
+ dest.writeString(BSSID);
+ dest.writeString(pin);
+ }
+
+ /** Implement the Parcelable interface */
+ public static final @android.annotation.NonNull Creator<WpsInfo> CREATOR =
+ new Creator<WpsInfo>() {
+ public WpsInfo createFromParcel(Parcel in) {
+ WpsInfo config = new WpsInfo();
+ config.setup = in.readInt();
+ config.BSSID = in.readString();
+ config.pin = in.readString();
+ return config;
+ }
+
+ public WpsInfo[] newArray(int size) {
+ return new WpsInfo[size];
+ }
+ };
+}
diff --git a/android/net/wifi/WpsResult.java b/android/net/wifi/WpsResult.java
new file mode 100644
index 0000000..f2ffb6f
--- /dev/null
+++ b/android/net/wifi/WpsResult.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2010 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class representing the result of a WPS request
+ * @hide
+ */
+public class WpsResult implements Parcelable {
+
+ public enum Status {
+ SUCCESS,
+ FAILURE,
+ IN_PROGRESS,
+ }
+
+ public Status status;
+
+ public String pin;
+
+ public WpsResult() {
+ status = Status.FAILURE;
+ pin = null;
+ }
+
+ public WpsResult(Status s) {
+ status = s;
+ pin = null;
+ }
+
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append(" status: ").append(status.toString());
+ sbuf.append('\n');
+ sbuf.append(" pin: ").append(pin);
+ sbuf.append("\n");
+ return sbuf.toString();
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** copy constructor {@hide} */
+ public WpsResult(WpsResult source) {
+ if (source != null) {
+ status = source.status;
+ pin = source.pin;
+ }
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(status.name());
+ dest.writeString(pin);
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final @android.annotation.NonNull Creator<WpsResult> CREATOR =
+ new Creator<WpsResult>() {
+ public WpsResult createFromParcel(Parcel in) {
+ WpsResult result = new WpsResult();
+ result.status = Status.valueOf(in.readString());
+ result.pin = in.readString();
+ return result;
+ }
+
+ public WpsResult[] newArray(int size) {
+ return new WpsResult[size];
+ }
+ };
+}
diff --git a/android/net/wifi/aware/AttachCallback.java b/android/net/wifi/aware/AttachCallback.java
new file mode 100644
index 0000000..c368b46
--- /dev/null
+++ b/android/net/wifi/aware/AttachCallback.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 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.aware;
+
+/**
+ * Base class for Aware attach callbacks. Should be extended by applications and set when calling
+ * {@link WifiAwareManager#attach(AttachCallback, android.os.Handler)}. These are callbacks
+ * applying to the Aware connection as a whole - not to specific publish or subscribe sessions -
+ * for that see {@link DiscoverySessionCallback}.
+ */
+public class AttachCallback {
+ /**
+ * Called when Aware attach operation
+ * {@link WifiAwareManager#attach(AttachCallback, android.os.Handler)}
+ * is completed and that we can now start discovery sessions or connections.
+ *
+ * @param session The Aware object on which we can execute further Aware operations - e.g.
+ * discovery, connections.
+ */
+ public void onAttached(WifiAwareSession session) {
+ /* empty */
+ }
+
+ /**
+ * Called when Aware attach operation
+ * {@link WifiAwareManager#attach(AttachCallback, android.os.Handler)} failed.
+ */
+ public void onAttachFailed() {
+ /* empty */
+ }
+}
diff --git a/android/net/wifi/aware/Characteristics.java b/android/net/wifi/aware/Characteristics.java
new file mode 100644
index 0000000..e2cf4dc
--- /dev/null
+++ b/android/net/wifi/aware/Characteristics.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2016 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.aware;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The characteristics of the Wi-Fi Aware implementation.
+ */
+public final class Characteristics implements Parcelable {
+ /** @hide */
+ public static final String KEY_MAX_SERVICE_NAME_LENGTH = "key_max_service_name_length";
+ /** @hide */
+ public static final String KEY_MAX_SERVICE_SPECIFIC_INFO_LENGTH =
+ "key_max_service_specific_info_length";
+ /** @hide */
+ public static final String KEY_MAX_MATCH_FILTER_LENGTH = "key_max_match_filter_length";
+
+ private Bundle mCharacteristics = new Bundle();
+
+ /** @hide : should not be created by apps */
+ public Characteristics(Bundle characteristics) {
+ mCharacteristics = characteristics;
+ }
+
+ /**
+ * Returns the maximum string length that can be used to specify a Aware service name. Restricts
+ * the parameters of the {@link PublishConfig.Builder#setServiceName(String)} and
+ * {@link SubscribeConfig.Builder#setServiceName(String)}.
+ *
+ * @return A positive integer, maximum string length of Aware service name.
+ */
+ public int getMaxServiceNameLength() {
+ return mCharacteristics.getInt(KEY_MAX_SERVICE_NAME_LENGTH);
+ }
+
+ /**
+ * Returns the maximum length of byte array that can be used to specify a Aware service specific
+ * information field: the arbitrary load used in discovery or the message length of Aware
+ * message exchange. Restricts the parameters of the
+ * {@link PublishConfig.Builder#setServiceSpecificInfo(byte[])},
+ * {@link SubscribeConfig.Builder#setServiceSpecificInfo(byte[])}, and
+ * {@link DiscoverySession#sendMessage(PeerHandle, int, byte[])}
+ * variants.
+ *
+ * @return A positive integer, maximum length of byte array for Aware messaging.
+ */
+ public int getMaxServiceSpecificInfoLength() {
+ return mCharacteristics.getInt(KEY_MAX_SERVICE_SPECIFIC_INFO_LENGTH);
+ }
+
+ /**
+ * Returns the maximum length of byte array that can be used to specify a Aware match filter.
+ * Restricts the parameters of the
+ * {@link PublishConfig.Builder#setMatchFilter(java.util.List)} and
+ * {@link SubscribeConfig.Builder#setMatchFilter(java.util.List)}.
+ *
+ * @return A positive integer, maximum legngth of byte array for Aware discovery match filter.
+ */
+ public int getMaxMatchFilterLength() {
+ return mCharacteristics.getInt(KEY_MAX_MATCH_FILTER_LENGTH);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBundle(mCharacteristics);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @android.annotation.NonNull Creator<Characteristics> CREATOR =
+ new Creator<Characteristics>() {
+ @Override
+ public Characteristics createFromParcel(Parcel in) {
+ Characteristics c = new Characteristics(in.readBundle());
+ return c;
+ }
+
+ @Override
+ public Characteristics[] newArray(int size) {
+ return new Characteristics[size];
+ }
+ };
+}
diff --git a/android/net/wifi/aware/ConfigRequest.java b/android/net/wifi/aware/ConfigRequest.java
new file mode 100644
index 0000000..b07d8ed
--- /dev/null
+++ b/android/net/wifi/aware/ConfigRequest.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2016 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.aware;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * Defines a request object to configure a Wi-Fi Aware network. Built using
+ * {@link ConfigRequest.Builder}. Configuration is requested using
+ * {@link WifiAwareManager#attach(AttachCallback, android.os.Handler)}.
+ * Note that the actual achieved configuration may be different from the
+ * requested configuration - since different applications may request different
+ * configurations.
+ *
+ * @hide
+ */
+public final class ConfigRequest implements Parcelable {
+ /**
+ * Lower range of possible cluster ID.
+ */
+ public static final int CLUSTER_ID_MIN = 0;
+
+ /**
+ * Upper range of possible cluster ID.
+ */
+ public static final int CLUSTER_ID_MAX = 0xFFFF;
+
+ /**
+ * Indices for configuration variables which are specified per band.
+ */
+ public static final int NAN_BAND_24GHZ = 0;
+ public static final int NAN_BAND_5GHZ = 1;
+
+ /**
+ * Magic values for Discovery Window (DW) interval configuration
+ */
+ public static final int DW_INTERVAL_NOT_INIT = -1;
+ public static final int DW_DISABLE = 0; // only valid for 5GHz
+
+ /**
+ * Indicates whether 5G band support is requested.
+ */
+ public final boolean mSupport5gBand;
+
+ /**
+ * Specifies the desired master preference.
+ */
+ public final int mMasterPreference;
+
+ /**
+ * Specifies the desired lower range of the cluster ID. Must be lower then
+ * {@link ConfigRequest#mClusterHigh}.
+ */
+ public final int mClusterLow;
+
+ /**
+ * Specifies the desired higher range of the cluster ID. Must be higher then
+ * {@link ConfigRequest#mClusterLow}.
+ */
+ public final int mClusterHigh;
+
+ /**
+ * Specifies the discovery window interval for the device on NAN_BAND_*.
+ */
+ public final int mDiscoveryWindowInterval[];
+
+ private ConfigRequest(boolean support5gBand, int masterPreference, int clusterLow,
+ int clusterHigh, int discoveryWindowInterval[]) {
+ mSupport5gBand = support5gBand;
+ mMasterPreference = masterPreference;
+ mClusterLow = clusterLow;
+ mClusterHigh = clusterHigh;
+ mDiscoveryWindowInterval = discoveryWindowInterval;
+ }
+
+ @Override
+ public String toString() {
+ return "ConfigRequest [mSupport5gBand=" + mSupport5gBand + ", mMasterPreference="
+ + mMasterPreference + ", mClusterLow=" + mClusterLow + ", mClusterHigh="
+ + mClusterHigh + ", mDiscoveryWindowInterval="
+ + Arrays.toString(mDiscoveryWindowInterval) + "]";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mSupport5gBand ? 1 : 0);
+ dest.writeInt(mMasterPreference);
+ dest.writeInt(mClusterLow);
+ dest.writeInt(mClusterHigh);
+ dest.writeIntArray(mDiscoveryWindowInterval);
+ }
+
+ public static final @android.annotation.NonNull Creator<ConfigRequest> CREATOR = new Creator<ConfigRequest>() {
+ @Override
+ public ConfigRequest[] newArray(int size) {
+ return new ConfigRequest[size];
+ }
+
+ @Override
+ public ConfigRequest createFromParcel(Parcel in) {
+ boolean support5gBand = in.readInt() != 0;
+ int masterPreference = in.readInt();
+ int clusterLow = in.readInt();
+ int clusterHigh = in.readInt();
+ int discoveryWindowInterval[] = in.createIntArray();
+
+ return new ConfigRequest(support5gBand, masterPreference, clusterLow, clusterHigh,
+ discoveryWindowInterval);
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof ConfigRequest)) {
+ return false;
+ }
+
+ ConfigRequest lhs = (ConfigRequest) o;
+
+ return mSupport5gBand == lhs.mSupport5gBand && mMasterPreference == lhs.mMasterPreference
+ && mClusterLow == lhs.mClusterLow && mClusterHigh == lhs.mClusterHigh
+ && Arrays.equals(mDiscoveryWindowInterval, lhs.mDiscoveryWindowInterval);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+
+ result = 31 * result + (mSupport5gBand ? 1 : 0);
+ result = 31 * result + mMasterPreference;
+ result = 31 * result + mClusterLow;
+ result = 31 * result + mClusterHigh;
+ result = 31 * result + Arrays.hashCode(mDiscoveryWindowInterval);
+
+ return result;
+ }
+
+ /**
+ * Verifies that the contents of the ConfigRequest are valid. Otherwise
+ * throws an IllegalArgumentException.
+ */
+ public void validate() throws IllegalArgumentException {
+ if (mMasterPreference < 0) {
+ throw new IllegalArgumentException(
+ "Master Preference specification must be non-negative");
+ }
+ if (mMasterPreference == 1 || mMasterPreference == 255 || mMasterPreference > 255) {
+ throw new IllegalArgumentException("Master Preference specification must not "
+ + "exceed 255 or use 1 or 255 (reserved values)");
+ }
+ if (mClusterLow < CLUSTER_ID_MIN) {
+ throw new IllegalArgumentException("Cluster specification must be non-negative");
+ }
+ if (mClusterLow > CLUSTER_ID_MAX) {
+ throw new IllegalArgumentException("Cluster specification must not exceed 0xFFFF");
+ }
+ if (mClusterHigh < CLUSTER_ID_MIN) {
+ throw new IllegalArgumentException("Cluster specification must be non-negative");
+ }
+ if (mClusterHigh > CLUSTER_ID_MAX) {
+ throw new IllegalArgumentException("Cluster specification must not exceed 0xFFFF");
+ }
+ if (mClusterLow > mClusterHigh) {
+ throw new IllegalArgumentException(
+ "Invalid argument combination - must have Cluster Low <= Cluster High");
+ }
+ if (mDiscoveryWindowInterval.length != 2) {
+ throw new IllegalArgumentException(
+ "Invalid discovery window interval: must have 2 elements (2.4 & 5");
+ }
+ if (mDiscoveryWindowInterval[NAN_BAND_24GHZ] != DW_INTERVAL_NOT_INIT &&
+ (mDiscoveryWindowInterval[NAN_BAND_24GHZ] < 1 // valid for 2.4GHz: [1-5]
+ || mDiscoveryWindowInterval[NAN_BAND_24GHZ] > 5)) {
+ throw new IllegalArgumentException(
+ "Invalid discovery window interval for 2.4GHz: valid is UNSET or [1,5]");
+ }
+ if (mDiscoveryWindowInterval[NAN_BAND_5GHZ] != DW_INTERVAL_NOT_INIT &&
+ (mDiscoveryWindowInterval[NAN_BAND_5GHZ] < 0 // valid for 5GHz: [0-5]
+ || mDiscoveryWindowInterval[NAN_BAND_5GHZ] > 5)) {
+ throw new IllegalArgumentException(
+ "Invalid discovery window interval for 5GHz: valid is UNSET or [0,5]");
+ }
+
+ }
+
+ /**
+ * Builder used to build {@link ConfigRequest} objects.
+ */
+ public static final class Builder {
+ private boolean mSupport5gBand = true;
+ private int mMasterPreference = 0;
+ private int mClusterLow = CLUSTER_ID_MIN;
+ private int mClusterHigh = CLUSTER_ID_MAX;
+ private int mDiscoveryWindowInterval[] = {DW_INTERVAL_NOT_INIT, DW_INTERVAL_NOT_INIT};
+
+ /**
+ * Specify whether 5G band support is required in this request. Disabled by default.
+ *
+ * @param support5gBand Support for 5G band is required.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setSupport5gBand(boolean support5gBand) {
+ mSupport5gBand = support5gBand;
+ return this;
+ }
+
+ /**
+ * Specify the Master Preference requested. The permitted range is 0 (the default) to
+ * 255 with 1 and 255 excluded (reserved).
+ *
+ * @param masterPreference The requested master preference
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setMasterPreference(int masterPreference) {
+ if (masterPreference < 0) {
+ throw new IllegalArgumentException(
+ "Master Preference specification must be non-negative");
+ }
+ if (masterPreference == 1 || masterPreference == 255 || masterPreference > 255) {
+ throw new IllegalArgumentException("Master Preference specification must not "
+ + "exceed 255 or use 1 or 255 (reserved values)");
+ }
+
+ mMasterPreference = masterPreference;
+ return this;
+ }
+
+ /**
+ * The Cluster ID is generated randomly for new Aware networks. Specify
+ * the lower range of the cluster ID. The upper range is specified using
+ * the {@link ConfigRequest.Builder#setClusterHigh(int)}. The permitted
+ * range is 0 (the default) to the value specified by
+ * {@link ConfigRequest.Builder#setClusterHigh(int)}. Equality of Low and High is
+ * permitted which restricts the Cluster ID to the specified value.
+ *
+ * @param clusterLow The lower range of the generated cluster ID.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setClusterLow(..).setClusterHigh(..)}.
+ */
+ public Builder setClusterLow(int clusterLow) {
+ if (clusterLow < CLUSTER_ID_MIN) {
+ throw new IllegalArgumentException("Cluster specification must be non-negative");
+ }
+ if (clusterLow > CLUSTER_ID_MAX) {
+ throw new IllegalArgumentException("Cluster specification must not exceed 0xFFFF");
+ }
+
+ mClusterLow = clusterLow;
+ return this;
+ }
+
+ /**
+ * The Cluster ID is generated randomly for new Aware networks. Specify
+ * the lower upper of the cluster ID. The lower range is specified using
+ * the {@link ConfigRequest.Builder#setClusterLow(int)}. The permitted
+ * range is the value specified by
+ * {@link ConfigRequest.Builder#setClusterLow(int)} to 0xFFFF (the default). Equality of
+ * Low and High is permitted which restricts the Cluster ID to the specified value.
+ *
+ * @param clusterHigh The upper range of the generated cluster ID.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setClusterLow(..).setClusterHigh(..)}.
+ */
+ public Builder setClusterHigh(int clusterHigh) {
+ if (clusterHigh < CLUSTER_ID_MIN) {
+ throw new IllegalArgumentException("Cluster specification must be non-negative");
+ }
+ if (clusterHigh > CLUSTER_ID_MAX) {
+ throw new IllegalArgumentException("Cluster specification must not exceed 0xFFFF");
+ }
+
+ mClusterHigh = clusterHigh;
+ return this;
+ }
+
+ /**
+ * The discovery window interval specifies the discovery windows in which the device will be
+ * awake. The configuration enables trading off latency vs. power (higher interval means
+ * higher discovery latency but lower power).
+ *
+ * @param band Either {@link #NAN_BAND_24GHZ} or {@link #NAN_BAND_5GHZ}.
+ * @param interval A value of 1, 2, 3, 4, or 5 indicating an interval of 2^(interval-1). For
+ * the 5GHz band a value of 0 indicates that the device will not be awake
+ * for any discovery windows.
+ *
+ * @return The builder itself to facilitate chaining operations
+ * {@code builder.setDiscoveryWindowInterval(...).setMasterPreference(...)}.
+ */
+ public Builder setDiscoveryWindowInterval(int band, int interval) {
+ if (band != NAN_BAND_24GHZ && band != NAN_BAND_5GHZ) {
+ throw new IllegalArgumentException("Invalid band value");
+ }
+ if ((band == NAN_BAND_24GHZ && (interval < 1 || interval > 5))
+ || (band == NAN_BAND_5GHZ && (interval < 0 || interval > 5))) {
+ throw new IllegalArgumentException(
+ "Invalid interval value: 2.4 GHz [1,5] or 5GHz [0,5]");
+ }
+
+ mDiscoveryWindowInterval[band] = interval;
+ return this;
+ }
+
+ /**
+ * Build {@link ConfigRequest} given the current requests made on the
+ * builder.
+ */
+ public ConfigRequest build() {
+ if (mClusterLow > mClusterHigh) {
+ throw new IllegalArgumentException(
+ "Invalid argument combination - must have Cluster Low <= Cluster High");
+ }
+
+ return new ConfigRequest(mSupport5gBand, mMasterPreference, mClusterLow, mClusterHigh,
+ mDiscoveryWindowInterval);
+ }
+ }
+}
diff --git a/android/net/wifi/aware/DiscoverySession.java b/android/net/wifi/aware/DiscoverySession.java
new file mode 100644
index 0000000..d97f6fb
--- /dev/null
+++ b/android/net/wifi/aware/DiscoverySession.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2016 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.aware;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.net.NetworkSpecifier;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import dalvik.system.CloseGuard;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * A class representing a single publish or subscribe Aware session. This object
+ * will not be created directly - only its child classes are available:
+ * {@link PublishDiscoverySession} and {@link SubscribeDiscoverySession}. This
+ * class provides functionality common to both publish and subscribe discovery sessions:
+ * <ul>
+ * <li>Sending messages: {@link #sendMessage(PeerHandle, int, byte[])} method.
+ * <li>Creating a network-specifier when requesting a Aware connection using
+ * {@link WifiAwareNetworkSpecifier.Builder}.
+ * </ul>
+ * <p>
+ * The {@link #close()} method must be called to destroy discovery sessions once they are
+ * no longer needed.
+ */
+public class DiscoverySession implements AutoCloseable {
+ private static final String TAG = "DiscoverySession";
+ private static final boolean DBG = false;
+ private static final boolean VDBG = false; // STOPSHIP if true
+
+ private static final int MAX_SEND_RETRY_COUNT = 5;
+
+ /** @hide */
+ protected WeakReference<WifiAwareManager> mMgr;
+ /** @hide */
+ protected final int mClientId;
+ /** @hide */
+ protected final int mSessionId;
+ /** @hide */
+ protected boolean mTerminated = false;
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ /**
+ * Return the maximum permitted retry count when sending messages using
+ * {@link #sendMessage(PeerHandle, int, byte[], int)}.
+ *
+ * @return Maximum retry count when sending messages.
+ *
+ * @hide
+ */
+ public static int getMaxSendRetryCount() {
+ return MAX_SEND_RETRY_COUNT;
+ }
+
+ /** @hide */
+ public DiscoverySession(WifiAwareManager manager, int clientId, int sessionId) {
+ if (VDBG) {
+ Log.v(TAG, "New discovery session created: manager=" + manager + ", clientId="
+ + clientId + ", sessionId=" + sessionId);
+ }
+
+ mMgr = new WeakReference<>(manager);
+ mClientId = clientId;
+ mSessionId = sessionId;
+
+ mCloseGuard.open("close");
+ }
+
+ /**
+ * Destroy the publish or subscribe session - free any resources, and stop
+ * transmitting packets on-air (for an active session) or listening for
+ * matches (for a passive session). The session may not be used for any
+ * additional operations after its destruction.
+ * <p>
+ * This operation must be done on a session which is no longer needed. Otherwise system
+ * resources will continue to be utilized until the application exits. The only
+ * exception is a session for which we received a termination callback,
+ * {@link DiscoverySessionCallback#onSessionTerminated()}.
+ */
+ @Override
+ public void close() {
+ WifiAwareManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.w(TAG, "destroy: called post GC on WifiAwareManager");
+ return;
+ }
+ mgr.terminateSession(mClientId, mSessionId);
+ mTerminated = true;
+ mMgr.clear();
+ mCloseGuard.close();
+ }
+
+ /**
+ * Sets the status of the session to terminated - i.e. an indication that
+ * already terminated rather than executing a termination.
+ *
+ * @hide
+ */
+ public void setTerminated() {
+ if (mTerminated) {
+ Log.w(TAG, "terminate: already terminated.");
+ return;
+ }
+
+ mTerminated = true;
+ mMgr.clear();
+ mCloseGuard.close();
+ }
+
+ /** @hide */
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
+ if (!mTerminated) {
+ close();
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Access the client ID of the Aware session.
+ *
+ * Note: internal visibility for testing.
+ *
+ * @return The internal client ID.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public int getClientId() {
+ return mClientId;
+ }
+
+ /**
+ * Access the discovery session ID of the Aware session.
+ *
+ * Note: internal visibility for testing.
+ *
+ * @return The internal discovery session ID.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public int getSessionId() {
+ return mSessionId;
+ }
+
+ /**
+ * Sends a message to the specified destination. Aware messages are transmitted in the context
+ * of a discovery session - executed subsequent to a publish/subscribe
+ * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
+ * byte[], java.util.List)} event.
+ * <p>
+ * Aware messages are not guaranteed delivery. Callbacks on
+ * {@link DiscoverySessionCallback} indicate message was transmitted successfully,
+ * {@link DiscoverySessionCallback#onMessageSendSucceeded(int)}, or transmission
+ * failed (possibly after several retries) -
+ * {@link DiscoverySessionCallback#onMessageSendFailed(int)}.
+ * <p>
+ * The peer will get a callback indicating a message was received using
+ * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle,
+ * byte[])}.
+ *
+ * @param peerHandle The peer's handle for the message. Must be a result of an
+ * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
+ * byte[], java.util.List)} or
+ * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle,
+ * byte[])} events.
+ * @param messageId An arbitrary integer used by the caller to identify the message. The same
+ * integer ID will be returned in the callbacks indicating message send success or
+ * failure. The {@code messageId} is not used internally by the Aware service - it
+ * can be arbitrary and non-unique.
+ * @param message The message to be transmitted.
+ * @param retryCount An integer specifying how many additional service-level (as opposed to PHY
+ * or MAC level) retries should be attempted if there is no ACK from the receiver
+ * (note: no retransmissions are attempted in other failure cases). A value of 0
+ * indicates no retries. Max permitted value is {@link #getMaxSendRetryCount()}.
+ *
+ * @hide
+ */
+ public void sendMessage(@NonNull PeerHandle peerHandle, int messageId,
+ @Nullable byte[] message, int retryCount) {
+ if (mTerminated) {
+ Log.w(TAG, "sendMessage: called on terminated session");
+ return;
+ }
+
+ WifiAwareManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.w(TAG, "sendMessage: called post GC on WifiAwareManager");
+ return;
+ }
+
+ mgr.sendMessage(mClientId, mSessionId, peerHandle, message, messageId, retryCount);
+ }
+
+ /**
+ * Sends a message to the specified destination. Aware messages are transmitted in the context
+ * of a discovery session - executed subsequent to a publish/subscribe
+ * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
+ * byte[], java.util.List)} event.
+ * <p>
+ * Aware messages are not guaranteed delivery. Callbacks on
+ * {@link DiscoverySessionCallback} indicate message was transmitted successfully,
+ * {@link DiscoverySessionCallback#onMessageSendSucceeded(int)}, or transmission
+ * failed (possibly after several retries) -
+ * {@link DiscoverySessionCallback#onMessageSendFailed(int)}.
+ * <p>
+ * The peer will get a callback indicating a message was received using
+ * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle,
+ * byte[])}.
+ *
+ * @param peerHandle The peer's handle for the message. Must be a result of an
+ * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
+ * byte[], java.util.List)} or
+ * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle,
+ * byte[])} events.
+ * @param messageId An arbitrary integer used by the caller to identify the message. The same
+ * integer ID will be returned in the callbacks indicating message send success or
+ * failure. The {@code messageId} is not used internally by the Aware service - it
+ * can be arbitrary and non-unique.
+ * @param message The message to be transmitted.
+ */
+ public void sendMessage(@NonNull PeerHandle peerHandle, int messageId,
+ @Nullable byte[] message) {
+ sendMessage(peerHandle, messageId, message, 0);
+ }
+
+ /**
+ * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
+ * an unencrypted WiFi Aware connection (link) to the specified peer. The
+ * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
+ * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
+ * <p>
+ * This method should be used when setting up a connection with a peer discovered through Aware
+ * discovery or communication (in such scenarios the MAC address of the peer is shielded by
+ * an opaque peer ID handle). If an Aware connection is needed to a peer discovered using other
+ * OOB (out-of-band) mechanism then use the alternative
+ * {@link WifiAwareSession#createNetworkSpecifierOpen(int, byte[])} method - which uses the
+ * peer's MAC address.
+ * <p>
+ * Note: per the Wi-Fi Aware specification the roles are fixed - a Subscriber is an INITIATOR
+ * and a Publisher is a RESPONDER.
+ * <p>
+ * To set up an encrypted link use the
+ * {@link #createNetworkSpecifierPassphrase(PeerHandle, String)} API.
+ * @deprecated Use the replacement {@link WifiAwareNetworkSpecifier.Builder}.
+ *
+ * @param peerHandle The peer's handle obtained through
+ * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], java.util.List)}
+ * or
+ * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle, byte[])}.
+ * On a RESPONDER this value is used to gate the acceptance of a connection
+ * request from only that peer.
+ *
+ * @return A {@link NetworkSpecifier} to be used to construct
+ * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
+ * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
+ * android.net.ConnectivityManager.NetworkCallback)}
+ * [or other varieties of that API].
+ */
+ @Deprecated
+ public NetworkSpecifier createNetworkSpecifierOpen(@NonNull PeerHandle peerHandle) {
+ if (mTerminated) {
+ Log.w(TAG, "createNetworkSpecifierOpen: called on terminated session");
+ return null;
+ }
+
+ WifiAwareManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.w(TAG, "createNetworkSpecifierOpen: called post GC on WifiAwareManager");
+ return null;
+ }
+
+ int role = this instanceof SubscribeDiscoverySession
+ ? WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
+ : WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER;
+
+ return mgr.createNetworkSpecifier(mClientId, role, mSessionId, peerHandle, null, null);
+ }
+
+ /**
+ * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
+ * an encrypted WiFi Aware connection (link) to the specified peer. The
+ * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
+ * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
+ * <p>
+ * This method should be used when setting up a connection with a peer discovered through Aware
+ * discovery or communication (in such scenarios the MAC address of the peer is shielded by
+ * an opaque peer ID handle). If an Aware connection is needed to a peer discovered using other
+ * OOB (out-of-band) mechanism then use the alternative
+ * {@link WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)} method -
+ * which uses the peer's MAC address.
+ * <p>
+ * Note: per the Wi-Fi Aware specification the roles are fixed - a Subscriber is an INITIATOR
+ * and a Publisher is a RESPONDER.
+ * @deprecated Use the replacement {@link WifiAwareNetworkSpecifier.Builder}.
+ *
+ * @param peerHandle The peer's handle obtained through
+ * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
+ * byte[], java.util.List)} or
+ * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle,
+ * byte[])}. On a RESPONDER this value is used to gate the acceptance of a connection request
+ * from only that peer.
+ * @param passphrase The passphrase to be used to encrypt the link. The PMK is generated from
+ * the passphrase. Use the
+ * {@link #createNetworkSpecifierOpen(PeerHandle)} API to
+ * specify an open (unencrypted) link.
+ *
+ * @return A {@link NetworkSpecifier} to be used to construct
+ * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
+ * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
+ * android.net.ConnectivityManager.NetworkCallback)}
+ * [or other varieties of that API].
+ */
+ @Deprecated
+ public NetworkSpecifier createNetworkSpecifierPassphrase(
+ @NonNull PeerHandle peerHandle, @NonNull String passphrase) {
+ if (!WifiAwareUtils.validatePassphrase(passphrase)) {
+ throw new IllegalArgumentException("Passphrase must meet length requirements");
+ }
+
+ if (mTerminated) {
+ Log.w(TAG, "createNetworkSpecifierPassphrase: called on terminated session");
+ return null;
+ }
+
+ WifiAwareManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.w(TAG, "createNetworkSpecifierPassphrase: called post GC on WifiAwareManager");
+ return null;
+ }
+
+ int role = this instanceof SubscribeDiscoverySession
+ ? WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
+ : WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER;
+
+ return mgr.createNetworkSpecifier(mClientId, role, mSessionId, peerHandle, null,
+ passphrase);
+ }
+
+ /**
+ * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
+ * an encrypted WiFi Aware connection (link) to the specified peer. The
+ * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
+ * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
+ * <p>
+ * This method should be used when setting up a connection with a peer discovered through Aware
+ * discovery or communication (in such scenarios the MAC address of the peer is shielded by
+ * an opaque peer ID handle). If an Aware connection is needed to a peer discovered using other
+ * OOB (out-of-band) mechanism then use the alternative
+ * {@link WifiAwareSession#createNetworkSpecifierPmk(int, byte[], byte[])} method - which uses
+ * the peer's MAC address.
+ * <p>
+ * Note: per the Wi-Fi Aware specification the roles are fixed - a Subscriber is an INITIATOR
+ * and a Publisher is a RESPONDER.
+ * @deprecated Use the replacement {@link WifiAwareNetworkSpecifier.Builder}.
+ *
+ * @param peerHandle The peer's handle obtained through
+ * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
+ * byte[], java.util.List)} or
+ * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle,
+ * byte[])}. On a RESPONDER this value is used to gate the acceptance of a connection request
+ * from only that peer.
+ * @param pmk A PMK (pairwise master key, see IEEE 802.11i) specifying the key to use for
+ * encrypting the data-path. Use the
+ * {@link #createNetworkSpecifierPassphrase(PeerHandle, String)} to specify a
+ * Passphrase or {@link #createNetworkSpecifierOpen(PeerHandle)} to specify an
+ * open (unencrypted) link.
+ *
+ * @return A {@link NetworkSpecifier} to be used to construct
+ * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
+ * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
+ * android.net.ConnectivityManager.NetworkCallback)}
+ * [or other varieties of that API].
+ *
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public NetworkSpecifier createNetworkSpecifierPmk(@NonNull PeerHandle peerHandle,
+ @NonNull byte[] pmk) {
+ if (!WifiAwareUtils.validatePmk(pmk)) {
+ throw new IllegalArgumentException("PMK must 32 bytes");
+ }
+
+ if (mTerminated) {
+ Log.w(TAG, "createNetworkSpecifierPmk: called on terminated session");
+ return null;
+ }
+
+ WifiAwareManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.w(TAG, "createNetworkSpecifierPmk: called post GC on WifiAwareManager");
+ return null;
+ }
+
+ int role = this instanceof SubscribeDiscoverySession
+ ? WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
+ : WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER;
+
+ return mgr.createNetworkSpecifier(mClientId, role, mSessionId, peerHandle, pmk, null);
+ }
+}
diff --git a/android/net/wifi/aware/DiscoverySessionCallback.java b/android/net/wifi/aware/DiscoverySessionCallback.java
new file mode 100644
index 0000000..bfb0462
--- /dev/null
+++ b/android/net/wifi/aware/DiscoverySessionCallback.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2016 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.aware;
+
+import android.annotation.NonNull;
+
+import java.util.List;
+
+/**
+ * Base class for Aware session events callbacks. Should be extended by
+ * applications wanting notifications. The callbacks are set when a
+ * publish or subscribe session is created using
+ * {@link WifiAwareSession#publish(PublishConfig, DiscoverySessionCallback,
+ * android.os.Handler)} or
+ * {@link WifiAwareSession#subscribe(SubscribeConfig, DiscoverySessionCallback,
+ * android.os.Handler)}.
+ * <p>
+ * A single callback is set at session creation - it cannot be replaced.
+ */
+public class DiscoverySessionCallback {
+ /**
+ * Called when a publish operation is started successfully in response to a
+ * {@link WifiAwareSession#publish(PublishConfig, DiscoverySessionCallback,
+ * android.os.Handler)} operation.
+ *
+ * @param session The {@link PublishDiscoverySession} used to control the
+ * discovery session.
+ */
+ public void onPublishStarted(@NonNull PublishDiscoverySession session) {
+ /* empty */
+ }
+
+ /**
+ * Called when a subscribe operation is started successfully in response to a
+ * {@link WifiAwareSession#subscribe(SubscribeConfig, DiscoverySessionCallback,
+ * android.os.Handler)} operation.
+ *
+ * @param session The {@link SubscribeDiscoverySession} used to control the
+ * discovery session.
+ */
+ public void onSubscribeStarted(@NonNull SubscribeDiscoverySession session) {
+ /* empty */
+ }
+
+ /**
+ * Called when a publish or subscribe discovery session configuration update request
+ * succeeds. Called in response to
+ * {@link PublishDiscoverySession#updatePublish(PublishConfig)} or
+ * {@link SubscribeDiscoverySession#updateSubscribe(SubscribeConfig)}.
+ */
+ public void onSessionConfigUpdated() {
+ /* empty */
+ }
+
+ /**
+ * Called when a publish or subscribe discovery session cannot be created:
+ * {@link WifiAwareSession#publish(PublishConfig, DiscoverySessionCallback,
+ * android.os.Handler)} or
+ * {@link WifiAwareSession#subscribe(SubscribeConfig, DiscoverySessionCallback,
+ * android.os.Handler)}, or when a configuration update fails:
+ * {@link PublishDiscoverySession#updatePublish(PublishConfig)} or
+ * {@link SubscribeDiscoverySession#updateSubscribe(SubscribeConfig)}.
+ * <p>
+ * For discovery session updates failure leaves the session running with its previous
+ * configuration - the discovery session is not terminated.
+ */
+ public void onSessionConfigFailed() {
+ /* empty */
+ }
+
+ /**
+ * Called when a discovery session (publish or subscribe) terminates. Termination may be due
+ * to user-request (either directly through {@link DiscoverySession#close()} or
+ * application-specified expiration, e.g. {@link PublishConfig.Builder#setTtlSec(int)}
+ * or {@link SubscribeConfig.Builder#setTtlSec(int)}).
+ */
+ public void onSessionTerminated() {
+ /* empty */
+ }
+
+ /**
+ * Called when a discovery (publish or subscribe) operation results in a
+ * service discovery.
+ * <p>
+ * Note that this method and
+ * {@link #onServiceDiscoveredWithinRange(PeerHandle, byte[], List, int)} may be called
+ * multiple times per service discovery.
+ *
+ * @param peerHandle An opaque handle to the peer matching our discovery operation.
+ * @param serviceSpecificInfo The service specific information (arbitrary
+ * byte array) provided by the peer as part of its discovery
+ * configuration.
+ * @param matchFilter The filter which resulted in this service discovery. For
+ * {@link PublishConfig#PUBLISH_TYPE_UNSOLICITED},
+ * {@link SubscribeConfig#SUBSCRIBE_TYPE_PASSIVE} discovery sessions this is the publisher's
+ * match filter. For {@link PublishConfig#PUBLISH_TYPE_SOLICITED},
+ * {@link SubscribeConfig#SUBSCRIBE_TYPE_ACTIVE} discovery sessions this
+ * is the subscriber's match filter.
+ */
+ public void onServiceDiscovered(PeerHandle peerHandle,
+ byte[] serviceSpecificInfo, List<byte[]> matchFilter) {
+ /* empty */
+ }
+
+ /**
+ * Called when a discovery (publish or subscribe) operation results in a
+ * service discovery. Called when a Subscribe service was configured with a range requirement
+ * {@link SubscribeConfig.Builder#setMinDistanceMm(int)} and/or
+ * {@link SubscribeConfig.Builder#setMaxDistanceMm(int)} and the Publish service was configured
+ * with {@link PublishConfig.Builder#setRangingEnabled(boolean)}.
+ * <p>
+ * If either Publisher or Subscriber does not enable Ranging, or if Ranging is temporarily
+ * disabled by the underlying device, service discovery proceeds without ranging and the
+ * {@link #onServiceDiscovered(PeerHandle, byte[], List)} is called.
+ * <p>
+ * Note that this method and {@link #onServiceDiscovered(PeerHandle, byte[], List)} may be
+ * called multiple times per service discovery.
+ *
+ * @param peerHandle An opaque handle to the peer matching our discovery operation.
+ * @param serviceSpecificInfo The service specific information (arbitrary
+ * byte array) provided by the peer as part of its discovery
+ * configuration.
+ * @param matchFilter The filter which resulted in this service discovery. For
+ * {@link PublishConfig#PUBLISH_TYPE_UNSOLICITED},
+ * {@link SubscribeConfig#SUBSCRIBE_TYPE_PASSIVE} discovery sessions this is the publisher's
+ * match filter. For {@link PublishConfig#PUBLISH_TYPE_SOLICITED},
+ * {@link SubscribeConfig#SUBSCRIBE_TYPE_ACTIVE} discovery sessions this
+ * is the subscriber's match filter.
+ * @param distanceMm The measured distance to the Publisher in mm. Note: the measured distance
+ * may be negative for very close devices.
+ */
+ public void onServiceDiscoveredWithinRange(PeerHandle peerHandle,
+ byte[] serviceSpecificInfo, List<byte[]> matchFilter, int distanceMm) {
+ /* empty */
+ }
+
+ /**
+ * Called in response to
+ * {@link DiscoverySession#sendMessage(PeerHandle, int, byte[])}
+ * when a message is transmitted successfully - i.e. when it was received successfully by the
+ * peer (corresponds to an ACK being received).
+ * <p>
+ * Note that either this callback or
+ * {@link DiscoverySessionCallback#onMessageSendFailed(int)} will be
+ * received - never both.
+ *
+ * @param messageId The arbitrary message ID specified when sending the message.
+ */
+ public void onMessageSendSucceeded(@SuppressWarnings("unused") int messageId) {
+ /* empty */
+ }
+
+ /**
+ * Called when message transmission initiated with
+ * {@link DiscoverySession#sendMessage(PeerHandle, int, byte[])} fails. E.g. when no ACK is
+ * received from the peer.
+ * <p>
+ * Note that either this callback or
+ * {@link DiscoverySessionCallback#onMessageSendSucceeded(int)} will be received
+ * - never both.
+ *
+ * @param messageId The arbitrary message ID specified when sending the message.
+ */
+ public void onMessageSendFailed(@SuppressWarnings("unused") int messageId) {
+ /* empty */
+ }
+
+ /**
+ * Called when a message is received from a discovery session peer - in response to the
+ * peer's {@link DiscoverySession#sendMessage(PeerHandle, int, byte[])}.
+ *
+ * @param peerHandle An opaque handle to the peer matching our discovery operation.
+ * @param message A byte array containing the message.
+ */
+ public void onMessageReceived(PeerHandle peerHandle, byte[] message) {
+ /* empty */
+ }
+}
diff --git a/android/net/wifi/aware/IdentityChangedListener.java b/android/net/wifi/aware/IdentityChangedListener.java
new file mode 100644
index 0000000..a8b19b3
--- /dev/null
+++ b/android/net/wifi/aware/IdentityChangedListener.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 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.aware;
+
+/**
+ * Base class for a listener which is called with the MAC address of the Aware interface whenever
+ * it is changed. Change may be due to device joining a cluster, starting a cluster, or discovery
+ * interface change (addresses are randomized at regular intervals). The implication is that
+ * peers you've been communicating with may no longer recognize you and you need to re-establish
+ * your identity - e.g. by starting a discovery session. This actual MAC address of the
+ * interface may also be useful if the application uses alternative (non-Aware) discovery but needs
+ * to set up a Aware connection. The provided Aware discovery interface MAC address can then be used
+ * in {@link WifiAwareSession#createNetworkSpecifierOpen(int, byte[])} or
+ * {@link WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)}.
+ */
+public class IdentityChangedListener {
+ /**
+ * @param mac The MAC address of the Aware discovery interface. The application must have the
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} to get the actual MAC address,
+ * otherwise all 0's will be provided.
+ */
+ public void onIdentityChanged(byte[] mac) {
+ /* empty */
+ }
+}
diff --git a/android/net/wifi/aware/ParcelablePeerHandle.java b/android/net/wifi/aware/ParcelablePeerHandle.java
new file mode 100644
index 0000000..ca473db
--- /dev/null
+++ b/android/net/wifi/aware/ParcelablePeerHandle.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.aware;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A Parcelable {@link PeerHandle}. Can be constructed from a {@code PeerHandle} and then passed
+ * to any of the APIs which take a {@code PeerHandle} as inputs.
+ */
+public final class ParcelablePeerHandle extends PeerHandle implements Parcelable {
+ /**
+ * Construct a parcelable version of {@link PeerHandle}.
+ *
+ * @param peerHandle The {@link PeerHandle} to be made parcelable.
+ */
+ public ParcelablePeerHandle(@NonNull PeerHandle peerHandle) {
+ super(peerHandle.peerId);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(peerId);
+ }
+
+ public static final @android.annotation.NonNull Creator<ParcelablePeerHandle> CREATOR =
+ new Creator<ParcelablePeerHandle>() {
+ @Override
+ public ParcelablePeerHandle[] newArray(int size) {
+ return new ParcelablePeerHandle[size];
+ }
+
+ @Override
+ public ParcelablePeerHandle createFromParcel(Parcel in) {
+ int peerHandle = in.readInt();
+ return new ParcelablePeerHandle(new PeerHandle(peerHandle));
+ }
+ };
+}
diff --git a/android/net/wifi/aware/PeerHandle.java b/android/net/wifi/aware/PeerHandle.java
new file mode 100644
index 0000000..422e177
--- /dev/null
+++ b/android/net/wifi/aware/PeerHandle.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2016 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.aware;
+
+/**
+ * Opaque object used to represent a Wi-Fi Aware peer. Obtained from discovery sessions in
+ * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], java.util.List)} or
+ * received messages in {@link DiscoverySessionCallback#onMessageReceived(PeerHandle, byte[])}, and
+ * used when sending messages e,g, {@link DiscoverySession#sendMessage(PeerHandle, int, byte[])},
+ * or when configuring a network link to a peer, e.g.
+ * {@link DiscoverySession#createNetworkSpecifierOpen(PeerHandle)} or
+ * {@link DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)}.
+ * <p>
+ * Note that while a {@code PeerHandle} can be used to track a particular peer (i.e. you can compare
+ * the values received from subsequent messages) - it is good practice not to rely on it. Instead
+ * use an application level peer identifier encoded in the message,
+ * {@link DiscoverySession#sendMessage(PeerHandle, int, byte[])}, and/or in the Publish
+ * configuration's service-specific information field,
+ * {@link PublishConfig.Builder#setServiceSpecificInfo(byte[])}, or match filter,
+ * {@link PublishConfig.Builder#setMatchFilter(java.util.List)}.
+ * <p>A parcelable handle object is available with {@link ParcelablePeerHandle}.
+ */
+public class PeerHandle {
+ /** @hide */
+ public PeerHandle(int peerId) {
+ this.peerId = peerId;
+ }
+
+ /** @hide */
+ public int peerId;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof PeerHandle)) {
+ return false;
+ }
+
+ return peerId == ((PeerHandle) o).peerId;
+ }
+
+ @Override
+ public int hashCode() {
+ return peerId;
+ }
+}
diff --git a/android/net/wifi/aware/PublishConfig.java b/android/net/wifi/aware/PublishConfig.java
new file mode 100644
index 0000000..1886b7e
--- /dev/null
+++ b/android/net/wifi/aware/PublishConfig.java
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2016 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.aware;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import libcore.util.HexEncoding;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Defines the configuration of a Aware publish session. Built using
+ * {@link PublishConfig.Builder}. A publish session is created using
+ * {@link WifiAwareSession#publish(PublishConfig, DiscoverySessionCallback,
+ * android.os.Handler)} or updated using
+ * {@link PublishDiscoverySession#updatePublish(PublishConfig)}.
+ */
+public final class PublishConfig implements Parcelable {
+ /** @hide */
+ @IntDef({
+ PUBLISH_TYPE_UNSOLICITED, PUBLISH_TYPE_SOLICITED })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PublishTypes {
+ }
+
+ /**
+ * Defines an unsolicited publish session - a publish session where the publisher is
+ * advertising itself by broadcasting on-the-air. An unsolicited publish session is paired
+ * with an passive subscribe session {@link SubscribeConfig#SUBSCRIBE_TYPE_PASSIVE}.
+ * Configuration is done using {@link PublishConfig.Builder#setPublishType(int)}.
+ */
+ public static final int PUBLISH_TYPE_UNSOLICITED = 0;
+
+ /**
+ * Defines a solicited publish session - a publish session which is silent, waiting for a
+ * matching active subscribe session - and responding to it in unicast. A
+ * solicited publish session is paired with an active subscribe session
+ * {@link SubscribeConfig#SUBSCRIBE_TYPE_ACTIVE}. Configuration is done using
+ * {@link PublishConfig.Builder#setPublishType(int)}.
+ */
+ public static final int PUBLISH_TYPE_SOLICITED = 1;
+
+ /** @hide */
+ public final byte[] mServiceName;
+
+ /** @hide */
+ public final byte[] mServiceSpecificInfo;
+
+ /** @hide */
+ public final byte[] mMatchFilter;
+
+ /** @hide */
+ public final int mPublishType;
+
+ /** @hide */
+ public final int mTtlSec;
+
+ /** @hide */
+ public final boolean mEnableTerminateNotification;
+
+ /** @hide */
+ public final boolean mEnableRanging;
+
+ /** @hide */
+ public PublishConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter,
+ int publishType, int ttlSec, boolean enableTerminateNotification,
+ boolean enableRanging) {
+ mServiceName = serviceName;
+ mServiceSpecificInfo = serviceSpecificInfo;
+ mMatchFilter = matchFilter;
+ mPublishType = publishType;
+ mTtlSec = ttlSec;
+ mEnableTerminateNotification = enableTerminateNotification;
+ mEnableRanging = enableRanging;
+ }
+
+ @Override
+ public String toString() {
+ return "PublishConfig [mServiceName='" + (mServiceName == null ? "<null>" : String.valueOf(
+ HexEncoding.encode(mServiceName))) + ", mServiceName.length=" + (
+ mServiceName == null ? 0 : mServiceName.length) + ", mServiceSpecificInfo='" + (
+ (mServiceSpecificInfo == null) ? "<null>" : String.valueOf(
+ HexEncoding.encode(mServiceSpecificInfo)))
+ + ", mServiceSpecificInfo.length=" + (mServiceSpecificInfo == null ? 0
+ : mServiceSpecificInfo.length) + ", mMatchFilter="
+ + (new TlvBufferUtils.TlvIterable(0, 1, mMatchFilter)).toString()
+ + ", mMatchFilter.length=" + (mMatchFilter == null ? 0 : mMatchFilter.length)
+ + ", mPublishType=" + mPublishType + ", mTtlSec=" + mTtlSec
+ + ", mEnableTerminateNotification=" + mEnableTerminateNotification
+ + ", mEnableRanging=" + mEnableRanging + "]";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeByteArray(mServiceName);
+ dest.writeByteArray(mServiceSpecificInfo);
+ dest.writeByteArray(mMatchFilter);
+ dest.writeInt(mPublishType);
+ dest.writeInt(mTtlSec);
+ dest.writeInt(mEnableTerminateNotification ? 1 : 0);
+ dest.writeInt(mEnableRanging ? 1 : 0);
+ }
+
+ public static final @android.annotation.NonNull Creator<PublishConfig> CREATOR = new Creator<PublishConfig>() {
+ @Override
+ public PublishConfig[] newArray(int size) {
+ return new PublishConfig[size];
+ }
+
+ @Override
+ public PublishConfig createFromParcel(Parcel in) {
+ byte[] serviceName = in.createByteArray();
+ byte[] ssi = in.createByteArray();
+ byte[] matchFilter = in.createByteArray();
+ int publishType = in.readInt();
+ int ttlSec = in.readInt();
+ boolean enableTerminateNotification = in.readInt() != 0;
+ boolean enableRanging = in.readInt() != 0;
+
+ return new PublishConfig(serviceName, ssi, matchFilter, publishType,
+ ttlSec, enableTerminateNotification, enableRanging);
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof PublishConfig)) {
+ return false;
+ }
+
+ PublishConfig lhs = (PublishConfig) o;
+
+ return Arrays.equals(mServiceName, lhs.mServiceName) && Arrays.equals(mServiceSpecificInfo,
+ lhs.mServiceSpecificInfo) && Arrays.equals(mMatchFilter, lhs.mMatchFilter)
+ && mPublishType == lhs.mPublishType
+ && mTtlSec == lhs.mTtlSec
+ && mEnableTerminateNotification == lhs.mEnableTerminateNotification
+ && mEnableRanging == lhs.mEnableRanging;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(Arrays.hashCode(mServiceName), Arrays.hashCode(mServiceSpecificInfo),
+ Arrays.hashCode(mMatchFilter), mPublishType, mTtlSec, mEnableTerminateNotification,
+ mEnableRanging);
+ }
+
+ /**
+ * Verifies that the contents of the PublishConfig are valid. Otherwise
+ * throws an IllegalArgumentException.
+ *
+ * @hide
+ */
+ public void assertValid(Characteristics characteristics, boolean rttSupported)
+ throws IllegalArgumentException {
+ WifiAwareUtils.validateServiceName(mServiceName);
+
+ if (!TlvBufferUtils.isValid(mMatchFilter, 0, 1)) {
+ throw new IllegalArgumentException(
+ "Invalid txFilter configuration - LV fields do not match up to length");
+ }
+ if (mPublishType < PUBLISH_TYPE_UNSOLICITED || mPublishType > PUBLISH_TYPE_SOLICITED) {
+ throw new IllegalArgumentException("Invalid publishType - " + mPublishType);
+ }
+ if (mTtlSec < 0) {
+ throw new IllegalArgumentException("Invalid ttlSec - must be non-negative");
+ }
+
+ if (characteristics != null) {
+ int maxServiceNameLength = characteristics.getMaxServiceNameLength();
+ if (maxServiceNameLength != 0 && mServiceName.length > maxServiceNameLength) {
+ throw new IllegalArgumentException(
+ "Service name longer than supported by device characteristics");
+ }
+ int maxServiceSpecificInfoLength = characteristics.getMaxServiceSpecificInfoLength();
+ if (maxServiceSpecificInfoLength != 0 && mServiceSpecificInfo != null
+ && mServiceSpecificInfo.length > maxServiceSpecificInfoLength) {
+ throw new IllegalArgumentException(
+ "Service specific info longer than supported by device characteristics");
+ }
+ int maxMatchFilterLength = characteristics.getMaxMatchFilterLength();
+ if (maxMatchFilterLength != 0 && mMatchFilter != null
+ && mMatchFilter.length > maxMatchFilterLength) {
+ throw new IllegalArgumentException(
+ "Match filter longer than supported by device characteristics");
+ }
+ }
+
+ if (!rttSupported && mEnableRanging) {
+ throw new IllegalArgumentException("Ranging is not supported");
+ }
+ }
+
+ /**
+ * Builder used to build {@link PublishConfig} objects.
+ */
+ public static final class Builder {
+ private byte[] mServiceName;
+ private byte[] mServiceSpecificInfo;
+ private byte[] mMatchFilter;
+ private int mPublishType = PUBLISH_TYPE_UNSOLICITED;
+ private int mTtlSec = 0;
+ private boolean mEnableTerminateNotification = true;
+ private boolean mEnableRanging = false;
+
+ /**
+ * Specify the service name of the publish session. The actual on-air
+ * value is a 6 byte hashed representation of this string.
+ * <p>
+ * The Service Name is a UTF-8 encoded string from 1 to 255 bytes in length.
+ * The only acceptable single-byte UTF-8 symbols for a Service Name are alphanumeric
+ * values (A-Z, a-z, 0-9), the hyphen ('-'), and the period ('.'). All valid multi-byte
+ * UTF-8 characters are acceptable in a Service Name.
+ * <p>
+ * Must be called - an empty ServiceName is not valid.
+ *
+ * @param serviceName The service name for the publish session.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setServiceName(@NonNull String serviceName) {
+ if (serviceName == null) {
+ throw new IllegalArgumentException("Invalid service name - must be non-null");
+ }
+ mServiceName = serviceName.getBytes(StandardCharsets.UTF_8);
+ return this;
+ }
+
+ /**
+ * Specify service specific information for the publish session. This is
+ * a free-form byte array available to the application to send
+ * additional information as part of the discovery operation - it
+ * will not be used to determine whether a publish/subscribe match
+ * occurs.
+ * <p>
+ * Optional. Empty by default.
+ *
+ * @param serviceSpecificInfo A byte-array for the service-specific
+ * information field.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setServiceSpecificInfo(@Nullable byte[] serviceSpecificInfo) {
+ mServiceSpecificInfo = serviceSpecificInfo;
+ return this;
+ }
+
+ /**
+ * The match filter for a publish session. Used to determine whether a service
+ * discovery occurred - in addition to relying on the service name.
+ * <p>
+ * Optional. Empty by default.
+ *
+ * @param matchFilter A list of match filter entries (each of which is an arbitrary byte
+ * array).
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setMatchFilter(@Nullable List<byte[]> matchFilter) {
+ mMatchFilter = new TlvBufferUtils.TlvConstructor(0, 1).allocateAndPut(
+ matchFilter).getArray();
+ return this;
+ }
+
+ /**
+ * Specify the type of the publish session: solicited (aka active - publish
+ * packets are transmitted over-the-air), or unsolicited (aka passive -
+ * no publish packets are transmitted, a match is made against an active
+ * subscribe session whose packets are transmitted over-the-air).
+ *
+ * @param publishType Publish session type:
+ * {@link PublishConfig#PUBLISH_TYPE_SOLICITED} or
+ * {@link PublishConfig#PUBLISH_TYPE_UNSOLICITED} (the default).
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setPublishType(@PublishTypes int publishType) {
+ if (publishType < PUBLISH_TYPE_UNSOLICITED || publishType > PUBLISH_TYPE_SOLICITED) {
+ throw new IllegalArgumentException("Invalid publishType - " + publishType);
+ }
+ mPublishType = publishType;
+ return this;
+ }
+
+ /**
+ * Sets the time interval (in seconds) an unsolicited (
+ * {@link PublishConfig.Builder#setPublishType(int)}) publish session
+ * will be alive - broadcasting a packet. When the TTL is reached
+ * an event will be generated for
+ * {@link DiscoverySessionCallback#onSessionTerminated()} [unless
+ * {@link #setTerminateNotificationEnabled(boolean)} disables the callback].
+ * <p>
+ * Optional. 0 by default - indicating the session doesn't terminate on its own.
+ * Session will be terminated when {@link DiscoverySession#close()} is
+ * called.
+ *
+ * @param ttlSec Lifetime of a publish session in seconds.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setTtlSec(int ttlSec) {
+ if (ttlSec < 0) {
+ throw new IllegalArgumentException("Invalid ttlSec - must be non-negative");
+ }
+ mTtlSec = ttlSec;
+ return this;
+ }
+
+ /**
+ * Configure whether a publish terminate notification
+ * {@link DiscoverySessionCallback#onSessionTerminated()} is reported
+ * back to the callback.
+ *
+ * @param enable If true the terminate callback will be called when the
+ * publish is terminated. Otherwise it will not be called.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setTerminateNotificationEnabled(boolean enable) {
+ mEnableTerminateNotification = enable;
+ return this;
+ }
+
+ /**
+ * Configure whether the publish discovery session supports ranging and allows peers to
+ * measure distance to it. This API is used in conjunction with
+ * {@link SubscribeConfig.Builder#setMinDistanceMm(int)} and
+ * {@link SubscribeConfig.Builder#setMaxDistanceMm(int)} to specify a minimum and/or
+ * maximum distance at which discovery will be triggered.
+ * <p>
+ * Optional. Disabled by default - i.e. any peer attempt to measure distance to this device
+ * will be refused and discovery will proceed without ranging constraints.
+ * <p>
+ * The device must support Wi-Fi RTT for this feature to be used. Feature support is checked
+ * as described in {@link android.net.wifi.rtt}.
+ *
+ * @param enable If true, ranging is supported on request of the peer.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setRangingEnabled(boolean enable) {
+ mEnableRanging = enable;
+ return this;
+ }
+
+ /**
+ * Build {@link PublishConfig} given the current requests made on the
+ * builder.
+ */
+ public PublishConfig build() {
+ return new PublishConfig(mServiceName, mServiceSpecificInfo, mMatchFilter, mPublishType,
+ mTtlSec, mEnableTerminateNotification, mEnableRanging);
+ }
+ }
+}
diff --git a/android/net/wifi/aware/PublishDiscoverySession.java b/android/net/wifi/aware/PublishDiscoverySession.java
new file mode 100644
index 0000000..1c99c87
--- /dev/null
+++ b/android/net/wifi/aware/PublishDiscoverySession.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 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.aware;
+
+import android.annotation.NonNull;
+import android.util.Log;
+
+/**
+ * A class representing a Aware publish session. Created when
+ * {@link WifiAwareSession#publish(PublishConfig, DiscoverySessionCallback,
+ * android.os.Handler)} is called and a discovery session is created and returned in
+ * {@link DiscoverySessionCallback#onPublishStarted(PublishDiscoverySession)}. See
+ * baseline functionality of all discovery sessions in {@link DiscoverySession}. This
+ * object allows updating an existing/running publish discovery session using
+ * {@link #updatePublish(PublishConfig)}.
+ */
+public class PublishDiscoverySession extends DiscoverySession {
+ private static final String TAG = "PublishDiscoverySession";
+
+ /** @hide */
+ public PublishDiscoverySession(WifiAwareManager manager, int clientId, int sessionId) {
+ super(manager, clientId, sessionId);
+ }
+
+ /**
+ * Re-configure the currently active publish session. The
+ * {@link DiscoverySessionCallback} is not replaced - the same listener used
+ * at creation is still used. The results of the configuration are returned using
+ * {@link DiscoverySessionCallback}:
+ * <ul>
+ * <li>{@link DiscoverySessionCallback#onSessionConfigUpdated()}: configuration
+ * update succeeded.
+ * <li>{@link DiscoverySessionCallback#onSessionConfigFailed()}: configuration
+ * update failed. The publish discovery session is still running using its previous
+ * configuration (i.e. update failure does not terminate the session).
+ * </ul>
+ *
+ * @param publishConfig The new discovery publish session configuration ({@link PublishConfig}).
+ */
+ public void updatePublish(@NonNull PublishConfig publishConfig) {
+ if (mTerminated) {
+ Log.w(TAG, "updatePublish: called on terminated session");
+ return;
+ } else {
+ WifiAwareManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.w(TAG, "updatePublish: called post GC on WifiAwareManager");
+ return;
+ }
+
+ mgr.updatePublish(mClientId, mSessionId, publishConfig);
+ }
+ }
+}
diff --git a/android/net/wifi/aware/SubscribeConfig.java b/android/net/wifi/aware/SubscribeConfig.java
new file mode 100644
index 0000000..f0f7581
--- /dev/null
+++ b/android/net/wifi/aware/SubscribeConfig.java
@@ -0,0 +1,498 @@
+/*
+ * Copyright (C) 2016 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.aware;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import libcore.util.HexEncoding;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Defines the configuration of a Aware subscribe session. Built using
+ * {@link SubscribeConfig.Builder}. Subscribe is done using
+ * {@link WifiAwareSession#subscribe(SubscribeConfig, DiscoverySessionCallback,
+ * android.os.Handler)} or
+ * {@link SubscribeDiscoverySession#updateSubscribe(SubscribeConfig)}.
+ */
+public final class SubscribeConfig implements Parcelable {
+ /** @hide */
+ @IntDef({
+ SUBSCRIBE_TYPE_PASSIVE, SUBSCRIBE_TYPE_ACTIVE })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SubscribeTypes {
+ }
+
+ /**
+ * Defines a passive subscribe session - a subscribe session where
+ * subscribe packets are not transmitted over-the-air and the device listens
+ * and matches to transmitted publish packets. Configuration is done using
+ * {@link SubscribeConfig.Builder#setSubscribeType(int)}.
+ */
+ public static final int SUBSCRIBE_TYPE_PASSIVE = 0;
+
+ /**
+ * Defines an active subscribe session - a subscribe session where
+ * subscribe packets are transmitted over-the-air. Configuration is done
+ * using {@link SubscribeConfig.Builder#setSubscribeType(int)}.
+ */
+ public static final int SUBSCRIBE_TYPE_ACTIVE = 1;
+
+ /** @hide */
+ public final byte[] mServiceName;
+
+ /** @hide */
+ public final byte[] mServiceSpecificInfo;
+
+ /** @hide */
+ public final byte[] mMatchFilter;
+
+ /** @hide */
+ public final int mSubscribeType;
+
+ /** @hide */
+ public final int mTtlSec;
+
+ /** @hide */
+ public final boolean mEnableTerminateNotification;
+
+ /** @hide */
+ public final boolean mMinDistanceMmSet;
+
+ /** @hide */
+ public final int mMinDistanceMm;
+
+ /** @hide */
+ public final boolean mMaxDistanceMmSet;
+
+ /** @hide */
+ public final int mMaxDistanceMm;
+
+ /** @hide */
+ public SubscribeConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter,
+ int subscribeType, int ttlSec, boolean enableTerminateNotification,
+ boolean minDistanceMmSet, int minDistanceMm, boolean maxDistanceMmSet,
+ int maxDistanceMm) {
+ mServiceName = serviceName;
+ mServiceSpecificInfo = serviceSpecificInfo;
+ mMatchFilter = matchFilter;
+ mSubscribeType = subscribeType;
+ mTtlSec = ttlSec;
+ mEnableTerminateNotification = enableTerminateNotification;
+ mMinDistanceMm = minDistanceMm;
+ mMinDistanceMmSet = minDistanceMmSet;
+ mMaxDistanceMm = maxDistanceMm;
+ mMaxDistanceMmSet = maxDistanceMmSet;
+ }
+
+ @Override
+ public String toString() {
+ return "SubscribeConfig [mServiceName='" + (mServiceName == null ? "<null>"
+ : String.valueOf(HexEncoding.encode(mServiceName))) + ", mServiceName.length=" + (
+ mServiceName == null ? 0 : mServiceName.length) + ", mServiceSpecificInfo='" + (
+ (mServiceSpecificInfo == null) ? "<null>" : String.valueOf(
+ HexEncoding.encode(mServiceSpecificInfo)))
+ + ", mServiceSpecificInfo.length=" + (mServiceSpecificInfo == null ? 0
+ : mServiceSpecificInfo.length) + ", mMatchFilter="
+ + (new TlvBufferUtils.TlvIterable(0, 1, mMatchFilter)).toString()
+ + ", mMatchFilter.length=" + (mMatchFilter == null ? 0 : mMatchFilter.length)
+ + ", mSubscribeType=" + mSubscribeType + ", mTtlSec=" + mTtlSec
+ + ", mEnableTerminateNotification=" + mEnableTerminateNotification
+ + ", mMinDistanceMm=" + mMinDistanceMm
+ + ", mMinDistanceMmSet=" + mMinDistanceMmSet
+ + ", mMaxDistanceMm=" + mMaxDistanceMm
+ + ", mMaxDistanceMmSet=" + mMaxDistanceMmSet + "]";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeByteArray(mServiceName);
+ dest.writeByteArray(mServiceSpecificInfo);
+ dest.writeByteArray(mMatchFilter);
+ dest.writeInt(mSubscribeType);
+ dest.writeInt(mTtlSec);
+ dest.writeInt(mEnableTerminateNotification ? 1 : 0);
+ dest.writeInt(mMinDistanceMm);
+ dest.writeInt(mMinDistanceMmSet ? 1 : 0);
+ dest.writeInt(mMaxDistanceMm);
+ dest.writeInt(mMaxDistanceMmSet ? 1 : 0);
+ }
+
+ public static final @android.annotation.NonNull Creator<SubscribeConfig> CREATOR = new Creator<SubscribeConfig>() {
+ @Override
+ public SubscribeConfig[] newArray(int size) {
+ return new SubscribeConfig[size];
+ }
+
+ @Override
+ public SubscribeConfig createFromParcel(Parcel in) {
+ byte[] serviceName = in.createByteArray();
+ byte[] ssi = in.createByteArray();
+ byte[] matchFilter = in.createByteArray();
+ int subscribeType = in.readInt();
+ int ttlSec = in.readInt();
+ boolean enableTerminateNotification = in.readInt() != 0;
+ int minDistanceMm = in.readInt();
+ boolean minDistanceMmSet = in.readInt() != 0;
+ int maxDistanceMm = in.readInt();
+ boolean maxDistanceMmSet = in.readInt() != 0;
+
+ return new SubscribeConfig(serviceName, ssi, matchFilter, subscribeType, ttlSec,
+ enableTerminateNotification, minDistanceMmSet, minDistanceMm, maxDistanceMmSet,
+ maxDistanceMm);
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof SubscribeConfig)) {
+ return false;
+ }
+
+ SubscribeConfig lhs = (SubscribeConfig) o;
+
+ if (!(Arrays.equals(mServiceName, lhs.mServiceName) && Arrays.equals(
+ mServiceSpecificInfo, lhs.mServiceSpecificInfo) && Arrays.equals(mMatchFilter,
+ lhs.mMatchFilter) && mSubscribeType == lhs.mSubscribeType && mTtlSec == lhs.mTtlSec
+ && mEnableTerminateNotification == lhs.mEnableTerminateNotification
+ && mMinDistanceMmSet == lhs.mMinDistanceMmSet
+ && mMaxDistanceMmSet == lhs.mMaxDistanceMmSet)) {
+ return false;
+ }
+
+ if (mMinDistanceMmSet && mMinDistanceMm != lhs.mMinDistanceMm) {
+ return false;
+ }
+
+ if (mMaxDistanceMmSet && mMaxDistanceMm != lhs.mMaxDistanceMm) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hash(Arrays.hashCode(mServiceName),
+ Arrays.hashCode(mServiceSpecificInfo), Arrays.hashCode(mMatchFilter),
+ mSubscribeType, mTtlSec, mEnableTerminateNotification, mMinDistanceMmSet,
+ mMaxDistanceMmSet);
+
+ if (mMinDistanceMmSet) {
+ result = Objects.hash(result, mMinDistanceMm);
+ }
+ if (mMaxDistanceMmSet) {
+ result = Objects.hash(result, mMaxDistanceMm);
+ }
+
+ return result;
+ }
+
+ /**
+ * Verifies that the contents of the SubscribeConfig are valid. Otherwise
+ * throws an IllegalArgumentException.
+ *
+ * @hide
+ */
+ public void assertValid(Characteristics characteristics, boolean rttSupported)
+ throws IllegalArgumentException {
+ WifiAwareUtils.validateServiceName(mServiceName);
+
+ if (!TlvBufferUtils.isValid(mMatchFilter, 0, 1)) {
+ throw new IllegalArgumentException(
+ "Invalid matchFilter configuration - LV fields do not match up to length");
+ }
+ if (mSubscribeType < SUBSCRIBE_TYPE_PASSIVE || mSubscribeType > SUBSCRIBE_TYPE_ACTIVE) {
+ throw new IllegalArgumentException("Invalid subscribeType - " + mSubscribeType);
+ }
+ if (mTtlSec < 0) {
+ throw new IllegalArgumentException("Invalid ttlSec - must be non-negative");
+ }
+
+ if (characteristics != null) {
+ int maxServiceNameLength = characteristics.getMaxServiceNameLength();
+ if (maxServiceNameLength != 0 && mServiceName.length > maxServiceNameLength) {
+ throw new IllegalArgumentException(
+ "Service name longer than supported by device characteristics");
+ }
+ int maxServiceSpecificInfoLength = characteristics.getMaxServiceSpecificInfoLength();
+ if (maxServiceSpecificInfoLength != 0 && mServiceSpecificInfo != null
+ && mServiceSpecificInfo.length > maxServiceSpecificInfoLength) {
+ throw new IllegalArgumentException(
+ "Service specific info longer than supported by device characteristics");
+ }
+ int maxMatchFilterLength = characteristics.getMaxMatchFilterLength();
+ if (maxMatchFilterLength != 0 && mMatchFilter != null
+ && mMatchFilter.length > maxMatchFilterLength) {
+ throw new IllegalArgumentException(
+ "Match filter longer than supported by device characteristics");
+ }
+ }
+
+ if (mMinDistanceMmSet && mMinDistanceMm < 0) {
+ throw new IllegalArgumentException("Minimum distance must be non-negative");
+ }
+ if (mMaxDistanceMmSet && mMaxDistanceMm < 0) {
+ throw new IllegalArgumentException("Maximum distance must be non-negative");
+ }
+ if (mMinDistanceMmSet && mMaxDistanceMmSet && mMaxDistanceMm <= mMinDistanceMm) {
+ throw new IllegalArgumentException(
+ "Maximum distance must be greater than minimum distance");
+ }
+
+ if (!rttSupported && (mMinDistanceMmSet || mMaxDistanceMmSet)) {
+ throw new IllegalArgumentException("Ranging is not supported");
+ }
+ }
+
+ /**
+ * Builder used to build {@link SubscribeConfig} objects.
+ */
+ public static final class Builder {
+ private byte[] mServiceName;
+ private byte[] mServiceSpecificInfo;
+ private byte[] mMatchFilter;
+ private int mSubscribeType = SUBSCRIBE_TYPE_PASSIVE;
+ private int mTtlSec = 0;
+ private boolean mEnableTerminateNotification = true;
+ private boolean mMinDistanceMmSet = false;
+ private int mMinDistanceMm;
+ private boolean mMaxDistanceMmSet = false;
+ private int mMaxDistanceMm;
+
+ /**
+ * Specify the service name of the subscribe session. The actual on-air
+ * value is a 6 byte hashed representation of this string.
+ * <p>
+ * The Service Name is a UTF-8 encoded string from 1 to 255 bytes in length.
+ * The only acceptable single-byte UTF-8 symbols for a Service Name are alphanumeric
+ * values (A-Z, a-z, 0-9), the hyphen ('-'), and the period ('.'). All valid multi-byte
+ * UTF-8 characters are acceptable in a Service Name.
+ * <p>
+ * Must be called - an empty ServiceName is not valid.
+ *
+ * @param serviceName The service name for the subscribe session.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setServiceName(@NonNull String serviceName) {
+ if (serviceName == null) {
+ throw new IllegalArgumentException("Invalid service name - must be non-null");
+ }
+ mServiceName = serviceName.getBytes(StandardCharsets.UTF_8);
+ return this;
+ }
+
+ /**
+ * Specify service specific information for the subscribe session. This is
+ * a free-form byte array available to the application to send
+ * additional information as part of the discovery operation - i.e. it
+ * will not be used to determine whether a publish/subscribe match
+ * occurs.
+ * <p>
+ * Optional. Empty by default.
+ *
+ * @param serviceSpecificInfo A byte-array for the service-specific
+ * information field.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setServiceSpecificInfo(@Nullable byte[] serviceSpecificInfo) {
+ mServiceSpecificInfo = serviceSpecificInfo;
+ return this;
+ }
+
+ /**
+ * The match filter for a subscribe session. Used to determine whether a service
+ * discovery occurred - in addition to relying on the service name.
+ * <p>
+ * Optional. Empty by default.
+ *
+ * @param matchFilter A list of match filter entries (each of which is an arbitrary byte
+ * array).
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setMatchFilter(@Nullable List<byte[]> matchFilter) {
+ mMatchFilter = new TlvBufferUtils.TlvConstructor(0, 1).allocateAndPut(
+ matchFilter).getArray();
+ return this;
+ }
+
+ /**
+ * Sets the type of the subscribe session: active (subscribe packets are
+ * transmitted over-the-air), or passive (no subscribe packets are
+ * transmitted, a match is made against a solicited/active publish
+ * session whose packets are transmitted over-the-air).
+ *
+ * @param subscribeType Subscribe session type:
+ * {@link SubscribeConfig#SUBSCRIBE_TYPE_ACTIVE} or
+ * {@link SubscribeConfig#SUBSCRIBE_TYPE_PASSIVE}.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setSubscribeType(@SubscribeTypes int subscribeType) {
+ if (subscribeType < SUBSCRIBE_TYPE_PASSIVE || subscribeType > SUBSCRIBE_TYPE_ACTIVE) {
+ throw new IllegalArgumentException("Invalid subscribeType - " + subscribeType);
+ }
+ mSubscribeType = subscribeType;
+ return this;
+ }
+
+ /**
+ * Sets the time interval (in seconds) an active (
+ * {@link SubscribeConfig.Builder#setSubscribeType(int)}) subscribe session
+ * will be alive - i.e. broadcasting a packet. When the TTL is reached
+ * an event will be generated for
+ * {@link DiscoverySessionCallback#onSessionTerminated()}.
+ * <p>
+ * Optional. 0 by default - indicating the session doesn't terminate on its own.
+ * Session will be terminated when {@link DiscoverySession#close()} is
+ * called.
+ *
+ * @param ttlSec Lifetime of a subscribe session in seconds.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setTtlSec(int ttlSec) {
+ if (ttlSec < 0) {
+ throw new IllegalArgumentException("Invalid ttlSec - must be non-negative");
+ }
+ mTtlSec = ttlSec;
+ return this;
+ }
+
+ /**
+ * Configure whether a subscribe terminate notification
+ * {@link DiscoverySessionCallback#onSessionTerminated()} is reported
+ * back to the callback.
+ *
+ * @param enable If true the terminate callback will be called when the
+ * subscribe is terminated. Otherwise it will not be called.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setTerminateNotificationEnabled(boolean enable) {
+ mEnableTerminateNotification = enable;
+ return this;
+ }
+
+ /**
+ * Configure the minimum distance to a discovered publisher at which to trigger a discovery
+ * notification. I.e. discovery will be triggered if we've found a matching publisher
+ * (based on the other criteria in this configuration) <b>and</b> the distance to the
+ * publisher is larger than the value specified in this API. Can be used in conjunction with
+ * {@link #setMaxDistanceMm(int)} to specify a geofence, i.e. discovery with min <=
+ * distance <= max.
+ * <p>
+ * For ranging to be used in discovery it must also be enabled on the publisher using
+ * {@link PublishConfig.Builder#setRangingEnabled(boolean)}. However, ranging may
+ * not be available or enabled on the publisher or may be temporarily disabled on either
+ * subscriber or publisher - in such cases discovery will proceed without ranging.
+ * <p>
+ * When ranging is enabled and available on both publisher and subscriber and a service
+ * is discovered based on geofence constraints the
+ * {@link DiscoverySessionCallback#onServiceDiscoveredWithinRange(PeerHandle, byte[], List, int)}
+ * is called, otherwise the
+ * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], List)}
+ * is called.
+ * <p>
+ * The device must support Wi-Fi RTT for this feature to be used. Feature support is checked
+ * as described in {@link android.net.wifi.rtt}.
+ *
+ * @param minDistanceMm Minimum distance, in mm, to the publisher above which to trigger
+ * discovery.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setMinDistanceMm(int minDistanceMm) {
+ mMinDistanceMm = minDistanceMm;
+ mMinDistanceMmSet = true;
+ return this;
+ }
+
+ /**
+ * Configure the maximum distance to a discovered publisher at which to trigger a discovery
+ * notification. I.e. discovery will be triggered if we've found a matching publisher
+ * (based on the other criteria in this configuration) <b>and</b> the distance to the
+ * publisher is smaller than the value specified in this API. Can be used in conjunction
+ * with {@link #setMinDistanceMm(int)} to specify a geofence, i.e. discovery with min <=
+ * distance <= max.
+ * <p>
+ * For ranging to be used in discovery it must also be enabled on the publisher using
+ * {@link PublishConfig.Builder#setRangingEnabled(boolean)}. However, ranging may
+ * not be available or enabled on the publisher or may be temporarily disabled on either
+ * subscriber or publisher - in such cases discovery will proceed without ranging.
+ * <p>
+ * When ranging is enabled and available on both publisher and subscriber and a service
+ * is discovered based on geofence constraints the
+ * {@link DiscoverySessionCallback#onServiceDiscoveredWithinRange(PeerHandle, byte[], List, int)}
+ * is called, otherwise the
+ * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], List)}
+ * is called.
+ * <p>
+ * The device must support Wi-Fi RTT for this feature to be used. Feature support is checked
+ * as described in {@link android.net.wifi.rtt}.
+ *
+ * @param maxDistanceMm Maximum distance, in mm, to the publisher below which to trigger
+ * discovery.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setMaxDistanceMm(int maxDistanceMm) {
+ mMaxDistanceMm = maxDistanceMm;
+ mMaxDistanceMmSet = true;
+ return this;
+ }
+
+ /**
+ * Build {@link SubscribeConfig} given the current requests made on the
+ * builder.
+ */
+ public SubscribeConfig build() {
+ return new SubscribeConfig(mServiceName, mServiceSpecificInfo, mMatchFilter,
+ mSubscribeType, mTtlSec, mEnableTerminateNotification,
+ mMinDistanceMmSet, mMinDistanceMm, mMaxDistanceMmSet, mMaxDistanceMm);
+ }
+ }
+}
diff --git a/android/net/wifi/aware/SubscribeDiscoverySession.java b/android/net/wifi/aware/SubscribeDiscoverySession.java
new file mode 100644
index 0000000..ca88a90
--- /dev/null
+++ b/android/net/wifi/aware/SubscribeDiscoverySession.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2016 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.aware;
+
+import android.annotation.NonNull;
+import android.util.Log;
+
+/**
+ * A class representing a Aware subscribe session. Created when
+ * {@link WifiAwareSession#subscribe(SubscribeConfig,
+ * DiscoverySessionCallback, android.os.Handler)}
+ * is called and a discovery session is created and returned in
+ * {@link DiscoverySessionCallback#onSubscribeStarted(SubscribeDiscoverySession)}.
+ * See baseline functionality of all discovery sessions in {@link DiscoverySession}.
+ * This object allows updating an existing/running subscribe discovery session using
+ * {@link #updateSubscribe(SubscribeConfig)}.
+ */
+public class SubscribeDiscoverySession extends DiscoverySession {
+ private static final String TAG = "SubscribeDiscSession";
+
+ /**
+ * {@hide}
+ */
+ public SubscribeDiscoverySession(WifiAwareManager manager, int clientId,
+ int sessionId) {
+ super(manager, clientId, sessionId);
+ }
+
+ /**
+ * Re-configure the currently active subscribe session. The
+ * {@link DiscoverySessionCallback} is not replaced - the same listener used
+ * at creation is still used. The results of the configuration are returned using
+ * {@link DiscoverySessionCallback}:
+ * <ul>
+ * <li>{@link DiscoverySessionCallback#onSessionConfigUpdated()}: configuration
+ * update succeeded.
+ * <li>{@link DiscoverySessionCallback#onSessionConfigFailed()}: configuration
+ * update failed. The subscribe discovery session is still running using its previous
+ * configuration (i.e. update failure does not terminate the session).
+ * </ul>
+ *
+ * @param subscribeConfig The new discovery subscribe session configuration
+ * ({@link SubscribeConfig}).
+ */
+ public void updateSubscribe(@NonNull SubscribeConfig subscribeConfig) {
+ if (mTerminated) {
+ Log.w(TAG, "updateSubscribe: called on terminated session");
+ return;
+ } else {
+ WifiAwareManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.w(TAG, "updateSubscribe: called post GC on WifiAwareManager");
+ return;
+ }
+
+ mgr.updateSubscribe(mClientId, mSessionId, subscribeConfig);
+ }
+ }
+}
diff --git a/android/net/wifi/aware/TlvBufferUtils.java b/android/net/wifi/aware/TlvBufferUtils.java
new file mode 100644
index 0000000..b3b5b29
--- /dev/null
+++ b/android/net/wifi/aware/TlvBufferUtils.java
@@ -0,0 +1,670 @@
+/*
+ * Copyright (C) 2016 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.aware;
+
+import android.annotation.Nullable;
+
+import libcore.io.Memory;
+
+import java.nio.BufferOverflowException;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+/**
+ * Utility class to construct and parse byte arrays using the TLV format -
+ * Type/Length/Value format. The utilities accept a configuration of the size of
+ * the Type field and the Length field. A Type field size of 0 is allowed -
+ * allowing usage for LV (no T) array formats.
+ *
+ * @hide
+ */
+public class TlvBufferUtils {
+ private TlvBufferUtils() {
+ // no reason to ever create this class
+ }
+
+ /**
+ * Utility class to construct byte arrays using the TLV format -
+ * Type/Length/Value.
+ * <p>
+ * A constructor is created specifying the size of the Type (T) and Length
+ * (L) fields. A specification of zero size T field is allowed - resulting
+ * in LV type format.
+ * <p>
+ * The byte array is either provided (using
+ * {@link TlvConstructor#wrap(byte[])}) or allocated (using
+ * {@link TlvConstructor#allocate(int)}).
+ * <p>
+ * Values are added to the structure using the {@code TlvConstructor.put*()}
+ * methods.
+ * <p>
+ * The final byte array is obtained using {@link TlvConstructor#getArray()}.
+ */
+ public static class TlvConstructor {
+ private int mTypeSize;
+ private int mLengthSize;
+ private ByteOrder mByteOrder = ByteOrder.BIG_ENDIAN;
+
+ private byte[] mArray;
+ private int mArrayLength;
+ private int mPosition;
+
+ /**
+ * Define a TLV constructor with the specified size of the Type (T) and
+ * Length (L) fields.
+ *
+ * @param typeSize Number of bytes used for the Type (T) field. Values
+ * of 0, 1, or 2 bytes are allowed. A specification of 0
+ * bytes implies that the field being constructed has the LV
+ * format rather than the TLV format.
+ * @param lengthSize Number of bytes used for the Length (L) field.
+ * Values of 1 or 2 bytes are allowed.
+ */
+ public TlvConstructor(int typeSize, int lengthSize) {
+ if (typeSize < 0 || typeSize > 2 || lengthSize <= 0 || lengthSize > 2) {
+ throw new IllegalArgumentException(
+ "Invalid sizes - typeSize=" + typeSize + ", lengthSize=" + lengthSize);
+ }
+ mTypeSize = typeSize;
+ mLengthSize = lengthSize;
+ mPosition = 0;
+ }
+
+ /**
+ * Configure the TLV constructor to use a particular byte order. Should be
+ * {@link ByteOrder#BIG_ENDIAN} (the default at construction) or
+ * {@link ByteOrder#LITTLE_ENDIAN}.
+ *
+ * @return The constructor to facilitate chaining
+ * {@code ctr.putXXX(..).putXXX(..)}.
+ */
+ public TlvConstructor setByteOrder(ByteOrder byteOrder) {
+ mByteOrder = byteOrder;
+ return this;
+ }
+
+ /**
+ * Set the byte array to be used to construct the TLV.
+ *
+ * @param array Byte array to be formatted.
+ * @return The constructor to facilitate chaining
+ * {@code ctr.putXXX(..).putXXX(..)}.
+ */
+ public TlvConstructor wrap(@Nullable byte[] array) {
+ mArray = array;
+ mArrayLength = (array == null) ? 0 : array.length;
+ mPosition = 0;
+ return this;
+ }
+
+ /**
+ * Allocates a new byte array to be used ot construct a TLV.
+ *
+ * @param capacity The size of the byte array to be allocated.
+ * @return The constructor to facilitate chaining
+ * {@code ctr.putXXX(..).putXXX(..)}.
+ */
+ public TlvConstructor allocate(int capacity) {
+ mArray = new byte[capacity];
+ mArrayLength = capacity;
+ mPosition = 0;
+ return this;
+ }
+
+ /**
+ * Creates a TLV array (of the previously specified Type and Length sizes) from the input
+ * list. Allocates an array matching the contents (and required Type and Length
+ * fields), copies the contents, and set the Length fields. The Type field is set to 0.
+ *
+ * @param list A list of fields to be added to the TLV buffer.
+ * @return The constructor of the TLV.
+ */
+ public TlvConstructor allocateAndPut(@Nullable List<byte[]> list) {
+ if (list != null) {
+ int size = 0;
+ for (byte[] field : list) {
+ size += mTypeSize + mLengthSize;
+ if (field != null) {
+ size += field.length;
+ }
+ }
+ allocate(size);
+ for (byte[] field : list) {
+ putByteArray(0, field);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Copies a byte into the TLV with the indicated type. For an LV
+ * formatted structure (i.e. typeLength=0 in {@link TlvConstructor
+ * TlvConstructor(int, int)} ) the type field is ignored.
+ *
+ * @param type The value to be placed into the Type field.
+ * @param b The byte to be inserted into the structure.
+ * @return The constructor to facilitate chaining
+ * {@code ctr.putXXX(..).putXXX(..)}.
+ */
+ public TlvConstructor putByte(int type, byte b) {
+ checkLength(1);
+ addHeader(type, 1);
+ mArray[mPosition++] = b;
+ return this;
+ }
+
+ /**
+ * Copies a raw byte into the TLV buffer - without a type or a length.
+ *
+ * @param b The byte to be inserted into the structure.
+ * @return The constructor to facilitate chaining {@code cts.putXXX(..).putXXX(..)}.
+ */
+ public TlvConstructor putRawByte(byte b) {
+ checkRawLength(1);
+ mArray[mPosition++] = b;
+ return this;
+ }
+
+ /**
+ * Copies a byte array into the TLV with the indicated type. For an LV
+ * formatted structure (i.e. typeLength=0 in {@link TlvConstructor
+ * TlvConstructor(int, int)} ) the type field is ignored.
+ *
+ * @param type The value to be placed into the Type field.
+ * @param array The array to be copied into the TLV structure.
+ * @param offset Start copying from the array at the specified offset.
+ * @param length Copy the specified number (length) of bytes from the
+ * array.
+ * @return The constructor to facilitate chaining
+ * {@code ctr.putXXX(..).putXXX(..)}.
+ */
+ public TlvConstructor putByteArray(int type, @Nullable byte[] array, int offset,
+ int length) {
+ checkLength(length);
+ addHeader(type, length);
+ if (length != 0) {
+ System.arraycopy(array, offset, mArray, mPosition, length);
+ }
+ mPosition += length;
+ return this;
+ }
+
+ /**
+ * Copies a byte array into the TLV with the indicated type. For an LV
+ * formatted structure (i.e. typeLength=0 in {@link TlvConstructor
+ * TlvConstructor(int, int)} ) the type field is ignored.
+ *
+ * @param type The value to be placed into the Type field.
+ * @param array The array to be copied (in full) into the TLV structure.
+ * @return The constructor to facilitate chaining
+ * {@code ctr.putXXX(..).putXXX(..)}.
+ */
+ public TlvConstructor putByteArray(int type, @Nullable byte[] array) {
+ return putByteArray(type, array, 0, (array == null) ? 0 : array.length);
+ }
+
+ /**
+ * Copies a byte array into the TLV - without a type or a length.
+ *
+ * @param array The array to be copied (in full) into the TLV structure.
+ * @return The constructor to facilitate chaining
+ * {@code ctr.putXXX(..).putXXX(..)}.
+ */
+ public TlvConstructor putRawByteArray(@Nullable byte[] array) {
+ if (array == null) return this;
+
+ checkRawLength(array.length);
+ System.arraycopy(array, 0, mArray, mPosition, array.length);
+ mPosition += array.length;
+ return this;
+ }
+
+ /**
+ * Places a zero length element (i.e. Length field = 0) into the TLV.
+ * For an LV formatted structure (i.e. typeLength=0 in
+ * {@link TlvConstructor TlvConstructor(int, int)} ) the type field is
+ * ignored.
+ *
+ * @param type The value to be placed into the Type field.
+ * @return The constructor to facilitate chaining
+ * {@code ctr.putXXX(..).putXXX(..)}.
+ */
+ public TlvConstructor putZeroLengthElement(int type) {
+ checkLength(0);
+ addHeader(type, 0);
+ return this;
+ }
+
+ /**
+ * Copies short into the TLV with the indicated type. For an LV
+ * formatted structure (i.e. typeLength=0 in {@link TlvConstructor
+ * TlvConstructor(int, int)} ) the type field is ignored.
+ *
+ * @param type The value to be placed into the Type field.
+ * @param data The short to be inserted into the structure.
+ * @return The constructor to facilitate chaining
+ * {@code ctr.putXXX(..).putXXX(..)}.
+ */
+ public TlvConstructor putShort(int type, short data) {
+ checkLength(2);
+ addHeader(type, 2);
+ Memory.pokeShort(mArray, mPosition, data, mByteOrder);
+ mPosition += 2;
+ return this;
+ }
+
+ /**
+ * Copies integer into the TLV with the indicated type. For an LV
+ * formatted structure (i.e. typeLength=0 in {@link TlvConstructor
+ * TlvConstructor(int, int)} ) the type field is ignored.
+ *
+ * @param type The value to be placed into the Type field.
+ * @param data The integer to be inserted into the structure.
+ * @return The constructor to facilitate chaining
+ * {@code ctr.putXXX(..).putXXX(..)}.
+ */
+ public TlvConstructor putInt(int type, int data) {
+ checkLength(4);
+ addHeader(type, 4);
+ Memory.pokeInt(mArray, mPosition, data, mByteOrder);
+ mPosition += 4;
+ return this;
+ }
+
+ /**
+ * Copies a String's byte representation into the TLV with the indicated
+ * type. For an LV formatted structure (i.e. typeLength=0 in
+ * {@link TlvConstructor TlvConstructor(int, int)} ) the type field is
+ * ignored.
+ *
+ * @param type The value to be placed into the Type field.
+ * @param data The string whose bytes are to be inserted into the
+ * structure.
+ * @return The constructor to facilitate chaining
+ * {@code ctr.putXXX(..).putXXX(..)}.
+ */
+ public TlvConstructor putString(int type, @Nullable String data) {
+ byte[] bytes = null;
+ int length = 0;
+ if (data != null) {
+ bytes = data.getBytes();
+ length = bytes.length;
+ }
+ return putByteArray(type, bytes, 0, length);
+ }
+
+ /**
+ * Returns the constructed TLV formatted byte-array. This array is a copy of the wrapped
+ * or allocated array - truncated to just the significant bytes - i.e. those written into
+ * the (T)LV.
+ *
+ * @return The byte array containing the TLV formatted structure.
+ */
+ public byte[] getArray() {
+ return Arrays.copyOf(mArray, getActualLength());
+ }
+
+ /**
+ * Returns the size of the TLV formatted portion of the wrapped or
+ * allocated byte array. The array itself is returned with
+ * {@link TlvConstructor#getArray()}.
+ *
+ * @return The size of the TLV formatted portion of the byte array.
+ */
+ private int getActualLength() {
+ return mPosition;
+ }
+
+ private void checkLength(int dataLength) {
+ if (mPosition + mTypeSize + mLengthSize + dataLength > mArrayLength) {
+ throw new BufferOverflowException();
+ }
+ }
+
+ private void checkRawLength(int dataLength) {
+ if (mPosition + dataLength > mArrayLength) {
+ throw new BufferOverflowException();
+ }
+ }
+
+ private void addHeader(int type, int length) {
+ if (mTypeSize == 1) {
+ mArray[mPosition] = (byte) type;
+ } else if (mTypeSize == 2) {
+ Memory.pokeShort(mArray, mPosition, (short) type, mByteOrder);
+ }
+ mPosition += mTypeSize;
+
+ if (mLengthSize == 1) {
+ mArray[mPosition] = (byte) length;
+ } else if (mLengthSize == 2) {
+ Memory.pokeShort(mArray, mPosition, (short) length, mByteOrder);
+ }
+ mPosition += mLengthSize;
+ }
+ }
+
+ /**
+ * Utility class used when iterating over a TLV formatted byte-array. Use
+ * {@link TlvIterable} to iterate over array. A {@link TlvElement}
+ * represents each entry in a TLV formatted byte-array.
+ */
+ public static class TlvElement {
+ /**
+ * The Type (T) field of the current TLV element. Note that for LV
+ * formatted byte-arrays (i.e. TLV whose Type/T size is 0) the value of
+ * this field is undefined.
+ */
+ public int type;
+
+ /**
+ * The Length (L) field of the current TLV element.
+ */
+ public int length;
+
+ /**
+ * Control of the endianess of the TLV element - true for big-endian, false for little-
+ * endian.
+ */
+ public ByteOrder byteOrder = ByteOrder.BIG_ENDIAN;
+
+ /**
+ * The Value (V) field - a raw byte array representing the current TLV
+ * element where the entry starts at {@link TlvElement#offset}.
+ */
+ private byte[] mRefArray;
+
+ /**
+ * The offset to be used into {@link TlvElement#mRefArray} to access the
+ * raw data representing the current TLV element.
+ */
+ public int offset;
+
+ private TlvElement(int type, int length, @Nullable byte[] refArray, int offset) {
+ this.type = type;
+ this.length = length;
+ mRefArray = refArray;
+ this.offset = offset;
+
+ if (offset + length > refArray.length) {
+ throw new BufferOverflowException();
+ }
+ }
+
+ /**
+ * Return the raw byte array of the Value (V) field.
+ *
+ * @return The Value (V) field as a byte array.
+ */
+ public byte[] getRawData() {
+ return Arrays.copyOfRange(mRefArray, offset, offset + length);
+ }
+
+ /**
+ * Utility function to return a byte representation of a TLV element of
+ * length 1. Note: an attempt to call this function on a TLV item whose
+ * {@link TlvElement#length} is != 1 will result in an exception.
+ *
+ * @return byte representation of current TLV element.
+ */
+ public byte getByte() {
+ if (length != 1) {
+ throw new IllegalArgumentException(
+ "Accesing a byte from a TLV element of length " + length);
+ }
+ return mRefArray[offset];
+ }
+
+ /**
+ * Utility function to return a short representation of a TLV element of
+ * length 2. Note: an attempt to call this function on a TLV item whose
+ * {@link TlvElement#length} is != 2 will result in an exception.
+ *
+ * @return short representation of current TLV element.
+ */
+ public short getShort() {
+ if (length != 2) {
+ throw new IllegalArgumentException(
+ "Accesing a short from a TLV element of length " + length);
+ }
+ return Memory.peekShort(mRefArray, offset, byteOrder);
+ }
+
+ /**
+ * Utility function to return an integer representation of a TLV element
+ * of length 4. Note: an attempt to call this function on a TLV item
+ * whose {@link TlvElement#length} is != 4 will result in an exception.
+ *
+ * @return integer representation of current TLV element.
+ */
+ public int getInt() {
+ if (length != 4) {
+ throw new IllegalArgumentException(
+ "Accesing an int from a TLV element of length " + length);
+ }
+ return Memory.peekInt(mRefArray, offset, byteOrder);
+ }
+
+ /**
+ * Utility function to return a String representation of a TLV element.
+ *
+ * @return String repersentation of the current TLV element.
+ */
+ public String getString() {
+ return new String(mRefArray, offset, length);
+ }
+ }
+
+ /**
+ * Utility class to iterate over a TLV formatted byte-array.
+ */
+ public static class TlvIterable implements Iterable<TlvElement> {
+ private int mTypeSize;
+ private int mLengthSize;
+ private ByteOrder mByteOrder = ByteOrder.BIG_ENDIAN;
+ private byte[] mArray;
+ private int mArrayLength;
+
+ /**
+ * Constructs a TlvIterable object - specifying the format of the TLV
+ * (the sizes of the Type and Length fields), and the byte array whose
+ * data is to be parsed.
+ *
+ * @param typeSize Number of bytes used for the Type (T) field. Valid
+ * values are 0 (i.e. indicating the format is LV rather than
+ * TLV), 1, and 2 bytes.
+ * @param lengthSize Number of bytes used for the Length (L) field.
+ * Values values are 1 or 2 bytes.
+ * @param array The TLV formatted byte-array to parse.
+ */
+ public TlvIterable(int typeSize, int lengthSize, @Nullable byte[] array) {
+ if (typeSize < 0 || typeSize > 2 || lengthSize <= 0 || lengthSize > 2) {
+ throw new IllegalArgumentException(
+ "Invalid sizes - typeSize=" + typeSize + ", lengthSize=" + lengthSize);
+ }
+ mTypeSize = typeSize;
+ mLengthSize = lengthSize;
+ mArray = array;
+ mArrayLength = (array == null) ? 0 : array.length;
+ }
+
+ /**
+ * Configure the TLV iterator to use little-endian byte ordering.
+ */
+ public void setByteOrder(ByteOrder byteOrder) {
+ mByteOrder = byteOrder;
+ }
+
+ /**
+ * Prints out a parsed representation of the TLV-formatted byte array.
+ * Whenever possible bytes, shorts, and integer are printed out (for
+ * fields whose length is 1, 2, or 4 respectively).
+ */
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+
+ builder.append("[");
+ boolean first = true;
+ for (TlvElement tlv : this) {
+ if (!first) {
+ builder.append(",");
+ }
+ first = false;
+ builder.append(" (");
+ if (mTypeSize != 0) {
+ builder.append("T=" + tlv.type + ",");
+ }
+ builder.append("L=" + tlv.length + ") ");
+ if (tlv.length == 0) {
+ builder.append("<null>");
+ } else if (tlv.length == 1) {
+ builder.append(tlv.getByte());
+ } else if (tlv.length == 2) {
+ builder.append(tlv.getShort());
+ } else if (tlv.length == 4) {
+ builder.append(tlv.getInt());
+ } else {
+ builder.append("<bytes>");
+ }
+ if (tlv.length != 0) {
+ builder.append(" (S='" + tlv.getString() + "')");
+ }
+ }
+ builder.append("]");
+
+ return builder.toString();
+ }
+
+ /**
+ * Returns a List with the raw contents (no types) of the iterator.
+ */
+ public List<byte[]> toList() {
+ List<byte[]> list = new ArrayList<>();
+ for (TlvElement tlv : this) {
+ list.add(Arrays.copyOfRange(tlv.mRefArray, tlv.offset, tlv.offset + tlv.length));
+ }
+
+ return list;
+ }
+
+ /**
+ * Returns an iterator to step through a TLV formatted byte-array. The
+ * individual elements returned by the iterator are {@link TlvElement}.
+ */
+ @Override
+ public Iterator<TlvElement> iterator() {
+ return new Iterator<TlvElement>() {
+ private int mOffset = 0;
+
+ @Override
+ public boolean hasNext() {
+ return mOffset < mArrayLength;
+ }
+
+ @Override
+ public TlvElement next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+
+ int type = 0;
+ if (mTypeSize == 1) {
+ type = mArray[mOffset];
+ } else if (mTypeSize == 2) {
+ type = Memory.peekShort(mArray, mOffset, mByteOrder);
+ }
+ mOffset += mTypeSize;
+
+ int length = 0;
+ if (mLengthSize == 1) {
+ length = mArray[mOffset];
+ } else if (mLengthSize == 2) {
+ length = Memory.peekShort(mArray, mOffset, mByteOrder);
+ }
+ mOffset += mLengthSize;
+
+ TlvElement tlv = new TlvElement(type, length, mArray, mOffset);
+ tlv.byteOrder = mByteOrder;
+ mOffset += length;
+ return tlv;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ }
+
+ /**
+ * Validates that a (T)LV array is constructed correctly. I.e. that its specified Length
+ * fields correctly fill the specified length (and do not overshoot). Uses big-endian
+ * byte ordering.
+ *
+ * @param array The (T)LV array to verify.
+ * @param typeSize The size (in bytes) of the type field. Valid values are 0, 1, or 2.
+ * @param lengthSize The size (in bytes) of the length field. Valid values are 1 or 2.
+ * @return A boolean indicating whether the array is valid (true) or invalid (false).
+ */
+ public static boolean isValid(@Nullable byte[] array, int typeSize, int lengthSize) {
+ return isValidEndian(array, typeSize, lengthSize, ByteOrder.BIG_ENDIAN);
+ }
+
+ /**
+ * Validates that a (T)LV array is constructed correctly. I.e. that its specified Length
+ * fields correctly fill the specified length (and do not overshoot).
+ *
+ * @param array The (T)LV array to verify.
+ * @param typeSize The size (in bytes) of the type field. Valid values are 0, 1, or 2.
+ * @param lengthSize The size (in bytes) of the length field. Valid values are 1 or 2.
+ * @param byteOrder The endianness of the byte array: {@link ByteOrder#BIG_ENDIAN} or
+ * {@link ByteOrder#LITTLE_ENDIAN}.
+ * @return A boolean indicating whether the array is valid (true) or invalid (false).
+ */
+ public static boolean isValidEndian(@Nullable byte[] array, int typeSize, int lengthSize,
+ ByteOrder byteOrder) {
+ if (typeSize < 0 || typeSize > 2) {
+ throw new IllegalArgumentException(
+ "Invalid arguments - typeSize must be 0, 1, or 2: typeSize=" + typeSize);
+ }
+ if (lengthSize <= 0 || lengthSize > 2) {
+ throw new IllegalArgumentException(
+ "Invalid arguments - lengthSize must be 1 or 2: lengthSize=" + lengthSize);
+ }
+ if (array == null) {
+ return true;
+ }
+
+ int nextTlvIndex = 0;
+ while (nextTlvIndex + typeSize + lengthSize <= array.length) {
+ nextTlvIndex += typeSize;
+ if (lengthSize == 1) {
+ nextTlvIndex += lengthSize + array[nextTlvIndex];
+ } else {
+ nextTlvIndex += lengthSize + Memory.peekShort(array, nextTlvIndex, byteOrder);
+ }
+ }
+
+ return nextTlvIndex == array.length;
+ }
+}
diff --git a/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java b/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java
new file mode 100644
index 0000000..9164d04
--- /dev/null
+++ b/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java
@@ -0,0 +1,231 @@
+/*
+ * 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 android.net.wifi.aware;
+
+import android.net.NetworkSpecifier;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import libcore.util.HexEncoding;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.StringJoiner;
+
+/**
+ * A network specifier object used to represent the capabilities of an network agent. A collection
+ * of multiple WifiAwareNetworkSpecifier objects whose matching critiera (satisfiedBy) is an OR:
+ * a match on any of the network specifiers in the collection is a match.
+ *
+ * This class is not intended for use in network requests.
+ *
+ * @hide
+ */
+public class WifiAwareAgentNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+ private static final String TAG = "WifiAwareAgentNs";
+
+ private static final boolean VDBG = false; // STOPSHIP if true
+
+ private Set<ByteArrayWrapper> mNetworkSpecifiers = new HashSet<>();
+ private MessageDigest mDigester;
+
+ public WifiAwareAgentNetworkSpecifier() {
+ initialize();
+ }
+
+ public WifiAwareAgentNetworkSpecifier(WifiAwareNetworkSpecifier ns) {
+ initialize();
+ mNetworkSpecifiers.add(convert(ns));
+ }
+
+ public WifiAwareAgentNetworkSpecifier(WifiAwareNetworkSpecifier[] nss) {
+ initialize();
+ for (WifiAwareNetworkSpecifier ns : nss) {
+ mNetworkSpecifiers.add(convert(ns));
+ }
+ }
+
+ public boolean isEmpty() {
+ return mNetworkSpecifiers.isEmpty();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeArray(mNetworkSpecifiers.toArray());
+ }
+
+ public static final @android.annotation.NonNull Creator<WifiAwareAgentNetworkSpecifier> CREATOR =
+ new Creator<WifiAwareAgentNetworkSpecifier>() {
+ @Override
+ public WifiAwareAgentNetworkSpecifier createFromParcel(Parcel in) {
+ WifiAwareAgentNetworkSpecifier agentNs = new WifiAwareAgentNetworkSpecifier();
+ Object[] objs = in.readArray(null);
+ for (Object obj : objs) {
+ agentNs.mNetworkSpecifiers.add((ByteArrayWrapper) obj);
+ }
+ return agentNs;
+ }
+
+ @Override
+ public WifiAwareAgentNetworkSpecifier[] newArray(int size) {
+ return new WifiAwareAgentNetworkSpecifier[size];
+ }
+ };
+
+ @Override
+ public int hashCode() {
+ return mNetworkSpecifiers.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof WifiAwareAgentNetworkSpecifier)) {
+ return false;
+ }
+ return mNetworkSpecifiers.equals(((WifiAwareAgentNetworkSpecifier) obj).mNetworkSpecifiers);
+ }
+
+ @Override
+ public String toString() {
+ StringJoiner sj = new StringJoiner(",");
+ for (ByteArrayWrapper baw: mNetworkSpecifiers) {
+ sj.add(baw.toString());
+ }
+ return sj.toString();
+ }
+
+ @Override
+ public boolean satisfiedBy(NetworkSpecifier other) {
+ if (!(other instanceof WifiAwareAgentNetworkSpecifier)) {
+ return false;
+ }
+ WifiAwareAgentNetworkSpecifier otherNs = (WifiAwareAgentNetworkSpecifier) other;
+
+ // called as old.satifiedBy(new): satisfied if old contained in new
+ for (ByteArrayWrapper baw: mNetworkSpecifiers) {
+ if (!otherNs.mNetworkSpecifiers.contains(baw)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public boolean satisfiesAwareNetworkSpecifier(WifiAwareNetworkSpecifier ns) {
+ if (VDBG) Log.v(TAG, "satisfiesAwareNetworkSpecifier: ns=" + ns);
+ ByteArrayWrapper nsBytes = convert(ns);
+ return mNetworkSpecifiers.contains(nsBytes);
+ }
+
+ @Override
+ public void assertValidFromUid(int requestorUid) {
+ throw new SecurityException(
+ "WifiAwareAgentNetworkSpecifier should not be used in network requests");
+ }
+
+ @Override
+ public NetworkSpecifier redact() {
+ return null;
+ }
+
+ private void initialize() {
+ try {
+ mDigester = MessageDigest.getInstance("SHA-256");
+ } catch (NoSuchAlgorithmException e) {
+ Log.e(TAG, "Can not instantiate a SHA-256 digester!? Will match nothing.");
+ return;
+ }
+ }
+
+ private ByteArrayWrapper convert(WifiAwareNetworkSpecifier ns) {
+ if (mDigester == null) {
+ return null;
+ }
+
+ Parcel parcel = Parcel.obtain();
+ ns.writeToParcel(parcel, 0);
+ byte[] bytes = parcel.marshall();
+
+ mDigester.reset();
+ mDigester.update(bytes);
+ return new ByteArrayWrapper(mDigester.digest());
+ }
+
+ private static class ByteArrayWrapper implements Parcelable {
+ private byte[] mData;
+
+ ByteArrayWrapper(byte[] data) {
+ mData = data;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(mData);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof ByteArrayWrapper)) {
+ return false;
+ }
+ return Arrays.equals(((ByteArrayWrapper) obj).mData, mData);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBlob(mData);
+ }
+
+ public static final @android.annotation.NonNull Creator<ByteArrayWrapper> CREATOR =
+ new Creator<ByteArrayWrapper>() {
+ @Override
+ public ByteArrayWrapper createFromParcel(Parcel in) {
+ return new ByteArrayWrapper(in.readBlob());
+ }
+
+ @Override
+ public ByteArrayWrapper[] newArray(int size) {
+ return new ByteArrayWrapper[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return new String(HexEncoding.encode(mData));
+ }
+ }
+}
diff --git a/android/net/wifi/aware/WifiAwareManager.java b/android/net/wifi/aware/WifiAwareManager.java
new file mode 100644
index 0000000..41a412b
--- /dev/null
+++ b/android/net/wifi/aware/WifiAwareManager.java
@@ -0,0 +1,814 @@
+/*
+ * Copyright (C) 2016 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.aware;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+import libcore.util.HexEncoding;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.nio.BufferOverflowException;
+import java.util.List;
+
+/**
+ * This class provides the primary API for managing Wi-Fi Aware operations:
+ * discovery and peer-to-peer data connections.
+ * <p>
+ * The class provides access to:
+ * <ul>
+ * <li>Initialize a Aware cluster (peer-to-peer synchronization). Refer to
+ * {@link #attach(AttachCallback, Handler)}.
+ * <li>Create discovery sessions (publish or subscribe sessions). Refer to
+ * {@link WifiAwareSession#publish(PublishConfig, DiscoverySessionCallback, Handler)} and
+ * {@link WifiAwareSession#subscribe(SubscribeConfig, DiscoverySessionCallback, Handler)}.
+ * <li>Create a Aware network specifier to be used with
+ * {@link ConnectivityManager#requestNetwork(NetworkRequest, ConnectivityManager.NetworkCallback)}
+ * to set-up a Aware connection with a peer. Refer to {@link WifiAwareNetworkSpecifier.Builder}.
+ * </ul>
+ * <p>
+ * Aware may not be usable when Wi-Fi is disabled (and other conditions). To validate that
+ * the functionality is available use the {@link #isAvailable()} function. To track
+ * changes in Aware usability register for the {@link #ACTION_WIFI_AWARE_STATE_CHANGED}
+ * broadcast. Note that this broadcast is not sticky - you should register for it and then
+ * check the above API to avoid a race condition.
+ * <p>
+ * An application must use {@link #attach(AttachCallback, Handler)} to initialize a
+ * Aware cluster - before making any other Aware operation. Aware cluster membership is a
+ * device-wide operation - the API guarantees that the device is in a cluster or joins a
+ * Aware cluster (or starts one if none can be found). Information about attach success (or
+ * failure) are returned in callbacks of {@link AttachCallback}. Proceed with Aware
+ * discovery or connection setup only after receiving confirmation that Aware attach
+ * succeeded - {@link AttachCallback#onAttached(WifiAwareSession)}. When an
+ * application is finished using Aware it <b>must</b> use the
+ * {@link WifiAwareSession#close()} API to indicate to the Aware service that the device
+ * may detach from the Aware cluster. The device will actually disable Aware once the last
+ * application detaches.
+ * <p>
+ * Once a Aware attach is confirmed use the
+ * {@link WifiAwareSession#publish(PublishConfig, DiscoverySessionCallback, Handler)}
+ * or
+ * {@link WifiAwareSession#subscribe(SubscribeConfig, DiscoverySessionCallback,
+ * Handler)} to create publish or subscribe Aware discovery sessions. Events are called on the
+ * provided callback object {@link DiscoverySessionCallback}. Specifically, the
+ * {@link DiscoverySessionCallback#onPublishStarted(PublishDiscoverySession)}
+ * and
+ * {@link DiscoverySessionCallback#onSubscribeStarted(
+ *SubscribeDiscoverySession)}
+ * return {@link PublishDiscoverySession} and
+ * {@link SubscribeDiscoverySession}
+ * objects respectively on which additional session operations can be performed, e.g. updating
+ * the session {@link PublishDiscoverySession#updatePublish(PublishConfig)} and
+ * {@link SubscribeDiscoverySession#updateSubscribe(SubscribeConfig)}. Sessions can
+ * also be used to send messages using the
+ * {@link DiscoverySession#sendMessage(PeerHandle, int, byte[])} APIs. When an
+ * application is finished with a discovery session it <b>must</b> terminate it using the
+ * {@link DiscoverySession#close()} API.
+ * <p>
+ * Creating connections between Aware devices is managed by the standard
+ * {@link ConnectivityManager#requestNetwork(NetworkRequest,
+ * ConnectivityManager.NetworkCallback)}.
+ * The {@link NetworkRequest} object should be constructed with:
+ * <ul>
+ * <li>{@link NetworkRequest.Builder#addTransportType(int)} of
+ * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
+ * <li>{@link NetworkRequest.Builder#setNetworkSpecifier(String)} using
+ * {@link WifiAwareNetworkSpecifier.Builder}.
+ * </ul>
+ */
+@SystemService(Context.WIFI_AWARE_SERVICE)
+public class WifiAwareManager {
+ private static final String TAG = "WifiAwareManager";
+ private static final boolean DBG = false;
+ private static final boolean VDBG = false; // STOPSHIP if true
+
+ /**
+ * Broadcast intent action to indicate that the state of Wi-Fi Aware availability has changed.
+ * Use the {@link #isAvailable()} to query the current status.
+ * This broadcast is <b>not</b> sticky, use the {@link #isAvailable()} API after registering
+ * the broadcast to check the current state of Wi-Fi Aware.
+ * <p>Note: The broadcast is only delivered to registered receivers - no manifest registered
+ * components will be launched.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_WIFI_AWARE_STATE_CHANGED =
+ "android.net.wifi.aware.action.WIFI_AWARE_STATE_CHANGED";
+
+ /** @hide */
+ @IntDef({
+ WIFI_AWARE_DATA_PATH_ROLE_INITIATOR, WIFI_AWARE_DATA_PATH_ROLE_RESPONDER})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DataPathRole {
+ }
+
+ /**
+ * Connection creation role is that of INITIATOR. Used to create a network specifier string
+ * when requesting a Aware network.
+ *
+ * @see WifiAwareSession#createNetworkSpecifierOpen(int, byte[])
+ * @see WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)
+ */
+ public static final int WIFI_AWARE_DATA_PATH_ROLE_INITIATOR = 0;
+
+ /**
+ * Connection creation role is that of RESPONDER. Used to create a network specifier string
+ * when requesting a Aware network.
+ *
+ * @see WifiAwareSession#createNetworkSpecifierOpen(int, byte[])
+ * @see WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)
+ */
+ public static final int WIFI_AWARE_DATA_PATH_ROLE_RESPONDER = 1;
+
+ private final Context mContext;
+ private final IWifiAwareManager mService;
+
+ private final Object mLock = new Object(); // lock access to the following vars
+
+ /** @hide */
+ public WifiAwareManager(Context context, IWifiAwareManager service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Returns the current status of Aware API: whether or not Aware is available. To track
+ * changes in the state of Aware API register for the
+ * {@link #ACTION_WIFI_AWARE_STATE_CHANGED} broadcast.
+ *
+ * @return A boolean indicating whether the app can use the Aware API at this time (true) or
+ * not (false).
+ */
+ public boolean isAvailable() {
+ try {
+ return mService.isUsageEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the characteristics of the Wi-Fi Aware interface: a set of parameters which specify
+ * limitations on configurations, e.g. the maximum service name length.
+ *
+ * @return An object specifying configuration limitations of Aware.
+ */
+ public Characteristics getCharacteristics() {
+ try {
+ return mService.getCharacteristics();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Attach to the Wi-Fi Aware service - enabling the application to create discovery sessions or
+ * create connections to peers. The device will attach to an existing cluster if it can find
+ * one or create a new cluster (if it is the first to enable Aware in its vicinity). Results
+ * (e.g. successful attach to a cluster) are provided to the {@code attachCallback} object.
+ * An application <b>must</b> call {@link WifiAwareSession#close()} when done with the
+ * Wi-Fi Aware object.
+ * <p>
+ * Note: a Aware cluster is a shared resource - if the device is already attached to a cluster
+ * then this function will simply indicate success immediately using the same {@code
+ * attachCallback}.
+ *
+ * @param attachCallback A callback for attach events, extended from
+ * {@link AttachCallback}.
+ * @param handler The Handler on whose thread to execute the callbacks of the {@code
+ * attachCallback} object. If a null is provided then the application's main thread will be
+ * used.
+ */
+ public void attach(@NonNull AttachCallback attachCallback, @Nullable Handler handler) {
+ attach(handler, null, attachCallback, null);
+ }
+
+ /**
+ * Attach to the Wi-Fi Aware service - enabling the application to create discovery sessions or
+ * create connections to peers. The device will attach to an existing cluster if it can find
+ * one or create a new cluster (if it is the first to enable Aware in its vicinity). Results
+ * (e.g. successful attach to a cluster) are provided to the {@code attachCallback} object.
+ * An application <b>must</b> call {@link WifiAwareSession#close()} when done with the
+ * Wi-Fi Aware object.
+ * <p>
+ * Note: a Aware cluster is a shared resource - if the device is already attached to a cluster
+ * then this function will simply indicate success immediately using the same {@code
+ * attachCallback}.
+ * <p>
+ * This version of the API attaches a listener to receive the MAC address of the Aware interface
+ * on startup and whenever it is updated (it is randomized at regular intervals for privacy).
+ * The application must have the {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
+ * permission to execute this attach request. Otherwise, use the
+ * {@link #attach(AttachCallback, Handler)} version. Note that aside from permission
+ * requirements this listener will wake up the host at regular intervals causing higher power
+ * consumption, do not use it unless the information is necessary (e.g. for OOB discovery).
+ *
+ * @param attachCallback A callback for attach events, extended from
+ * {@link AttachCallback}.
+ * @param identityChangedListener A listener for changed identity, extended from
+ * {@link IdentityChangedListener}.
+ * @param handler The Handler on whose thread to execute the callbacks of the {@code
+ * attachCallback} and {@code identityChangedListener} objects. If a null is provided then the
+ * application's main thread will be used.
+ */
+ public void attach(@NonNull AttachCallback attachCallback,
+ @NonNull IdentityChangedListener identityChangedListener,
+ @Nullable Handler handler) {
+ attach(handler, null, attachCallback, identityChangedListener);
+ }
+
+ /** @hide */
+ public void attach(Handler handler, ConfigRequest configRequest,
+ AttachCallback attachCallback,
+ IdentityChangedListener identityChangedListener) {
+ if (VDBG) {
+ Log.v(TAG, "attach(): handler=" + handler + ", callback=" + attachCallback
+ + ", configRequest=" + configRequest + ", identityChangedListener="
+ + identityChangedListener);
+ }
+
+ if (attachCallback == null) {
+ throw new IllegalArgumentException("Null callback provided");
+ }
+
+ synchronized (mLock) {
+ Looper looper = (handler == null) ? Looper.getMainLooper() : handler.getLooper();
+
+ try {
+ Binder binder = new Binder();
+ mService.connect(binder, mContext.getOpPackageName(),
+ new WifiAwareEventCallbackProxy(this, looper, binder, attachCallback,
+ identityChangedListener), configRequest,
+ identityChangedListener != null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /** @hide */
+ public void disconnect(int clientId, Binder binder) {
+ if (VDBG) Log.v(TAG, "disconnect()");
+
+ try {
+ mService.disconnect(clientId, binder);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void publish(int clientId, Looper looper, PublishConfig publishConfig,
+ DiscoverySessionCallback callback) {
+ if (VDBG) Log.v(TAG, "publish(): clientId=" + clientId + ", config=" + publishConfig);
+
+ if (callback == null) {
+ throw new IllegalArgumentException("Null callback provided");
+ }
+
+ try {
+ mService.publish(mContext.getOpPackageName(), clientId, publishConfig,
+ new WifiAwareDiscoverySessionCallbackProxy(this, looper, true, callback,
+ clientId));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void updatePublish(int clientId, int sessionId, PublishConfig publishConfig) {
+ if (VDBG) {
+ Log.v(TAG, "updatePublish(): clientId=" + clientId + ",sessionId=" + sessionId
+ + ", config=" + publishConfig);
+ }
+
+ try {
+ mService.updatePublish(clientId, sessionId, publishConfig);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void subscribe(int clientId, Looper looper, SubscribeConfig subscribeConfig,
+ DiscoverySessionCallback callback) {
+ if (VDBG) {
+ if (VDBG) {
+ Log.v(TAG,
+ "subscribe(): clientId=" + clientId + ", config=" + subscribeConfig);
+ }
+ }
+
+ if (callback == null) {
+ throw new IllegalArgumentException("Null callback provided");
+ }
+
+ try {
+ mService.subscribe(mContext.getOpPackageName(), clientId, subscribeConfig,
+ new WifiAwareDiscoverySessionCallbackProxy(this, looper, false, callback,
+ clientId));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void updateSubscribe(int clientId, int sessionId, SubscribeConfig subscribeConfig) {
+ if (VDBG) {
+ Log.v(TAG, "updateSubscribe(): clientId=" + clientId + ",sessionId=" + sessionId
+ + ", config=" + subscribeConfig);
+ }
+
+ try {
+ mService.updateSubscribe(clientId, sessionId, subscribeConfig);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void terminateSession(int clientId, int sessionId) {
+ if (VDBG) {
+ Log.d(TAG,
+ "terminateSession(): clientId=" + clientId + ", sessionId=" + sessionId);
+ }
+
+ try {
+ mService.terminateSession(clientId, sessionId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void sendMessage(int clientId, int sessionId, PeerHandle peerHandle, byte[] message,
+ int messageId, int retryCount) {
+ if (peerHandle == null) {
+ throw new IllegalArgumentException(
+ "sendMessage: invalid peerHandle - must be non-null");
+ }
+
+ if (VDBG) {
+ Log.v(TAG, "sendMessage(): clientId=" + clientId + ", sessionId=" + sessionId
+ + ", peerHandle=" + peerHandle.peerId + ", messageId="
+ + messageId + ", retryCount=" + retryCount);
+ }
+
+ try {
+ mService.sendMessage(clientId, sessionId, peerHandle.peerId, message, messageId,
+ retryCount);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public NetworkSpecifier createNetworkSpecifier(int clientId, int role, int sessionId,
+ @NonNull PeerHandle peerHandle, @Nullable byte[] pmk, @Nullable String passphrase) {
+ if (VDBG) {
+ Log.v(TAG, "createNetworkSpecifier: role=" + role + ", sessionId=" + sessionId
+ + ", peerHandle=" + ((peerHandle == null) ? peerHandle : peerHandle.peerId)
+ + ", pmk=" + ((pmk == null) ? "null" : "non-null")
+ + ", passphrase=" + ((passphrase == null) ? "null" : "non-null"));
+ }
+
+ if (!WifiAwareUtils.isLegacyVersion(mContext, Build.VERSION_CODES.Q)) {
+ throw new UnsupportedOperationException(
+ "API deprecated - use WifiAwareNetworkSpecifier.Builder");
+ }
+
+ if (role != WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
+ && role != WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) {
+ throw new IllegalArgumentException(
+ "createNetworkSpecifier: Invalid 'role' argument when creating a network "
+ + "specifier");
+ }
+ if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR || !WifiAwareUtils.isLegacyVersion(mContext,
+ Build.VERSION_CODES.P)) {
+ if (peerHandle == null) {
+ throw new IllegalArgumentException(
+ "createNetworkSpecifier: Invalid peer handle - cannot be null");
+ }
+ }
+
+ return new WifiAwareNetworkSpecifier(
+ (peerHandle == null) ? WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB_ANY_PEER
+ : WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB,
+ role,
+ clientId,
+ sessionId,
+ peerHandle != null ? peerHandle.peerId : 0, // 0 is an invalid peer ID
+ null, // peerMac (not used in this method)
+ pmk,
+ passphrase,
+ 0, // no port info for deprecated IB APIs
+ -1, // no transport info for deprecated IB APIs
+ Process.myUid());
+ }
+
+ /** @hide */
+ public NetworkSpecifier createNetworkSpecifier(int clientId, @DataPathRole int role,
+ @NonNull byte[] peer, @Nullable byte[] pmk, @Nullable String passphrase) {
+ if (VDBG) {
+ Log.v(TAG, "createNetworkSpecifier: role=" + role
+ + ", pmk=" + ((pmk == null) ? "null" : "non-null")
+ + ", passphrase=" + ((passphrase == null) ? "null" : "non-null"));
+ }
+
+ if (role != WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
+ && role != WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) {
+ throw new IllegalArgumentException(
+ "createNetworkSpecifier: Invalid 'role' argument when creating a network "
+ + "specifier");
+ }
+ if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR || !WifiAwareUtils.isLegacyVersion(mContext,
+ Build.VERSION_CODES.P)) {
+ if (peer == null) {
+ throw new IllegalArgumentException(
+ "createNetworkSpecifier: Invalid peer MAC - cannot be null");
+ }
+ }
+ if (peer != null && peer.length != 6) {
+ throw new IllegalArgumentException("createNetworkSpecifier: Invalid peer MAC address");
+ }
+
+ return new WifiAwareNetworkSpecifier(
+ (peer == null) ? WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER
+ : WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB,
+ role,
+ clientId,
+ 0, // 0 is an invalid session ID
+ 0, // 0 is an invalid peer ID
+ peer,
+ pmk,
+ passphrase,
+ 0, // no port info for OOB APIs
+ -1, // no transport protocol info for OOB APIs
+ Process.myUid());
+ }
+
+ private static class WifiAwareEventCallbackProxy extends IWifiAwareEventCallback.Stub {
+ private static final int CALLBACK_CONNECT_SUCCESS = 0;
+ private static final int CALLBACK_CONNECT_FAIL = 1;
+ private static final int CALLBACK_IDENTITY_CHANGED = 2;
+
+ private final Handler mHandler;
+ private final WeakReference<WifiAwareManager> mAwareManager;
+ private final Binder mBinder;
+ private final Looper mLooper;
+
+ /**
+ * Constructs a {@link AttachCallback} using the specified looper.
+ * All callbacks will delivered on the thread of the specified looper.
+ *
+ * @param looper The looper on which to execute the callbacks.
+ */
+ WifiAwareEventCallbackProxy(WifiAwareManager mgr, Looper looper, Binder binder,
+ final AttachCallback attachCallback,
+ final IdentityChangedListener identityChangedListener) {
+ mAwareManager = new WeakReference<>(mgr);
+ mLooper = looper;
+ mBinder = binder;
+
+ if (VDBG) Log.v(TAG, "WifiAwareEventCallbackProxy ctor: looper=" + looper);
+ mHandler = new Handler(looper) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (DBG) {
+ Log.d(TAG, "WifiAwareEventCallbackProxy: What=" + msg.what + ", msg="
+ + msg);
+ }
+
+ WifiAwareManager mgr = mAwareManager.get();
+ if (mgr == null) {
+ Log.w(TAG, "WifiAwareEventCallbackProxy: handleMessage post GC");
+ return;
+ }
+
+ switch (msg.what) {
+ case CALLBACK_CONNECT_SUCCESS:
+ attachCallback.onAttached(
+ new WifiAwareSession(mgr, mBinder, msg.arg1));
+ break;
+ case CALLBACK_CONNECT_FAIL:
+ mAwareManager.clear();
+ attachCallback.onAttachFailed();
+ break;
+ case CALLBACK_IDENTITY_CHANGED:
+ if (identityChangedListener == null) {
+ Log.e(TAG, "CALLBACK_IDENTITY_CHANGED: null listener.");
+ } else {
+ identityChangedListener.onIdentityChanged((byte[]) msg.obj);
+ }
+ break;
+ }
+ }
+ };
+ }
+
+ @Override
+ public void onConnectSuccess(int clientId) {
+ if (VDBG) Log.v(TAG, "onConnectSuccess");
+
+ Message msg = mHandler.obtainMessage(CALLBACK_CONNECT_SUCCESS);
+ msg.arg1 = clientId;
+ mHandler.sendMessage(msg);
+ }
+
+ @Override
+ public void onConnectFail(int reason) {
+ if (VDBG) Log.v(TAG, "onConnectFail: reason=" + reason);
+
+ Message msg = mHandler.obtainMessage(CALLBACK_CONNECT_FAIL);
+ msg.arg1 = reason;
+ mHandler.sendMessage(msg);
+ }
+
+ @Override
+ public void onIdentityChanged(byte[] mac) {
+ if (VDBG) Log.v(TAG, "onIdentityChanged: mac=" + new String(HexEncoding.encode(mac)));
+
+ Message msg = mHandler.obtainMessage(CALLBACK_IDENTITY_CHANGED);
+ msg.obj = mac;
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ private static class WifiAwareDiscoverySessionCallbackProxy extends
+ IWifiAwareDiscoverySessionCallback.Stub {
+ private static final int CALLBACK_SESSION_STARTED = 0;
+ private static final int CALLBACK_SESSION_CONFIG_SUCCESS = 1;
+ private static final int CALLBACK_SESSION_CONFIG_FAIL = 2;
+ private static final int CALLBACK_SESSION_TERMINATED = 3;
+ private static final int CALLBACK_MATCH = 4;
+ private static final int CALLBACK_MESSAGE_SEND_SUCCESS = 5;
+ private static final int CALLBACK_MESSAGE_SEND_FAIL = 6;
+ private static final int CALLBACK_MESSAGE_RECEIVED = 7;
+ private static final int CALLBACK_MATCH_WITH_DISTANCE = 8;
+
+ private static final String MESSAGE_BUNDLE_KEY_MESSAGE = "message";
+ private static final String MESSAGE_BUNDLE_KEY_MESSAGE2 = "message2";
+
+ private final WeakReference<WifiAwareManager> mAwareManager;
+ private final boolean mIsPublish;
+ private final DiscoverySessionCallback mOriginalCallback;
+ private final int mClientId;
+
+ private final Handler mHandler;
+ private DiscoverySession mSession;
+
+ WifiAwareDiscoverySessionCallbackProxy(WifiAwareManager mgr, Looper looper,
+ boolean isPublish, DiscoverySessionCallback originalCallback,
+ int clientId) {
+ mAwareManager = new WeakReference<>(mgr);
+ mIsPublish = isPublish;
+ mOriginalCallback = originalCallback;
+ mClientId = clientId;
+
+ if (VDBG) {
+ Log.v(TAG, "WifiAwareDiscoverySessionCallbackProxy ctor: isPublish=" + isPublish);
+ }
+
+ mHandler = new Handler(looper) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (DBG) Log.d(TAG, "What=" + msg.what + ", msg=" + msg);
+
+ if (mAwareManager.get() == null) {
+ Log.w(TAG, "WifiAwareDiscoverySessionCallbackProxy: handleMessage post GC");
+ return;
+ }
+
+ switch (msg.what) {
+ case CALLBACK_SESSION_STARTED:
+ onProxySessionStarted(msg.arg1);
+ break;
+ case CALLBACK_SESSION_CONFIG_SUCCESS:
+ mOriginalCallback.onSessionConfigUpdated();
+ break;
+ case CALLBACK_SESSION_CONFIG_FAIL:
+ mOriginalCallback.onSessionConfigFailed();
+ if (mSession == null) {
+ /*
+ * creation failed (as opposed to update
+ * failing)
+ */
+ mAwareManager.clear();
+ }
+ break;
+ case CALLBACK_SESSION_TERMINATED:
+ onProxySessionTerminated(msg.arg1);
+ break;
+ case CALLBACK_MATCH:
+ case CALLBACK_MATCH_WITH_DISTANCE:
+ {
+ List<byte[]> matchFilter = null;
+ byte[] arg = msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE2);
+ try {
+ matchFilter = new TlvBufferUtils.TlvIterable(0, 1, arg).toList();
+ } catch (BufferOverflowException e) {
+ matchFilter = null;
+ Log.e(TAG, "onServiceDiscovered: invalid match filter byte array '"
+ + new String(HexEncoding.encode(arg))
+ + "' - cannot be parsed: e=" + e);
+ }
+ if (msg.what == CALLBACK_MATCH) {
+ mOriginalCallback.onServiceDiscovered(new PeerHandle(msg.arg1),
+ msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE),
+ matchFilter);
+ } else {
+ mOriginalCallback.onServiceDiscoveredWithinRange(
+ new PeerHandle(msg.arg1),
+ msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE),
+ matchFilter, msg.arg2);
+ }
+ break;
+ }
+ case CALLBACK_MESSAGE_SEND_SUCCESS:
+ mOriginalCallback.onMessageSendSucceeded(msg.arg1);
+ break;
+ case CALLBACK_MESSAGE_SEND_FAIL:
+ mOriginalCallback.onMessageSendFailed(msg.arg1);
+ break;
+ case CALLBACK_MESSAGE_RECEIVED:
+ mOriginalCallback.onMessageReceived(new PeerHandle(msg.arg1),
+ (byte[]) msg.obj);
+ break;
+ }
+ }
+ };
+ }
+
+ @Override
+ public void onSessionStarted(int sessionId) {
+ if (VDBG) Log.v(TAG, "onSessionStarted: sessionId=" + sessionId);
+
+ Message msg = mHandler.obtainMessage(CALLBACK_SESSION_STARTED);
+ msg.arg1 = sessionId;
+ mHandler.sendMessage(msg);
+ }
+
+ @Override
+ public void onSessionConfigSuccess() {
+ if (VDBG) Log.v(TAG, "onSessionConfigSuccess");
+
+ Message msg = mHandler.obtainMessage(CALLBACK_SESSION_CONFIG_SUCCESS);
+ mHandler.sendMessage(msg);
+ }
+
+ @Override
+ public void onSessionConfigFail(int reason) {
+ if (VDBG) Log.v(TAG, "onSessionConfigFail: reason=" + reason);
+
+ Message msg = mHandler.obtainMessage(CALLBACK_SESSION_CONFIG_FAIL);
+ msg.arg1 = reason;
+ mHandler.sendMessage(msg);
+ }
+
+ @Override
+ public void onSessionTerminated(int reason) {
+ if (VDBG) Log.v(TAG, "onSessionTerminated: reason=" + reason);
+
+ Message msg = mHandler.obtainMessage(CALLBACK_SESSION_TERMINATED);
+ msg.arg1 = reason;
+ mHandler.sendMessage(msg);
+ }
+
+ private void onMatchCommon(int messageType, int peerId, byte[] serviceSpecificInfo,
+ byte[] matchFilter, int distanceMm) {
+ Bundle data = new Bundle();
+ data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE, serviceSpecificInfo);
+ data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE2, matchFilter);
+
+ Message msg = mHandler.obtainMessage(messageType);
+ msg.arg1 = peerId;
+ msg.arg2 = distanceMm;
+ msg.setData(data);
+ mHandler.sendMessage(msg);
+ }
+
+ @Override
+ public void onMatch(int peerId, byte[] serviceSpecificInfo, byte[] matchFilter) {
+ if (VDBG) Log.v(TAG, "onMatch: peerId=" + peerId);
+
+ onMatchCommon(CALLBACK_MATCH, peerId, serviceSpecificInfo, matchFilter, 0);
+ }
+
+ @Override
+ public void onMatchWithDistance(int peerId, byte[] serviceSpecificInfo, byte[] matchFilter,
+ int distanceMm) {
+ if (VDBG) {
+ Log.v(TAG, "onMatchWithDistance: peerId=" + peerId + ", distanceMm=" + distanceMm);
+ }
+
+ onMatchCommon(CALLBACK_MATCH_WITH_DISTANCE, peerId, serviceSpecificInfo, matchFilter,
+ distanceMm);
+ }
+
+ @Override
+ public void onMessageSendSuccess(int messageId) {
+ if (VDBG) Log.v(TAG, "onMessageSendSuccess");
+
+ Message msg = mHandler.obtainMessage(CALLBACK_MESSAGE_SEND_SUCCESS);
+ msg.arg1 = messageId;
+ mHandler.sendMessage(msg);
+ }
+
+ @Override
+ public void onMessageSendFail(int messageId, int reason) {
+ if (VDBG) Log.v(TAG, "onMessageSendFail: reason=" + reason);
+
+ Message msg = mHandler.obtainMessage(CALLBACK_MESSAGE_SEND_FAIL);
+ msg.arg1 = messageId;
+ msg.arg2 = reason;
+ mHandler.sendMessage(msg);
+ }
+
+ @Override
+ public void onMessageReceived(int peerId, byte[] message) {
+ if (VDBG) {
+ Log.v(TAG, "onMessageReceived: peerId=" + peerId);
+ }
+
+ Message msg = mHandler.obtainMessage(CALLBACK_MESSAGE_RECEIVED);
+ msg.arg1 = peerId;
+ msg.obj = message;
+ mHandler.sendMessage(msg);
+ }
+
+ /*
+ * Proxied methods
+ */
+ public void onProxySessionStarted(int sessionId) {
+ if (VDBG) Log.v(TAG, "Proxy: onSessionStarted: sessionId=" + sessionId);
+ if (mSession != null) {
+ Log.e(TAG,
+ "onSessionStarted: sessionId=" + sessionId + ": session already created!?");
+ throw new IllegalStateException(
+ "onSessionStarted: sessionId=" + sessionId + ": session already created!?");
+ }
+
+ WifiAwareManager mgr = mAwareManager.get();
+ if (mgr == null) {
+ Log.w(TAG, "onProxySessionStarted: mgr GC'd");
+ return;
+ }
+
+ if (mIsPublish) {
+ PublishDiscoverySession session = new PublishDiscoverySession(mgr,
+ mClientId, sessionId);
+ mSession = session;
+ mOriginalCallback.onPublishStarted(session);
+ } else {
+ SubscribeDiscoverySession
+ session = new SubscribeDiscoverySession(mgr, mClientId, sessionId);
+ mSession = session;
+ mOriginalCallback.onSubscribeStarted(session);
+ }
+ }
+
+ public void onProxySessionTerminated(int reason) {
+ if (VDBG) Log.v(TAG, "Proxy: onSessionTerminated: reason=" + reason);
+ if (mSession != null) {
+ mSession.setTerminated();
+ mSession = null;
+ } else {
+ Log.w(TAG, "Proxy: onSessionTerminated called but mSession is null!?");
+ }
+ mAwareManager.clear();
+ mOriginalCallback.onSessionTerminated();
+ }
+ }
+}
diff --git a/android/net/wifi/aware/WifiAwareNetworkInfo.java b/android/net/wifi/aware/WifiAwareNetworkInfo.java
new file mode 100644
index 0000000..fd26817
--- /dev/null
+++ b/android/net/wifi/aware/WifiAwareNetworkInfo.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.aware;
+
+import android.annotation.Nullable;
+import android.net.NetworkCapabilities;
+import android.net.TransportInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.net.Inet6Address;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Objects;
+
+/**
+ * Wi-Fi Aware-specific network information. The information can be extracted from the
+ * {@link android.net.NetworkCapabilities} of the network using
+ * {@link NetworkCapabilities#getTransportInfo()}.
+ * The {@link NetworkCapabilities} is provided by the connectivity service to apps, e.g. received
+ * through the
+ * {@link android.net.ConnectivityManager.NetworkCallback#onCapabilitiesChanged(android.net.Network,
+ * android.net.NetworkCapabilities)} callback.
+ * <p>
+ * The Wi-Fi Aware-specific network information include the peer's scoped link-local IPv6 address
+ * for the Wi-Fi Aware link, as well as (optionally) the port and transport protocol specified by
+ * the peer.
+ * The scoped link-local IPv6, port, and transport protocol can then be used to create a
+ * {@link java.net.Socket} connection to the peer.
+ * <p>
+ * Note: these are the peer's IPv6 and port information - not the local device's!
+ */
+public final class WifiAwareNetworkInfo implements TransportInfo, Parcelable {
+ private Inet6Address mIpv6Addr;
+ private int mPort = 0; // a value of 0 is considered invalid
+ private int mTransportProtocol = -1; // a value of -1 is considered invalid
+
+ /** @hide */
+ public WifiAwareNetworkInfo(Inet6Address ipv6Addr) {
+ mIpv6Addr = ipv6Addr;
+ }
+
+ /** @hide */
+ public WifiAwareNetworkInfo(Inet6Address ipv6Addr, int port, int transportProtocol) {
+ mIpv6Addr = ipv6Addr;
+ mPort = port;
+ mTransportProtocol = transportProtocol;
+ }
+
+ /**
+ * Get the scoped link-local IPv6 address of the Wi-Fi Aware peer (not of the local device!).
+ *
+ * @return An IPv6 address.
+ */
+ @Nullable
+ public Inet6Address getPeerIpv6Addr() {
+ return mIpv6Addr;
+ }
+
+ /**
+ * Get the port number to be used to create a network connection to the Wi-Fi Aware peer.
+ * The port information is provided by the app running on the peer which requested the
+ * connection, using the {@link WifiAwareNetworkSpecifier.Builder#setPort(int)}.
+ *
+ * @return A port number on the peer. A value of 0 indicates that no port was specified by the
+ * peer.
+ */
+ public int getPort() {
+ return mPort;
+ }
+
+ /**
+ * Get the transport protocol to be used to communicate over a network connection to the Wi-Fi
+ * Aware peer. The transport protocol is provided by the app running on the peer which requested
+ * the connection, using the
+ * {@link WifiAwareNetworkSpecifier.Builder#setTransportProtocol(int)}.
+ * <p>
+ * The transport protocol number is assigned by the Internet Assigned Numbers Authority
+ * (IANA) https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml.
+ *
+ * @return A transport protocol id. A value of -1 indicates that no transport protocol was
+ * specified by the peer.
+ */
+ public int getTransportProtocol() {
+ return mTransportProtocol;
+ }
+
+ // parcelable methods
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeByteArray(mIpv6Addr.getAddress());
+ NetworkInterface ni = mIpv6Addr.getScopedInterface();
+ dest.writeString(ni == null ? null : ni.getName());
+ dest.writeInt(mPort);
+ dest.writeInt(mTransportProtocol);
+ }
+
+ public static final @android.annotation.NonNull Creator<WifiAwareNetworkInfo> CREATOR =
+ new Creator<WifiAwareNetworkInfo>() {
+ @Override
+ public WifiAwareNetworkInfo createFromParcel(Parcel in) {
+ Inet6Address ipv6Addr;
+ try {
+ byte[] addr = in.createByteArray();
+ String interfaceName = in.readString();
+ NetworkInterface ni = null;
+ if (interfaceName != null) {
+ try {
+ ni = NetworkInterface.getByName(interfaceName);
+ } catch (SocketException e) {
+ e.printStackTrace();
+ }
+ }
+ ipv6Addr = Inet6Address.getByAddress(null, addr, ni);
+ } catch (UnknownHostException e) {
+ e.printStackTrace();
+ return null;
+ }
+ int port = in.readInt();
+ int transportProtocol = in.readInt();
+
+ return new WifiAwareNetworkInfo(ipv6Addr, port, transportProtocol);
+ }
+
+ @Override
+ public WifiAwareNetworkInfo[] newArray(int size) {
+ return new WifiAwareNetworkInfo[size];
+ }
+ };
+
+
+ // object methods
+
+ @Override
+ public String toString() {
+ return new StringBuilder("AwareNetworkInfo: IPv6=").append(mIpv6Addr).append(
+ ", port=").append(mPort).append(", transportProtocol=").append(
+ mTransportProtocol).toString();
+ }
+
+ /** @hide */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof WifiAwareNetworkInfo)) {
+ return false;
+ }
+
+ WifiAwareNetworkInfo lhs = (WifiAwareNetworkInfo) obj;
+ return Objects.equals(mIpv6Addr, lhs.mIpv6Addr) && mPort == lhs.mPort
+ && mTransportProtocol == lhs.mTransportProtocol;
+ }
+
+ /** @hide */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mIpv6Addr, mPort, mTransportProtocol);
+ }
+}
diff --git a/android/net/wifi/aware/WifiAwareNetworkSpecifier.java b/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
new file mode 100644
index 0000000..0511f24
--- /dev/null
+++ b/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
@@ -0,0 +1,471 @@
+/*
+ * 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 android.net.wifi.aware;
+
+import static android.net.wifi.aware.WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.net.NetworkSpecifier;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Process;
+import android.text.TextUtils;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Network specifier object used to request a Wi-Fi Aware network. Apps should use the
+ * {@link WifiAwareNetworkSpecifier.Builder} class to create an instance.
+ */
+public final class WifiAwareNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+ /**
+ * TYPE: in band, specific peer: role, client_id, session_id, peer_id, pmk/passphrase optional
+ * @hide
+ */
+ public static final int NETWORK_SPECIFIER_TYPE_IB = 0;
+
+ /**
+ * TYPE: in band, any peer: role, client_id, session_id, pmk/passphrase optional
+ * [only permitted for RESPONDER]
+ * @hide
+ */
+ public static final int NETWORK_SPECIFIER_TYPE_IB_ANY_PEER = 1;
+
+ /**
+ * TYPE: out-of-band: role, client_id, peer_mac, pmk/passphrase optional
+ * @hide
+ */
+ public static final int NETWORK_SPECIFIER_TYPE_OOB = 2;
+
+ /**
+ * TYPE: out-of-band, any peer: role, client_id, pmk/passphrase optional
+ * [only permitted for RESPONDER]
+ * @hide
+ */
+ public static final int NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER = 3;
+
+ /** @hide */
+ public static final int NETWORK_SPECIFIER_TYPE_MAX_VALID = NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER;
+
+ /**
+ * One of the NETWORK_SPECIFIER_TYPE_* constants. The type of the network specifier object.
+ * @hide
+ */
+ public final int type;
+
+ /**
+ * The role of the device: WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR or
+ * WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER.
+ * @hide
+ */
+ public final int role;
+
+ /**
+ * The client ID of the device.
+ * @hide
+ */
+ public final int clientId;
+
+ /**
+ * The session ID in which context to request a data-path. Only relevant for IB requests.
+ * @hide
+ */
+ public final int sessionId;
+
+ /**
+ * The peer ID of the device which the data-path should be connected to. Only relevant for
+ * IB requests (i.e. not IB_ANY_PEER or OOB*).
+ * @hide
+ */
+ public final int peerId;
+
+ /**
+ * The peer MAC address of the device which the data-path should be connected to. Only relevant
+ * for OB requests (i.e. not OOB_ANY_PEER or IB*).
+ * @hide
+ */
+ public final byte[] peerMac;
+
+ /**
+ * The PMK of the requested data-path. Can be null. Only one or none of pmk or passphrase should
+ * be specified.
+ * @hide
+ */
+ public final byte[] pmk;
+
+ /**
+ * The Passphrase of the requested data-path. Can be null. Only one or none of the pmk or
+ * passphrase should be specified.
+ * @hide
+ */
+ public final String passphrase;
+
+ /**
+ * The port information to be used for this link. This information will be communicated to the
+ * peer as part of the layer 2 link setup.
+ *
+ * Information only allowed on secure links since a single layer-2 link is set up for all
+ * requestors. Therefore if multiple apps on a single device request links to the same peer
+ * device they all get the same link. However, the link is only set up on the first request -
+ * hence only the first can transmit the port information. But we don't want to expose that
+ * information to other apps. Limiting to secure links would (usually) imply single app usage.
+ *
+ * @hide
+ */
+ public final int port;
+
+ /**
+ * The transport protocol information to be used for this link. This information will be
+ * communicated to the peer as part of the layer 2 link setup.
+ *
+ * Information only allowed on secure links since a single layer-2 link is set up for all
+ * requestors. Therefore if multiple apps on a single device request links to the same peer
+ * device they all get the same link. However, the link is only set up on the first request -
+ * hence only the first can transmit the port information. But we don't want to expose that
+ * information to other apps. Limiting to secure links would (usually) imply single app usage.
+ *
+ * @hide
+ */
+ public final int transportProtocol;
+
+ /**
+ * The UID of the process initializing this network specifier. Validated by receiver using
+ * checkUidIfNecessary() and is used by satisfiedBy() to determine whether matches the
+ * offered network.
+ *
+ * @hide
+ */
+ public final int requestorUid;
+
+ /** @hide */
+ public WifiAwareNetworkSpecifier(int type, int role, int clientId, int sessionId, int peerId,
+ byte[] peerMac, byte[] pmk, String passphrase, int port, int transportProtocol,
+ int requestorUid) {
+ this.type = type;
+ this.role = role;
+ this.clientId = clientId;
+ this.sessionId = sessionId;
+ this.peerId = peerId;
+ this.peerMac = peerMac;
+ this.pmk = pmk;
+ this.passphrase = passphrase;
+ this.port = port;
+ this.transportProtocol = transportProtocol;
+ this.requestorUid = requestorUid;
+ }
+
+ public static final @android.annotation.NonNull Creator<WifiAwareNetworkSpecifier> CREATOR =
+ new Creator<WifiAwareNetworkSpecifier>() {
+ @Override
+ public WifiAwareNetworkSpecifier createFromParcel(Parcel in) {
+ return new WifiAwareNetworkSpecifier(
+ in.readInt(), // type
+ in.readInt(), // role
+ in.readInt(), // clientId
+ in.readInt(), // sessionId
+ in.readInt(), // peerId
+ in.createByteArray(), // peerMac
+ in.createByteArray(), // pmk
+ in.readString(), // passphrase
+ in.readInt(), // port
+ in.readInt(), // transportProtocol
+ in.readInt()); // requestorUid
+ }
+
+ @Override
+ public WifiAwareNetworkSpecifier[] newArray(int size) {
+ return new WifiAwareNetworkSpecifier[size];
+ }
+ };
+
+ /**
+ * Indicates whether the network specifier specifies an OOB (out-of-band) data-path - i.e. a
+ * data-path created without a corresponding Aware discovery session.
+ *
+ * @hide
+ */
+ public boolean isOutOfBand() {
+ return type == NETWORK_SPECIFIER_TYPE_OOB || type == NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(type);
+ dest.writeInt(role);
+ dest.writeInt(clientId);
+ dest.writeInt(sessionId);
+ dest.writeInt(peerId);
+ dest.writeByteArray(peerMac);
+ dest.writeByteArray(pmk);
+ dest.writeString(passphrase);
+ dest.writeInt(port);
+ dest.writeInt(transportProtocol);
+ dest.writeInt(requestorUid);
+ }
+
+ /** @hide */
+ @Override
+ public boolean satisfiedBy(NetworkSpecifier other) {
+ // MatchAllNetworkSpecifier is taken care in NetworkCapabilities#satisfiedBySpecifier.
+ if (other instanceof WifiAwareAgentNetworkSpecifier) {
+ return ((WifiAwareAgentNetworkSpecifier) other).satisfiesAwareNetworkSpecifier(this);
+ }
+ return equals(other);
+ }
+
+ /** @hide */
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, role, clientId, sessionId, peerId, Arrays.hashCode(peerMac),
+ Arrays.hashCode(pmk), passphrase, port, transportProtocol, requestorUid);
+ }
+
+ /** @hide */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof WifiAwareNetworkSpecifier)) {
+ return false;
+ }
+
+ WifiAwareNetworkSpecifier lhs = (WifiAwareNetworkSpecifier) obj;
+
+ return type == lhs.type
+ && role == lhs.role
+ && clientId == lhs.clientId
+ && sessionId == lhs.sessionId
+ && peerId == lhs.peerId
+ && Arrays.equals(peerMac, lhs.peerMac)
+ && Arrays.equals(pmk, lhs.pmk)
+ && Objects.equals(passphrase, lhs.passphrase)
+ && port == lhs.port
+ && transportProtocol == lhs.transportProtocol
+ && requestorUid == lhs.requestorUid;
+ }
+
+ /** @hide */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("WifiAwareNetworkSpecifier [");
+ sb.append("type=").append(type)
+ .append(", role=").append(role)
+ .append(", clientId=").append(clientId)
+ .append(", sessionId=").append(sessionId)
+ .append(", peerId=").append(peerId)
+ // masking potential PII (although low impact information)
+ .append(", peerMac=").append((peerMac == null) ? "<null>" : "<non-null>")
+ // masking PII
+ .append(", pmk=").append((pmk == null) ? "<null>" : "<non-null>")
+ // masking PII
+ .append(", passphrase=").append((passphrase == null) ? "<null>" : "<non-null>")
+ .append(", port=").append(port).append(", transportProtocol=")
+ .append(transportProtocol).append(", requestorUid=").append(requestorUid)
+ .append("]");
+ return sb.toString();
+ }
+
+ /** @hide */
+ @Override
+ public void assertValidFromUid(int requestorUid) {
+ if (this.requestorUid != requestorUid) {
+ throw new SecurityException("mismatched UIDs");
+ }
+ }
+
+ /**
+ * A builder class for a Wi-Fi Aware network specifier to set up an Aware connection with a
+ * peer.
+ */
+ public static final class Builder {
+ private DiscoverySession mDiscoverySession;
+ private PeerHandle mPeerHandle;
+ private String mPskPassphrase;
+ private byte[] mPmk;
+ private int mPort = 0; // invalid value
+ private int mTransportProtocol = -1; // invalid value
+
+ /**
+ * Create a builder for {@link WifiAwareNetworkSpecifier} used in requests to set up a
+ * Wi-Fi Aware connection with a peer.
+ *
+ * @param discoverySession A Wi-Fi Aware discovery session in whose context the connection
+ * is created.
+ * @param peerHandle The handle of the peer to which the Wi-Fi Aware connection is
+ * requested. The peer is discovered through Wi-Fi Aware discovery. The
+ * handle can be obtained through
+ * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], java.util.List)}
+ * or
+ * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle, byte[])}.
+ */
+ public Builder(@NonNull DiscoverySession discoverySession, @NonNull PeerHandle peerHandle) {
+ if (discoverySession == null) {
+ throw new IllegalArgumentException("Non-null discoverySession required");
+ }
+ if (peerHandle == null) {
+ throw new IllegalArgumentException("Non-null peerHandle required");
+ }
+ mDiscoverySession = discoverySession;
+ mPeerHandle = peerHandle;
+ }
+
+ /**
+ * Configure the PSK Passphrase for the Wi-Fi Aware connection being requested. This method
+ * is optional - if not called, then an Open (unencrypted) connection will be created.
+ *
+ * @param pskPassphrase The (optional) passphrase to be used to encrypt the link.
+ * @return the current {@link Builder} builder, enabling chaining of builder
+ * methods.
+ */
+ public @NonNull Builder setPskPassphrase(@NonNull String pskPassphrase) {
+ if (!WifiAwareUtils.validatePassphrase(pskPassphrase)) {
+ throw new IllegalArgumentException("Passphrase must meet length requirements");
+ }
+ mPskPassphrase = pskPassphrase;
+ return this;
+ }
+
+ /**
+ * Configure the PMK for the Wi-Fi Aware connection being requested. This method
+ * is optional - if not called, then an Open (unencrypted) connection will be created.
+ *
+ * @param pmk A PMK (pairwise master key, see IEEE 802.11i) specifying the key to use for
+ * encrypting the data-path. Use the {@link #setPskPassphrase(String)} to
+ * specify a Passphrase.
+ * @return the current {@link Builder} builder, enabling chaining of builder
+ * methods.
+ * @hide
+ */
+ @SystemApi
+ public @NonNull Builder setPmk(@NonNull byte[] pmk) {
+ if (!WifiAwareUtils.validatePmk(pmk)) {
+ throw new IllegalArgumentException("PMK must 32 bytes");
+ }
+ mPmk = pmk;
+ return this;
+ }
+
+ /**
+ * Configure the port number which will be used to create a connection over this link. This
+ * configuration should only be done on the server device, e.g. the device creating the
+ * {@link java.net.ServerSocket}.
+ * <p>Notes:
+ * <ul>
+ * <li>The server device must be the Publisher device!
+ * <li>The port information can only be specified on secure links, specified using
+ * {@link #setPskPassphrase(String)}.
+ * </ul>
+ *
+ * @param port A positive integer indicating the port to be used for communication.
+ * @return the current {@link Builder} builder, enabling chaining of builder
+ * methods.
+ */
+ public @NonNull Builder setPort(@IntRange(from = 0, to = 65535) int port) {
+ if (port <= 0 || port > 65535) {
+ throw new IllegalArgumentException("The port must be a positive value (0, 65535]");
+ }
+ mPort = port;
+ return this;
+ }
+
+ /**
+ * Configure the transport protocol which will be used to create a connection over this
+ * link. This configuration should only be done on the server device, e.g. the device
+ * creating the {@link java.net.ServerSocket} for TCP.
+ * <p>Notes:
+ * <ul>
+ * <li>The server device must be the Publisher device!
+ * <li>The transport protocol information can only be specified on secure links,
+ * specified using {@link #setPskPassphrase(String)}.
+ * </ul>
+ * The transport protocol number is assigned by the Internet Assigned Numbers Authority
+ * (IANA) https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml.
+ *
+ * @param transportProtocol The transport protocol to be used for communication.
+ * @return the current {@link Builder} builder, enabling chaining of builder
+ * methods.
+ */
+ public @NonNull
+ Builder setTransportProtocol(@IntRange(from = 0, to = 255) int transportProtocol) {
+ if (transportProtocol < 0 || transportProtocol > 255) {
+ throw new IllegalArgumentException(
+ "The transport protocol must be in range [0, 255]");
+ }
+ mTransportProtocol = transportProtocol;
+ return this;
+ }
+
+ /**
+ * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)}
+ * for a WiFi Aware connection (link) to the specified peer. The
+ * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
+ * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
+ * <p> The default builder constructor will initialize a NetworkSpecifier which requests an
+ * open (non-encrypted) link. To request an encrypted link use the
+ * {@link #setPskPassphrase(String)} builder method.
+ *
+ * @return A {@link NetworkSpecifier} to be used to construct
+ * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass
+ * to {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
+ * android.net.ConnectivityManager.NetworkCallback)}
+ * [or other varieties of that API].
+ */
+ public @NonNull WifiAwareNetworkSpecifier build() {
+ if (mDiscoverySession == null) {
+ throw new IllegalStateException("Null discovery session!?");
+ }
+ if (mPeerHandle == null) {
+ throw new IllegalStateException("Null peerHandle!?");
+ }
+ if (mPskPassphrase != null & mPmk != null) {
+ throw new IllegalStateException(
+ "Can only specify a Passphrase or a PMK - not both!");
+ }
+
+ int role = mDiscoverySession instanceof SubscribeDiscoverySession
+ ? WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
+ : WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER;
+
+ if (mPort != 0 || mTransportProtocol != -1) {
+ if (role != WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) {
+ throw new IllegalStateException(
+ "Port and transport protocol information can only "
+ + "be specified on the Publisher device (which is the server");
+ }
+ if (TextUtils.isEmpty(mPskPassphrase) && mPmk == null) {
+ throw new IllegalStateException("Port and transport protocol information can "
+ + "only be specified on a secure link");
+ }
+ }
+
+ return new WifiAwareNetworkSpecifier(
+ WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB, role,
+ mDiscoverySession.mClientId, mDiscoverySession.mSessionId, mPeerHandle.peerId,
+ null, mPmk, mPskPassphrase, mPort, mTransportProtocol, Process.myUid());
+ }
+ }
+}
diff --git a/android/net/wifi/aware/WifiAwareSession.java b/android/net/wifi/aware/WifiAwareSession.java
new file mode 100644
index 0000000..3c97813
--- /dev/null
+++ b/android/net/wifi/aware/WifiAwareSession.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2016 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.aware;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.net.NetworkSpecifier;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import dalvik.system.CloseGuard;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * This class represents a Wi-Fi Aware session - an attachment to the Wi-Fi Aware service through
+ * which the app can execute discovery operations.
+ */
+public class WifiAwareSession implements AutoCloseable {
+ private static final String TAG = "WifiAwareSession";
+ private static final boolean DBG = false;
+ private static final boolean VDBG = false; // STOPSHIP if true
+
+ private final WeakReference<WifiAwareManager> mMgr;
+ private final Binder mBinder;
+ private final int mClientId;
+
+ private boolean mTerminated = true;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ /** @hide */
+ public WifiAwareSession(WifiAwareManager manager, Binder binder, int clientId) {
+ if (VDBG) Log.v(TAG, "New session created: manager=" + manager + ", clientId=" + clientId);
+
+ mMgr = new WeakReference<>(manager);
+ mBinder = binder;
+ mClientId = clientId;
+ mTerminated = false;
+
+ mCloseGuard.open("close");
+ }
+
+ /**
+ * Destroy the Wi-Fi Aware service session and, if no other applications are attached to Aware,
+ * also disable Aware. This method destroys all outstanding operations - i.e. all publish and
+ * subscribes are terminated, and any outstanding data-links are shut-down. However, it is
+ * good practice to destroy these discovery sessions and connections explicitly before a
+ * session-wide destroy.
+ * <p>
+ * An application may re-attach after a destroy using
+ * {@link WifiAwareManager#attach(AttachCallback, Handler)} .
+ */
+ @Override
+ public void close() {
+ WifiAwareManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.w(TAG, "destroy: called post GC on WifiAwareManager");
+ return;
+ }
+ mgr.disconnect(mClientId, mBinder);
+ mTerminated = true;
+ mMgr.clear();
+ mCloseGuard.close();
+ }
+
+ /** @hide */
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
+ if (!mTerminated) {
+ close();
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Access the client ID of the Aware session.
+ *
+ * Note: internal visibility for testing.
+ *
+ * @return The internal client ID.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public int getClientId() {
+ return mClientId;
+ }
+
+ /**
+ * Issue a request to the Aware service to create a new Aware publish discovery session, using
+ * the specified {@code publishConfig} configuration. The results of the publish operation
+ * are routed to the callbacks of {@link DiscoverySessionCallback}:
+ * <ul>
+ * <li>
+ * {@link DiscoverySessionCallback#onPublishStarted(
+ *PublishDiscoverySession)}
+ * is called when the publish session is created and provides a handle to the session.
+ * Further operations on the publish session can be executed on that object.
+ * <li>{@link DiscoverySessionCallback#onSessionConfigFailed()} is called if the
+ * publish operation failed.
+ * </ul>
+ * <p>
+ * Other results of the publish session operations will also be routed to callbacks
+ * on the {@code callback} object. The resulting publish session can be modified using
+ * {@link PublishDiscoverySession#updatePublish(PublishConfig)}.
+ * <p>
+ * An application must use the {@link DiscoverySession#close()} to
+ * terminate the publish discovery session once it isn't needed. This will free
+ * resources as well terminate any on-air transmissions.
+ * <p>The application must have the {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
+ * permission to start a publish discovery session.
+ *
+ * @param publishConfig The {@link PublishConfig} specifying the
+ * configuration of the requested publish session.
+ * @param callback A {@link DiscoverySessionCallback} derived object to be used for
+ * session event callbacks.
+ * @param handler The Handler on whose thread to execute the callbacks of the {@code
+ * callback} object. If a null is provided then the application's main thread will be used.
+ */
+ public void publish(@NonNull PublishConfig publishConfig,
+ @NonNull DiscoverySessionCallback callback, @Nullable Handler handler) {
+ WifiAwareManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.e(TAG, "publish: called post GC on WifiAwareManager");
+ return;
+ }
+ if (mTerminated) {
+ Log.e(TAG, "publish: called after termination");
+ return;
+ }
+ mgr.publish(mClientId, (handler == null) ? Looper.getMainLooper() : handler.getLooper(),
+ publishConfig, callback);
+ }
+
+ /**
+ * Issue a request to the Aware service to create a new Aware subscribe discovery session, using
+ * the specified {@code subscribeConfig} configuration. The results of the subscribe
+ * operation are routed to the callbacks of {@link DiscoverySessionCallback}:
+ * <ul>
+ * <li>
+ * {@link DiscoverySessionCallback#onSubscribeStarted(
+ *SubscribeDiscoverySession)}
+ * is called when the subscribe session is created and provides a handle to the session.
+ * Further operations on the subscribe session can be executed on that object.
+ * <li>{@link DiscoverySessionCallback#onSessionConfigFailed()} is called if the
+ * subscribe operation failed.
+ * </ul>
+ * <p>
+ * Other results of the subscribe session operations will also be routed to callbacks
+ * on the {@code callback} object. The resulting subscribe session can be modified using
+ * {@link SubscribeDiscoverySession#updateSubscribe(SubscribeConfig)}.
+ * <p>
+ * An application must use the {@link DiscoverySession#close()} to
+ * terminate the subscribe discovery session once it isn't needed. This will free
+ * resources as well terminate any on-air transmissions.
+ * <p>The application must have the {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
+ * permission to start a subscribe discovery session.
+ *
+ * @param subscribeConfig The {@link SubscribeConfig} specifying the
+ * configuration of the requested subscribe session.
+ * @param callback A {@link DiscoverySessionCallback} derived object to be used for
+ * session event callbacks.
+ * @param handler The Handler on whose thread to execute the callbacks of the {@code
+ * callback} object. If a null is provided then the application's main thread will be used.
+ */
+ public void subscribe(@NonNull SubscribeConfig subscribeConfig,
+ @NonNull DiscoverySessionCallback callback, @Nullable Handler handler) {
+ WifiAwareManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.e(TAG, "publish: called post GC on WifiAwareManager");
+ return;
+ }
+ if (mTerminated) {
+ Log.e(TAG, "publish: called after termination");
+ return;
+ }
+ mgr.subscribe(mClientId, (handler == null) ? Looper.getMainLooper() : handler.getLooper(),
+ subscribeConfig, callback);
+ }
+
+ /**
+ * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
+ * an unencrypted WiFi Aware connection (link) to the specified peer. The
+ * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
+ * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
+ * <p>
+ * This API is targeted for applications which can obtain the peer MAC address using OOB
+ * (out-of-band) discovery. Aware discovery does not provide the MAC address of the peer -
+ * when using Aware discovery use the alternative network specifier method -
+ * {@link android.net.wifi.aware.WifiAwareNetworkSpecifier.Builder}.
+ * <p>
+ * To set up an encrypted link use the
+ * {@link #createNetworkSpecifierPassphrase(int, byte[], String)} API.
+ *
+ * @param role The role of this device:
+ * {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_INITIATOR} or
+ * {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_RESPONDER}
+ * @param peer The MAC address of the peer's Aware discovery interface. On a RESPONDER this
+ * value is used to gate the acceptance of a connection request from only that
+ * peer.
+ *
+ * @return A {@link NetworkSpecifier} to be used to construct
+ * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
+ * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
+ * android.net.ConnectivityManager.NetworkCallback)}
+ * [or other varieties of that API].
+ */
+ public NetworkSpecifier createNetworkSpecifierOpen(
+ @WifiAwareManager.DataPathRole int role, @NonNull byte[] peer) {
+ WifiAwareManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.e(TAG, "createNetworkSpecifierOpen: called post GC on WifiAwareManager");
+ return null;
+ }
+ if (mTerminated) {
+ Log.e(TAG, "createNetworkSpecifierOpen: called after termination");
+ return null;
+ }
+ return mgr.createNetworkSpecifier(mClientId, role, peer, null, null);
+ }
+
+ /**
+ * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
+ * an encrypted WiFi Aware connection (link) to the specified peer. The
+ * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
+ * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
+ * <p>
+ * This API is targeted for applications which can obtain the peer MAC address using OOB
+ * (out-of-band) discovery. Aware discovery does not provide the MAC address of the peer -
+ * when using Aware discovery use the alternative network specifier method -
+ * {@link android.net.wifi.aware.WifiAwareNetworkSpecifier.Builder}.
+ *
+ * @param role The role of this device:
+ * {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_INITIATOR} or
+ * {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_RESPONDER}
+ * @param peer The MAC address of the peer's Aware discovery interface. On a RESPONDER this
+ * value is used to gate the acceptance of a connection request from only that
+ * peer.
+ * @param passphrase The passphrase to be used to encrypt the link. The PMK is generated from
+ * the passphrase. Use {@link #createNetworkSpecifierOpen(int, byte[])} to
+ * specify an open (unencrypted) link.
+ *
+ * @return A {@link NetworkSpecifier} to be used to construct
+ * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
+ * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
+ * android.net.ConnectivityManager.NetworkCallback)}
+ * [or other varieties of that API].
+ */
+ public NetworkSpecifier createNetworkSpecifierPassphrase(
+ @WifiAwareManager.DataPathRole int role, @NonNull byte[] peer,
+ @NonNull String passphrase) {
+ WifiAwareManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.e(TAG, "createNetworkSpecifierPassphrase: called post GC on WifiAwareManager");
+ return null;
+ }
+ if (mTerminated) {
+ Log.e(TAG, "createNetworkSpecifierPassphrase: called after termination");
+ return null;
+ }
+ if (!WifiAwareUtils.validatePassphrase(passphrase)) {
+ throw new IllegalArgumentException("Passphrase must meet length requirements");
+ }
+
+ return mgr.createNetworkSpecifier(mClientId, role, peer, null, passphrase);
+ }
+
+ /**
+ * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
+ * an encrypted WiFi Aware connection (link) to the specified peer. The
+ * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
+ * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
+ * <p>
+ * This API is targeted for applications which can obtain the peer MAC address using OOB
+ * (out-of-band) discovery. Aware discovery does not provide the MAC address of the peer -
+ * when using Aware discovery use the alternative network specifier method -
+ * {@link android.net.wifi.aware.WifiAwareNetworkSpecifier.Builder}.
+ *
+ * @param role The role of this device:
+ * {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_INITIATOR} or
+ * {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_RESPONDER}
+ * @param peer The MAC address of the peer's Aware discovery interface. On a RESPONDER this
+ * value is used to gate the acceptance of a connection request from only that
+ * peer.
+ * @param pmk A PMK (pairwise master key, see IEEE 802.11i) specifying the key to use for
+ * encrypting the data-path. Use the
+ * {@link #createNetworkSpecifierPassphrase(int, byte[], String)} to specify a
+ * Passphrase or {@link #createNetworkSpecifierOpen(int, byte[])} to specify an
+ * open (unencrypted) link.
+ *
+ * @return A {@link NetworkSpecifier} to be used to construct
+ * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
+ * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
+ * android.net.ConnectivityManager.NetworkCallback)}
+ * [or other varieties of that API].
+ *
+ * @hide
+ */
+ @SystemApi
+ public NetworkSpecifier createNetworkSpecifierPmk(
+ @WifiAwareManager.DataPathRole int role, @NonNull byte[] peer, @NonNull byte[] pmk) {
+ WifiAwareManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.e(TAG, "createNetworkSpecifierPmk: called post GC on WifiAwareManager");
+ return null;
+ }
+ if (mTerminated) {
+ Log.e(TAG, "createNetworkSpecifierPmk: called after termination");
+ return null;
+ }
+ if (!WifiAwareUtils.validatePmk(pmk)) {
+ throw new IllegalArgumentException("PMK must 32 bytes");
+ }
+ return mgr.createNetworkSpecifier(mClientId, role, peer, pmk, null);
+ }
+}
diff --git a/android/net/wifi/aware/WifiAwareUtils.java b/android/net/wifi/aware/WifiAwareUtils.java
new file mode 100644
index 0000000..3ece93d
--- /dev/null
+++ b/android/net/wifi/aware/WifiAwareUtils.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2016 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.aware;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.wifi.V1_0.Constants;
+
+/**
+ * Provides utilities for the Wifi Aware manager/service.
+ *
+ * @hide
+ */
+public class WifiAwareUtils {
+ /**
+ * Per spec: The Service Name is a UTF-8 encoded string from 1 to 255 bytes in length. The
+ * only acceptable single-byte UTF-8 symbols for a Service Name are alphanumeric values (A-Z,
+ * a-z, 0-9), the hyphen ('-'), and the period ('.'). All valid multi-byte UTF-8 characters
+ * are acceptable in a Service Name.
+ */
+ public static void validateServiceName(byte[] serviceNameData) throws IllegalArgumentException {
+ if (serviceNameData == null) {
+ throw new IllegalArgumentException("Invalid service name - null");
+ }
+
+ if (serviceNameData.length < 1 || serviceNameData.length > 255) {
+ throw new IllegalArgumentException("Invalid service name length - must be between "
+ + "1 and 255 bytes (UTF-8 encoding)");
+ }
+
+ int index = 0;
+ while (index < serviceNameData.length) {
+ byte b = serviceNameData[index];
+ if ((b & 0x80) == 0x00) {
+ if (!((b >= '0' && b <= '9') || (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')
+ || b == '-' || b == '.')) {
+ throw new IllegalArgumentException("Invalid service name - illegal characters,"
+ + " allowed = (0-9, a-z,A-Z, -, .)");
+ }
+ }
+ ++index;
+ }
+ }
+
+ /**
+ * Validates that the passphrase is a non-null string of the right size (per the HAL min/max
+ * length parameters).
+ *
+ * @param passphrase Passphrase to test
+ * @return true if passphrase is valid, false if not
+ */
+ public static boolean validatePassphrase(String passphrase) {
+ if (passphrase == null
+ || passphrase.length() < Constants.NanParamSizeLimits.MIN_PASSPHRASE_LENGTH
+ || passphrase.length() > Constants.NanParamSizeLimits.MAX_PASSPHRASE_LENGTH) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Validates that the PMK is a non-null byte array of the right size (32 bytes per spec).
+ *
+ * @param pmk PMK to test
+ * @return true if PMK is valid, false if not
+ */
+ public static boolean validatePmk(byte[] pmk) {
+ if (pmk == null || pmk.length != 32) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns true if the App version is older than minVersion.
+ */
+ public static boolean isLegacyVersion(Context context, int minVersion) {
+ try {
+ if (context.getPackageManager().getApplicationInfo(context.getOpPackageName(), 0)
+ .targetSdkVersion < minVersion) {
+ return true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // In case of exception, assume known app (more strict checking)
+ // Note: This case will never happen since checkPackage is
+ // called to verify valididity before checking App's version.
+ }
+ return false;
+ }
+}
diff --git a/android/net/wifi/hotspot2/ConfigParser.java b/android/net/wifi/hotspot2/ConfigParser.java
new file mode 100644
index 0000000..e8e8731
--- /dev/null
+++ b/android/net/wifi/hotspot2/ConfigParser.java
@@ -0,0 +1,480 @@
+/**
+ * Copyright (c) 2016, 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.hotspot2;
+
+import android.net.wifi.hotspot2.omadm.PpsMoParser;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+import android.util.Pair;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Utility class for building PasspointConfiguration from an installation file.
+ */
+public final class ConfigParser {
+ private static final String TAG = "ConfigParser";
+
+ // Header names.
+ private static final String CONTENT_TYPE = "Content-Type";
+ private static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
+
+ // MIME types.
+ private static final String TYPE_MULTIPART_MIXED = "multipart/mixed";
+ private static final String TYPE_WIFI_CONFIG = "application/x-wifi-config";
+ private static final String TYPE_PASSPOINT_PROFILE = "application/x-passpoint-profile";
+ private static final String TYPE_CA_CERT = "application/x-x509-ca-cert";
+ private static final String TYPE_PKCS12 = "application/x-pkcs12";
+
+ private static final String ENCODING_BASE64 = "base64";
+ private static final String BOUNDARY = "boundary=";
+
+ /**
+ * Class represent a MIME (Multipurpose Internet Mail Extension) part.
+ */
+ private static class MimePart {
+ /**
+ * Content type of the part.
+ */
+ public String type = null;
+
+ /**
+ * Decoded data.
+ */
+ public byte[] data = null;
+
+ /**
+ * Flag indicating if this is the last part (ending with --{boundary}--).
+ */
+ public boolean isLast = false;
+ }
+
+ /**
+ * Class represent the MIME (Multipurpose Internet Mail Extension) header.
+ */
+ private static class MimeHeader {
+ /**
+ * Content type.
+ */
+ public String contentType = null;
+
+ /**
+ * Boundary string (optional), only applies for the outter MIME header.
+ */
+ public String boundary = null;
+
+ /**
+ * Encoding type.
+ */
+ public String encodingType = null;
+ }
+
+ /**
+ * @hide
+ */
+ public ConfigParser() {}
+
+ /**
+ * Parse the Hotspot 2.0 Release 1 configuration data into a {@link PasspointConfiguration}
+ * object. The configuration data is a base64 encoded MIME multipart data. Below is
+ * the format of the decoded message:
+ *
+ * Content-Type: multipart/mixed; boundary={boundary}
+ * Content-Transfer-Encoding: base64
+ * [Skip uninterested headers]
+ *
+ * --{boundary}
+ * Content-Type: application/x-passpoint-profile
+ * Content-Transfer-Encoding: base64
+ *
+ * [base64 encoded Passpoint profile data]
+ * --{boundary}
+ * Content-Type: application/x-x509-ca-cert
+ * Content-Transfer-Encoding: base64
+ *
+ * [base64 encoded X509 CA certificate data]
+ * --{boundary}
+ * Content-Type: application/x-pkcs12
+ * Content-Transfer-Encoding: base64
+ *
+ * [base64 encoded PKCS#12 ASN.1 structure containing client certificate chain]
+ * --{boundary}
+ *
+ * @param mimeType MIME type of the encoded data.
+ * @param data A base64 encoded MIME multipart message containing the Passpoint profile
+ * (required), CA (Certificate Authority) certificate (optional), and client
+ * certificate chain (optional).
+ * @return {@link PasspointConfiguration}
+ */
+ public static PasspointConfiguration parsePasspointConfig(String mimeType, byte[] data) {
+ // Verify MIME type.
+ if (!TextUtils.equals(mimeType, TYPE_WIFI_CONFIG)) {
+ Log.e(TAG, "Unexpected MIME type: " + mimeType);
+ return null;
+ }
+
+ try {
+ // Decode the data.
+ byte[] decodedData = Base64.decode(new String(data, StandardCharsets.ISO_8859_1),
+ Base64.DEFAULT);
+ Map<String, byte[]> mimeParts = parseMimeMultipartMessage(new LineNumberReader(
+ new InputStreamReader(new ByteArrayInputStream(decodedData),
+ StandardCharsets.ISO_8859_1)));
+ return createPasspointConfig(mimeParts);
+ } catch (IOException | IllegalArgumentException e) {
+ Log.e(TAG, "Failed to parse installation file: " + e.getMessage());
+ return null;
+ }
+ }
+
+ /**
+ * Create a {@link PasspointConfiguration} object from list of MIME (Multipurpose Internet
+ * Mail Extension) parts.
+ *
+ * @param mimeParts Map of content type and content data.
+ * @return {@link PasspointConfiguration}
+ * @throws IOException
+ */
+ private static PasspointConfiguration createPasspointConfig(Map<String, byte[]> mimeParts)
+ throws IOException {
+ byte[] profileData = mimeParts.get(TYPE_PASSPOINT_PROFILE);
+ if (profileData == null) {
+ throw new IOException("Missing Passpoint Profile");
+ }
+
+ PasspointConfiguration config = PpsMoParser.parseMoText(new String(profileData));
+ if (config == null) {
+ throw new IOException("Failed to parse Passpoint profile");
+ }
+
+ // Credential is needed for storing the certificates and private client key.
+ if (config.getCredential() == null) {
+ throw new IOException("Passpoint profile missing credential");
+ }
+
+ // Parse CA (Certificate Authority) certificate.
+ byte[] caCertData = mimeParts.get(TYPE_CA_CERT);
+ if (caCertData != null) {
+ try {
+ config.getCredential().setCaCertificate(parseCACert(caCertData));
+ } catch (CertificateException e) {
+ throw new IOException("Failed to parse CA Certificate");
+ }
+ }
+
+ // Parse PKCS12 data for client private key and certificate chain.
+ byte[] pkcs12Data = mimeParts.get(TYPE_PKCS12);
+ if (pkcs12Data != null) {
+ try {
+ Pair<PrivateKey, List<X509Certificate>> clientKey = parsePkcs12(pkcs12Data);
+ config.getCredential().setClientPrivateKey(clientKey.first);
+ config.getCredential().setClientCertificateChain(
+ clientKey.second.toArray(new X509Certificate[clientKey.second.size()]));
+ } catch(GeneralSecurityException | IOException e) {
+ throw new IOException("Failed to parse PCKS12 string");
+ }
+ }
+ return config;
+ }
+
+ /**
+ * Parse a MIME (Multipurpose Internet Mail Extension) multipart message from the given
+ * input stream.
+ *
+ * @param in The input stream for reading the message data
+ * @return A map of a content type and content data pair
+ * @throws IOException
+ */
+ private static Map<String, byte[]> parseMimeMultipartMessage(LineNumberReader in)
+ throws IOException {
+ // Parse the outer MIME header.
+ MimeHeader header = parseHeaders(in);
+ if (!TextUtils.equals(header.contentType, TYPE_MULTIPART_MIXED)) {
+ throw new IOException("Invalid content type: " + header.contentType);
+ }
+ if (TextUtils.isEmpty(header.boundary)) {
+ throw new IOException("Missing boundary string");
+ }
+ if (!TextUtils.equals(header.encodingType, ENCODING_BASE64)) {
+ throw new IOException("Unexpected encoding: " + header.encodingType);
+ }
+
+ // Read pass the first boundary string.
+ for (;;) {
+ String line = in.readLine();
+ if (line == null) {
+ throw new IOException("Unexpected EOF before first boundary @ " +
+ in.getLineNumber());
+ }
+ if (line.equals("--" + header.boundary)) {
+ break;
+ }
+ }
+
+ // Parse each MIME part.
+ Map<String, byte[]> mimeParts = new HashMap<>();
+ boolean isLast = false;
+ do {
+ MimePart mimePart = parseMimePart(in, header.boundary);
+ mimeParts.put(mimePart.type, mimePart.data);
+ isLast = mimePart.isLast;
+ } while(!isLast);
+ return mimeParts;
+ }
+
+ /**
+ * Parse a MIME (Multipurpose Internet Mail Extension) part. We expect the data to
+ * be encoded in base64.
+ *
+ * @param in Input stream to read the data from
+ * @param boundary Boundary string indicate the end of the part
+ * @return {@link MimePart}
+ * @throws IOException
+ */
+ private static MimePart parseMimePart(LineNumberReader in, String boundary)
+ throws IOException {
+ MimeHeader header = parseHeaders(in);
+ // Expect encoding type to be base64.
+ if (!TextUtils.equals(header.encodingType, ENCODING_BASE64)) {
+ throw new IOException("Unexpected encoding type: " + header.encodingType);
+ }
+
+ // Check for a valid content type.
+ if (!TextUtils.equals(header.contentType, TYPE_PASSPOINT_PROFILE) &&
+ !TextUtils.equals(header.contentType, TYPE_CA_CERT) &&
+ !TextUtils.equals(header.contentType, TYPE_PKCS12)) {
+ throw new IOException("Unexpected content type: " + header.contentType);
+ }
+
+ StringBuilder text = new StringBuilder();
+ boolean isLast = false;
+ String partBoundary = "--" + boundary;
+ String endBoundary = partBoundary + "--";
+ for (;;) {
+ String line = in.readLine();
+ if (line == null) {
+ throw new IOException("Unexpected EOF file in body @ " + in.getLineNumber());
+ }
+ // Check for boundary line.
+ if (line.startsWith(partBoundary)) {
+ if (line.equals(endBoundary)) {
+ isLast = true;
+ }
+ break;
+ }
+ text.append(line);
+ }
+
+ MimePart part = new MimePart();
+ part.type = header.contentType;
+ part.data = Base64.decode(text.toString(), Base64.DEFAULT);
+ part.isLast = isLast;
+ return part;
+ }
+
+ /**
+ * Parse a MIME (Multipurpose Internet Mail Extension) header from the input stream.
+ * @param in Input stream to read from.
+ * @return {@link MimeHeader}
+ * @throws IOException
+ */
+ private static MimeHeader parseHeaders(LineNumberReader in)
+ throws IOException {
+ MimeHeader header = new MimeHeader();
+
+ // Read the header from the input stream.
+ Map<String, String> headers = readHeaders(in);
+
+ // Parse each header.
+ for (Map.Entry<String, String> entry : headers.entrySet()) {
+ switch (entry.getKey()) {
+ case CONTENT_TYPE:
+ Pair<String, String> value = parseContentType(entry.getValue());
+ header.contentType = value.first;
+ header.boundary = value.second;
+ break;
+ case CONTENT_TRANSFER_ENCODING:
+ header.encodingType = entry.getValue();
+ break;
+ default:
+ Log.d(TAG, "Ignore header: " + entry.getKey());
+ break;
+ }
+ }
+ return header;
+ }
+
+ /**
+ * Parse the Content-Type header value. The value will contain the content type string and
+ * an optional boundary string separated by a ";". Below are examples of valid Content-Type
+ * header value:
+ * multipart/mixed; boundary={boundary}
+ * application/x-passpoint-profile
+ *
+ * @param contentType The Content-Type value string
+ * @return A pair of content type and boundary string
+ * @throws IOException
+ */
+ private static Pair<String, String> parseContentType(String contentType) throws IOException {
+ String[] attributes = contentType.split(";");
+ String type = null;
+ String boundary = null;
+
+ if (attributes.length < 1) {
+ throw new IOException("Invalid Content-Type: " + contentType);
+ }
+
+ // The type is always the first attribute.
+ type = attributes[0].trim();
+ // Look for boundary string from the rest of the attributes.
+ for (int i = 1; i < attributes.length; i++) {
+ String attribute = attributes[i].trim();
+ if (!attribute.startsWith(BOUNDARY)) {
+ Log.d(TAG, "Ignore Content-Type attribute: " + attributes[i]);
+ continue;
+ }
+ boundary = attribute.substring(BOUNDARY.length());
+ // Remove the leading and trailing quote if present.
+ if (boundary.length() > 1 && boundary.startsWith("\"") && boundary.endsWith("\"")) {
+ boundary = boundary.substring(1, boundary.length()-1);
+ }
+ }
+
+ return new Pair<String, String>(type, boundary);
+ }
+
+ /**
+ * Read the headers from the given input stream. The header section is terminated by
+ * an empty line.
+ *
+ * @param in The input stream to read from
+ * @return Map of key-value pairs.
+ * @throws IOException
+ */
+ private static Map<String, String> readHeaders(LineNumberReader in)
+ throws IOException {
+ Map<String, String> headers = new HashMap<>();
+ String line;
+ String name = null;
+ StringBuilder value = null;
+ for (;;) {
+ line = in.readLine();
+ if (line == null) {
+ throw new IOException("Missing line @ " + in.getLineNumber());
+ }
+
+ // End of headers section.
+ if (line.length() == 0 || line.trim().length() == 0) {
+ // Save the previous header line.
+ if (name != null) {
+ headers.put(name, value.toString());
+ }
+ break;
+ }
+
+ int nameEnd = line.indexOf(':');
+ if (nameEnd < 0) {
+ if (value != null) {
+ // Continuation line for the header value.
+ value.append(' ').append(line.trim());
+ } else {
+ throw new IOException("Bad header line: '" + line + "' @ " +
+ in.getLineNumber());
+ }
+ } else {
+ // New header line detected, make sure it doesn't start with a whitespace.
+ if (Character.isWhitespace(line.charAt(0))) {
+ throw new IOException("Illegal blank prefix in header line '" + line +
+ "' @ " + in.getLineNumber());
+ }
+
+ if (name != null) {
+ // Save the previous header line.
+ headers.put(name, value.toString());
+ }
+
+ // Setup the current header line.
+ name = line.substring(0, nameEnd).trim();
+ value = new StringBuilder();
+ value.append(line.substring(nameEnd+1).trim());
+ }
+ }
+ return headers;
+ }
+
+ /**
+ * Parse a CA (Certificate Authority) certificate data and convert it to a
+ * X509Certificate object.
+ *
+ * @param octets Certificate data
+ * @return X509Certificate
+ * @throws CertificateException
+ */
+ private static X509Certificate parseCACert(byte[] octets) throws CertificateException {
+ CertificateFactory factory = CertificateFactory.getInstance("X.509");
+ return (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(octets));
+ }
+
+ private static Pair<PrivateKey, List<X509Certificate>> parsePkcs12(byte[] octets)
+ throws GeneralSecurityException, IOException {
+ KeyStore ks = KeyStore.getInstance("PKCS12");
+ ByteArrayInputStream in = new ByteArrayInputStream(octets);
+ ks.load(in, new char[0]);
+ in.close();
+
+ // Only expects one set of key and certificate chain.
+ if (ks.size() != 1) {
+ throw new IOException("Unexpected key size: " + ks.size());
+ }
+
+ String alias = ks.aliases().nextElement();
+ if (alias == null) {
+ throw new IOException("No alias found");
+ }
+
+ PrivateKey clientKey = (PrivateKey) ks.getKey(alias, null);
+ List<X509Certificate> clientCertificateChain = null;
+ Certificate[] chain = ks.getCertificateChain(alias);
+ if (chain != null) {
+ clientCertificateChain = new ArrayList<>();
+ for (Certificate certificate : chain) {
+ if (!(certificate instanceof X509Certificate)) {
+ throw new IOException("Unexpceted certificate type: " +
+ certificate.getClass());
+ }
+ clientCertificateChain.add((X509Certificate) certificate);
+ }
+ }
+ return new Pair<PrivateKey, List<X509Certificate>>(clientKey, clientCertificateChain);
+ }
+}
diff --git a/android/net/wifi/hotspot2/OsuProvider.java b/android/net/wifi/hotspot2/OsuProvider.java
new file mode 100644
index 0000000..57dff9d
--- /dev/null
+++ b/android/net/wifi/hotspot2/OsuProvider.java
@@ -0,0 +1,283 @@
+/*
+ * 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 android.net.wifi.hotspot2;
+
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.net.wifi.WifiSsid;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Contained information for a Hotspot 2.0 OSU (Online Sign-Up provider).
+ *
+ * @hide
+ */
+@SystemApi
+public final class OsuProvider implements Parcelable {
+ /**
+ * OSU (Online Sign-Up) method: OMA DM (Open Mobile Alliance Device Management).
+ * For more info, refer to Section 8.3 of the Hotspot 2.0 Release 2 Technical Specification.
+ * @hide
+ */
+ public static final int METHOD_OMA_DM = 0;
+
+ /**
+ * OSU (Online Sign-Up) method: SOAP XML SPP (Subscription Provisioning Protocol).
+ * For more info, refer to Section 8.4 of the Hotspot 2.0 Release 2 Technical Specification.
+ * @hide
+ */
+ public static final int METHOD_SOAP_XML_SPP = 1;
+
+ /**
+ * SSID of the network to connect for service sign-up.
+ */
+ private WifiSsid mOsuSsid;
+
+ /**
+ * Map of friendly names expressed as different language for the OSU provider.
+ */
+ private final Map<String, String> mFriendlyNames;
+
+ /**
+ * Description of the OSU provider.
+ */
+ private final String mServiceDescription;
+
+ /**
+ * URI to browse to for service sign-up.
+ */
+ private final Uri mServerUri;
+
+ /**
+ * Network Access Identifier used for authenticating with the OSU network when OSEN is used.
+ */
+ private final String mNetworkAccessIdentifier;
+
+ /**
+ * List of OSU (Online Sign-Up) method supported.
+ */
+ private final List<Integer> mMethodList;
+
+ /**
+ * Icon data for the OSU (Online Sign-Up) provider.
+ */
+ private final Icon mIcon;
+
+ /** @hide */
+ public OsuProvider(WifiSsid osuSsid, Map<String, String> friendlyNames,
+ String serviceDescription, Uri serverUri, String nai, List<Integer> methodList,
+ Icon icon) {
+ mOsuSsid = osuSsid;
+ mFriendlyNames = friendlyNames;
+ mServiceDescription = serviceDescription;
+ mServerUri = serverUri;
+ mNetworkAccessIdentifier = nai;
+ if (methodList == null) {
+ mMethodList = new ArrayList<>();
+ } else {
+ mMethodList = new ArrayList<>(methodList);
+ }
+ mIcon = icon;
+ }
+
+ /**
+ * Copy constructor.
+ *
+ * @param source The source to copy from
+ * @hide
+ */
+ public OsuProvider(OsuProvider source) {
+ if (source == null) {
+ mOsuSsid = null;
+ mFriendlyNames = null;
+ mServiceDescription = null;
+ mServerUri = null;
+ mNetworkAccessIdentifier = null;
+ mMethodList = new ArrayList<>();
+ mIcon = null;
+ return;
+ }
+
+ mOsuSsid = source.mOsuSsid;
+ mFriendlyNames = source.mFriendlyNames;
+ mServiceDescription = source.mServiceDescription;
+ mServerUri = source.mServerUri;
+ mNetworkAccessIdentifier = source.mNetworkAccessIdentifier;
+ if (source.mMethodList == null) {
+ mMethodList = new ArrayList<>();
+ } else {
+ mMethodList = new ArrayList<>(source.mMethodList);
+ }
+ mIcon = source.mIcon;
+ }
+
+ /** @hide */
+ public WifiSsid getOsuSsid() {
+ return mOsuSsid;
+ }
+
+ /** @hide */
+ public void setOsuSsid(WifiSsid osuSsid) {
+ mOsuSsid = osuSsid;
+ }
+
+ /**
+ * Return the friendly Name for current language from the list of friendly names of OSU
+ * provider.
+ *
+ * The string matching the default locale will be returned if it is found, otherwise the string
+ * in english or the first string in the list will be returned if english is not found.
+ * A null will be returned if the list is empty.
+ *
+ * @return String matching the default locale, null otherwise
+ */
+ public @Nullable String getFriendlyName() {
+ if (mFriendlyNames == null || mFriendlyNames.isEmpty()) return null;
+ String lang = Locale.getDefault().getLanguage();
+ String friendlyName = mFriendlyNames.get(lang);
+ if (friendlyName != null) {
+ return friendlyName;
+ }
+ friendlyName = mFriendlyNames.get("en");
+ if (friendlyName != null) {
+ return friendlyName;
+ }
+ return mFriendlyNames.get(mFriendlyNames.keySet().stream().findFirst().get());
+ }
+
+ /** @hide */
+ public Map<String, String> getFriendlyNameList() {
+ return mFriendlyNames;
+ }
+
+ /** @hide */
+ public String getServiceDescription() {
+ return mServiceDescription;
+ }
+
+ public @Nullable Uri getServerUri() {
+ return mServerUri;
+ }
+
+ /** @hide */
+ public String getNetworkAccessIdentifier() {
+ return mNetworkAccessIdentifier;
+ }
+
+ /** @hide */
+ public List<Integer> getMethodList() {
+ return mMethodList;
+ }
+
+ /** @hide */
+ public Icon getIcon() {
+ return mIcon;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mOsuSsid, flags);
+ dest.writeString(mServiceDescription);
+ dest.writeParcelable(mServerUri, flags);
+ dest.writeString(mNetworkAccessIdentifier);
+ dest.writeList(mMethodList);
+ dest.writeParcelable(mIcon, flags);
+ Bundle bundle = new Bundle();
+ bundle.putSerializable("friendlyNameMap", (HashMap<String, String>) mFriendlyNames);
+ dest.writeBundle(bundle);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof OsuProvider)) {
+ return false;
+ }
+ OsuProvider that = (OsuProvider) thatObject;
+ return (mOsuSsid == null ? that.mOsuSsid == null : mOsuSsid.equals(that.mOsuSsid))
+ && (mFriendlyNames == null) ? that.mFriendlyNames == null
+ : mFriendlyNames.equals(that.mFriendlyNames)
+ && TextUtils.equals(mServiceDescription, that.mServiceDescription)
+ && (mServerUri == null ? that.mServerUri == null
+ : mServerUri.equals(that.mServerUri))
+ && TextUtils.equals(mNetworkAccessIdentifier, that.mNetworkAccessIdentifier)
+ && (mMethodList == null ? that.mMethodList == null
+ : mMethodList.equals(that.mMethodList))
+ && (mIcon == null ? that.mIcon == null : mIcon.sameAs(that.mIcon));
+ }
+
+ @Override
+ public int hashCode() {
+ // mIcon is not hashable, skip the variable.
+ return Objects.hash(mOsuSsid, mServiceDescription, mFriendlyNames,
+ mServerUri, mNetworkAccessIdentifier, mMethodList);
+ }
+
+ @Override
+ public String toString() {
+ return "OsuProvider{mOsuSsid=" + mOsuSsid
+ + " mFriendlyNames=" + mFriendlyNames
+ + " mServiceDescription=" + mServiceDescription
+ + " mServerUri=" + mServerUri
+ + " mNetworkAccessIdentifier=" + mNetworkAccessIdentifier
+ + " mMethodList=" + mMethodList
+ + " mIcon=" + mIcon;
+ }
+
+ public static final @android.annotation.NonNull Creator<OsuProvider> CREATOR =
+ new Creator<OsuProvider>() {
+ @Override
+ public OsuProvider createFromParcel(Parcel in) {
+ WifiSsid osuSsid = in.readParcelable(null);
+ String serviceDescription = in.readString();
+ Uri serverUri = in.readParcelable(null);
+ String nai = in.readString();
+ List<Integer> methodList = new ArrayList<>();
+ in.readList(methodList, null);
+ Icon icon = in.readParcelable(null);
+ Bundle bundle = in.readBundle();
+ Map<String, String> friendlyNamesMap = (HashMap) bundle.getSerializable(
+ "friendlyNameMap");
+ return new OsuProvider(osuSsid, friendlyNamesMap, serviceDescription,
+ serverUri, nai, methodList, icon);
+ }
+
+ @Override
+ public OsuProvider[] newArray(int size) {
+ return new OsuProvider[size];
+ }
+ };
+}
diff --git a/android/net/wifi/hotspot2/PasspointConfiguration.java b/android/net/wifi/hotspot2/PasspointConfiguration.java
new file mode 100644
index 0000000..9095b5d
--- /dev/null
+++ b/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -0,0 +1,698 @@
+/**
+ * Copyright (c) 2016, 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.hotspot2;
+
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.HomeSp;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Class representing Passpoint configuration. This contains configurations specified in
+ * PerProviderSubscription (PPS) Management Object (MO) tree.
+ *
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ */
+public final class PasspointConfiguration implements Parcelable {
+ private static final String TAG = "PasspointConfiguration";
+
+ /**
+ * Number of bytes for certificate SHA-256 fingerprint byte array.
+ */
+ private static final int CERTIFICATE_SHA256_BYTES = 32;
+
+ /**
+ * Maximum bytes for URL string.
+ */
+ private static final int MAX_URL_BYTES = 1023;
+
+ /**
+ * Integer value used for indicating null value in the Parcel.
+ */
+ private static final int NULL_VALUE = -1;
+
+ /**
+ * Configurations under HomeSp subtree.
+ */
+ private HomeSp mHomeSp = null;
+ /**
+ * Set the Home SP (Service Provider) information.
+ *
+ * @param homeSp The Home SP information to set to
+ */
+ public void setHomeSp(HomeSp homeSp) { mHomeSp = homeSp; }
+ /**
+ * Get the Home SP (Service Provider) information.
+ *
+ * @return Home SP information
+ */
+ public HomeSp getHomeSp() { return mHomeSp; }
+
+ /**
+ * Configurations under Credential subtree.
+ */
+ private Credential mCredential = null;
+ /**
+ * Set the credential information.
+ *
+ * @param credential The credential information to set to
+ */
+ public void setCredential(Credential credential) {
+ mCredential = credential;
+ }
+ /**
+ * Get the credential information.
+ *
+ * @return credential information
+ */
+ public Credential getCredential() {
+ return mCredential;
+ }
+
+ /**
+ * Configurations under Policy subtree.
+ */
+ private Policy mPolicy = null;
+ /**
+ * @hide
+ */
+ public void setPolicy(Policy policy) {
+ mPolicy = policy;
+ }
+ /**
+ * @hide
+ */
+ public Policy getPolicy() {
+ return mPolicy;
+ }
+
+ /**
+ * Meta data for performing subscription update.
+ */
+ private UpdateParameter mSubscriptionUpdate = null;
+ /**
+ * @hide
+ */
+ public void setSubscriptionUpdate(UpdateParameter subscriptionUpdate) {
+ mSubscriptionUpdate = subscriptionUpdate;
+ }
+ /**
+ * @hide
+ */
+ public UpdateParameter getSubscriptionUpdate() {
+ return mSubscriptionUpdate;
+ }
+
+ /**
+ * List of HTTPS URL for retrieving trust root certificate and the corresponding SHA-256
+ * fingerprint of the certificate. The certificates are used for verifying AAA server's
+ * identity during EAP authentication.
+ */
+ private Map<String, byte[]> mTrustRootCertList = null;
+ /**
+ * @hide
+ */
+ public void setTrustRootCertList(Map<String, byte[]> trustRootCertList) {
+ mTrustRootCertList = trustRootCertList;
+ }
+ /**
+ * @hide
+ */
+ public Map<String, byte[]> getTrustRootCertList() {
+ return mTrustRootCertList;
+ }
+
+ /**
+ * Set by the subscription server, updated every time the configuration is updated by
+ * the subscription server.
+ *
+ * Use Integer.MIN_VALUE to indicate unset value.
+ */
+ private int mUpdateIdentifier = Integer.MIN_VALUE;
+ /**
+ * @hide
+ */
+ public void setUpdateIdentifier(int updateIdentifier) {
+ mUpdateIdentifier = updateIdentifier;
+ }
+ /**
+ * @hide
+ */
+ public int getUpdateIdentifier() {
+ return mUpdateIdentifier;
+ }
+
+ /**
+ * The priority of the credential.
+ *
+ * Use Integer.MIN_VALUE to indicate unset value.
+ */
+ private int mCredentialPriority = Integer.MIN_VALUE;
+ /**
+ * @hide
+ */
+ public void setCredentialPriority(int credentialPriority) {
+ mCredentialPriority = credentialPriority;
+ }
+ /**
+ * @hide
+ */
+ public int getCredentialPriority() {
+ return mCredentialPriority;
+ }
+
+ /**
+ * The time this subscription is created. It is in the format of number
+ * of milliseconds since January 1, 1970, 00:00:00 GMT.
+ *
+ * Use Long.MIN_VALUE to indicate unset value.
+ */
+ private long mSubscriptionCreationTimeInMillis = Long.MIN_VALUE;
+ /**
+ * @hide
+ */
+ public void setSubscriptionCreationTimeInMillis(long subscriptionCreationTimeInMillis) {
+ mSubscriptionCreationTimeInMillis = subscriptionCreationTimeInMillis;
+ }
+ /**
+ * @hide
+ */
+ public long getSubscriptionCreationTimeInMillis() {
+ return mSubscriptionCreationTimeInMillis;
+ }
+
+ /**
+ * The time this subscription will expire. It is in the format of number
+ * of milliseconds since January 1, 1970, 00:00:00 GMT.
+ *
+ * Use Long.MIN_VALUE to indicate unset value.
+ */
+ private long mSubscriptionExpirationTimeInMillis = Long.MIN_VALUE;
+ /**
+ * @hide
+ */
+ public void setSubscriptionExpirationTimeInMillis(long subscriptionExpirationTimeInMillis) {
+ mSubscriptionExpirationTimeInMillis = subscriptionExpirationTimeInMillis;
+ }
+ /**
+ * @hide
+ */
+ public long getSubscriptionExpirationTimeInMillis() {
+ return mSubscriptionExpirationTimeInMillis;
+ }
+
+ /**
+ * The type of the subscription. This is defined by the provider and the value is provider
+ * specific.
+ */
+ private String mSubscriptionType = null;
+ /**
+ * @hide
+ */
+ public void setSubscriptionType(String subscriptionType) {
+ mSubscriptionType = subscriptionType;
+ }
+ /**
+ * @hide
+ */
+ public String getSubscriptionType() {
+ return mSubscriptionType;
+ }
+
+ /**
+ * The time period for usage statistics accumulation. A value of zero means that usage
+ * statistics are not accumulated on a periodic basis (e.g., a one-time limit for
+ * “pay as you go” - PAYG service). A non-zero value specifies the usage interval in minutes.
+ */
+ private long mUsageLimitUsageTimePeriodInMinutes = Long.MIN_VALUE;
+ /**
+ * @hide
+ */
+ public void setUsageLimitUsageTimePeriodInMinutes(long usageLimitUsageTimePeriodInMinutes) {
+ mUsageLimitUsageTimePeriodInMinutes = usageLimitUsageTimePeriodInMinutes;
+ }
+ /**
+ * @hide
+ */
+ public long getUsageLimitUsageTimePeriodInMinutes() {
+ return mUsageLimitUsageTimePeriodInMinutes;
+ }
+
+ /**
+ * The time at which usage statistic accumulation begins. It is in the format of number
+ * of milliseconds since January 1, 1970, 00:00:00 GMT.
+ *
+ * Use Long.MIN_VALUE to indicate unset value.
+ */
+ private long mUsageLimitStartTimeInMillis = Long.MIN_VALUE;
+ /**
+ * @hide
+ */
+ public void setUsageLimitStartTimeInMillis(long usageLimitStartTimeInMillis) {
+ mUsageLimitStartTimeInMillis = usageLimitStartTimeInMillis;
+ }
+ /**
+ * @hide
+ */
+ public long getUsageLimitStartTimeInMillis() {
+ return mUsageLimitStartTimeInMillis;
+ }
+
+ /**
+ * The cumulative data limit in megabytes for the {@link #usageLimitUsageTimePeriodInMinutes}.
+ * A value of zero indicate unlimited data usage.
+ *
+ * Use Long.MIN_VALUE to indicate unset value.
+ */
+ private long mUsageLimitDataLimit = Long.MIN_VALUE;
+ /**
+ * @hide
+ */
+ public void setUsageLimitDataLimit(long usageLimitDataLimit) {
+ mUsageLimitDataLimit = usageLimitDataLimit;
+ }
+ /**
+ * @hide
+ */
+ public long getUsageLimitDataLimit() {
+ return mUsageLimitDataLimit;
+ }
+
+ /**
+ * The cumulative time limit in minutes for the {@link #usageLimitUsageTimePeriodInMinutes}.
+ * A value of zero indicate unlimited time usage.
+ */
+ private long mUsageLimitTimeLimitInMinutes = Long.MIN_VALUE;
+ /**
+ * @hide
+ */
+ public void setUsageLimitTimeLimitInMinutes(long usageLimitTimeLimitInMinutes) {
+ mUsageLimitTimeLimitInMinutes = usageLimitTimeLimitInMinutes;
+ }
+ /**
+ * @hide
+ */
+ public long getUsageLimitTimeLimitInMinutes() {
+ return mUsageLimitTimeLimitInMinutes;
+ }
+
+ /**
+ * The map of OSU service provider names whose each element is presented in different
+ * languages for the service provider, which is used for finding a matching
+ * PasspointConfiguration with a given service provider name.
+ */
+ private Map<String, String> mServiceFriendlyNames = null;
+
+ /**
+ * @hide
+ */
+ public void setServiceFriendlyNames(Map<String, String> serviceFriendlyNames) {
+ mServiceFriendlyNames = serviceFriendlyNames;
+ }
+
+ /**
+ * @hide
+ */
+ public Map<String, String> getServiceFriendlyNames() {
+ return mServiceFriendlyNames;
+ }
+
+ /**
+ * Return the friendly Name for current language from the list of friendly names of OSU
+ * provider.
+ * The string matching the default locale will be returned if it is found, otherwise the
+ * first string in the list will be returned. A null will be returned if the list is empty.
+ *
+ * @return String matching the default locale, null otherwise
+ * @hide
+ */
+ public String getServiceFriendlyName() {
+ if (mServiceFriendlyNames == null || mServiceFriendlyNames.isEmpty()) return null;
+ String lang = Locale.getDefault().getLanguage();
+ String friendlyName = mServiceFriendlyNames.get(lang);
+ if (friendlyName != null) {
+ return friendlyName;
+ }
+ friendlyName = mServiceFriendlyNames.get("en");
+ if (friendlyName != null) {
+ return friendlyName;
+ }
+ return mServiceFriendlyNames.get(mServiceFriendlyNames.keySet().stream().findFirst().get());
+ }
+
+ /**
+ * Constructor for creating PasspointConfiguration with default values.
+ */
+ public PasspointConfiguration() {}
+
+ /**
+ * Copy constructor.
+ *
+ * @param source The source to copy from
+ */
+ public PasspointConfiguration(PasspointConfiguration source) {
+ if (source == null) {
+ return;
+ }
+
+ if (source.mHomeSp != null) {
+ mHomeSp = new HomeSp(source.mHomeSp);
+ }
+ if (source.mCredential != null) {
+ mCredential = new Credential(source.mCredential);
+ }
+ if (source.mPolicy != null) {
+ mPolicy = new Policy(source.mPolicy);
+ }
+ if (source.mTrustRootCertList != null) {
+ mTrustRootCertList = Collections.unmodifiableMap(source.mTrustRootCertList);
+ }
+ if (source.mSubscriptionUpdate != null) {
+ mSubscriptionUpdate = new UpdateParameter(source.mSubscriptionUpdate);
+ }
+ mUpdateIdentifier = source.mUpdateIdentifier;
+ mCredentialPriority = source.mCredentialPriority;
+ mSubscriptionCreationTimeInMillis = source.mSubscriptionCreationTimeInMillis;
+ mSubscriptionExpirationTimeInMillis = source.mSubscriptionExpirationTimeInMillis;
+ mSubscriptionType = source.mSubscriptionType;
+ mUsageLimitDataLimit = source.mUsageLimitDataLimit;
+ mUsageLimitStartTimeInMillis = source.mUsageLimitStartTimeInMillis;
+ mUsageLimitTimeLimitInMinutes = source.mUsageLimitTimeLimitInMinutes;
+ mUsageLimitUsageTimePeriodInMinutes = source.mUsageLimitUsageTimePeriodInMinutes;
+ mServiceFriendlyNames = source.mServiceFriendlyNames;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mHomeSp, flags);
+ dest.writeParcelable(mCredential, flags);
+ dest.writeParcelable(mPolicy, flags);
+ dest.writeParcelable(mSubscriptionUpdate, flags);
+ writeTrustRootCerts(dest, mTrustRootCertList);
+ dest.writeInt(mUpdateIdentifier);
+ dest.writeInt(mCredentialPriority);
+ dest.writeLong(mSubscriptionCreationTimeInMillis);
+ dest.writeLong(mSubscriptionExpirationTimeInMillis);
+ dest.writeString(mSubscriptionType);
+ dest.writeLong(mUsageLimitUsageTimePeriodInMinutes);
+ dest.writeLong(mUsageLimitStartTimeInMillis);
+ dest.writeLong(mUsageLimitDataLimit);
+ dest.writeLong(mUsageLimitTimeLimitInMinutes);
+ Bundle bundle = new Bundle();
+ bundle.putSerializable("serviceFriendlyNames",
+ (HashMap<String, String>) mServiceFriendlyNames);
+ dest.writeBundle(bundle);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof PasspointConfiguration)) {
+ return false;
+ }
+ PasspointConfiguration that = (PasspointConfiguration) thatObject;
+ return (mHomeSp == null ? that.mHomeSp == null : mHomeSp.equals(that.mHomeSp))
+ && (mCredential == null ? that.mCredential == null
+ : mCredential.equals(that.mCredential))
+ && (mPolicy == null ? that.mPolicy == null : mPolicy.equals(that.mPolicy))
+ && (mSubscriptionUpdate == null ? that.mSubscriptionUpdate == null
+ : mSubscriptionUpdate.equals(that.mSubscriptionUpdate))
+ && isTrustRootCertListEquals(mTrustRootCertList, that.mTrustRootCertList)
+ && mUpdateIdentifier == that.mUpdateIdentifier
+ && mCredentialPriority == that.mCredentialPriority
+ && mSubscriptionCreationTimeInMillis == that.mSubscriptionCreationTimeInMillis
+ && mSubscriptionExpirationTimeInMillis == that.mSubscriptionExpirationTimeInMillis
+ && TextUtils.equals(mSubscriptionType, that.mSubscriptionType)
+ && mUsageLimitUsageTimePeriodInMinutes == that.mUsageLimitUsageTimePeriodInMinutes
+ && mUsageLimitStartTimeInMillis == that.mUsageLimitStartTimeInMillis
+ && mUsageLimitDataLimit == that.mUsageLimitDataLimit
+ && mUsageLimitTimeLimitInMinutes == that.mUsageLimitTimeLimitInMinutes
+ && (mServiceFriendlyNames == null ? that.mServiceFriendlyNames == null
+ : mServiceFriendlyNames.equals(that.mServiceFriendlyNames));
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mHomeSp, mCredential, mPolicy, mSubscriptionUpdate, mTrustRootCertList,
+ mUpdateIdentifier, mCredentialPriority, mSubscriptionCreationTimeInMillis,
+ mSubscriptionExpirationTimeInMillis, mUsageLimitUsageTimePeriodInMinutes,
+ mUsageLimitStartTimeInMillis, mUsageLimitDataLimit, mUsageLimitTimeLimitInMinutes,
+ mServiceFriendlyNames);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("UpdateIdentifier: ").append(mUpdateIdentifier).append("\n");
+ builder.append("CredentialPriority: ").append(mCredentialPriority).append("\n");
+ builder.append("SubscriptionCreationTime: ").append(
+ mSubscriptionCreationTimeInMillis != Long.MIN_VALUE
+ ? new Date(mSubscriptionCreationTimeInMillis) : "Not specified").append("\n");
+ builder.append("SubscriptionExpirationTime: ").append(
+ mSubscriptionExpirationTimeInMillis != Long.MIN_VALUE
+ ? new Date(mSubscriptionExpirationTimeInMillis) : "Not specified").append("\n");
+ builder.append("UsageLimitStartTime: ").append(mUsageLimitStartTimeInMillis != Long.MIN_VALUE
+ ? new Date(mUsageLimitStartTimeInMillis) : "Not specified").append("\n");
+ builder.append("UsageTimePeriod: ").append(mUsageLimitUsageTimePeriodInMinutes)
+ .append("\n");
+ builder.append("UsageLimitDataLimit: ").append(mUsageLimitDataLimit).append("\n");
+ builder.append("UsageLimitTimeLimit: ").append(mUsageLimitTimeLimitInMinutes).append("\n");
+ if (mHomeSp != null) {
+ builder.append("HomeSP Begin ---\n");
+ builder.append(mHomeSp);
+ builder.append("HomeSP End ---\n");
+ }
+ if (mCredential != null) {
+ builder.append("Credential Begin ---\n");
+ builder.append(mCredential);
+ builder.append("Credential End ---\n");
+ }
+ if (mPolicy != null) {
+ builder.append("Policy Begin ---\n");
+ builder.append(mPolicy);
+ builder.append("Policy End ---\n");
+ }
+ if (mSubscriptionUpdate != null) {
+ builder.append("SubscriptionUpdate Begin ---\n");
+ builder.append(mSubscriptionUpdate);
+ builder.append("SubscriptionUpdate End ---\n");
+ }
+ if (mTrustRootCertList != null) {
+ builder.append("TrustRootCertServers: ").append(mTrustRootCertList.keySet())
+ .append("\n");
+ }
+ if (mServiceFriendlyNames != null) {
+ builder.append("ServiceFriendlyNames: ").append(mServiceFriendlyNames);
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Validate the R1 configuration data.
+ *
+ * @return true on success or false on failure
+ * @hide
+ */
+ public boolean validate() {
+ // Optional: PerProviderSubscription/<X+>/SubscriptionUpdate
+ if (mSubscriptionUpdate != null && !mSubscriptionUpdate.validate()) {
+ return false;
+ }
+ return validateForCommonR1andR2(true);
+ }
+
+ /**
+ * Validate the R2 configuration data.
+ *
+ * @return true on success or false on failure
+ * @hide
+ */
+ public boolean validateForR2() {
+ // Required: PerProviderSubscription/UpdateIdentifier
+ if (mUpdateIdentifier == Integer.MIN_VALUE) {
+ return false;
+ }
+
+ // Required: PerProviderSubscription/<X+>/SubscriptionUpdate
+ if (mSubscriptionUpdate == null || !mSubscriptionUpdate.validate()) {
+ return false;
+ }
+ return validateForCommonR1andR2(false);
+ }
+
+ private boolean validateForCommonR1andR2(boolean isR1) {
+ // Required: PerProviderSubscription/<X+>/HomeSP
+ if (mHomeSp == null || !mHomeSp.validate()) {
+ return false;
+ }
+
+ // Required: PerProviderSubscription/<X+>/Credential
+ if (mCredential == null || !mCredential.validate(isR1)) {
+ return false;
+ }
+
+ // Optional: PerProviderSubscription/<X+>/Policy
+ if (mPolicy != null && !mPolicy.validate()) {
+ return false;
+ }
+
+ if (mTrustRootCertList != null) {
+ for (Map.Entry<String, byte[]> entry : mTrustRootCertList.entrySet()) {
+ String url = entry.getKey();
+ byte[] certFingerprint = entry.getValue();
+ if (TextUtils.isEmpty(url)) {
+ Log.d(TAG, "Empty URL");
+ return false;
+ }
+ if (url.getBytes(StandardCharsets.UTF_8).length > MAX_URL_BYTES) {
+ Log.d(TAG, "URL bytes exceeded the max: "
+ + url.getBytes(StandardCharsets.UTF_8).length);
+ return false;
+ }
+
+ if (certFingerprint == null) {
+ Log.d(TAG, "Fingerprint not specified");
+ return false;
+ }
+ if (certFingerprint.length != CERTIFICATE_SHA256_BYTES) {
+ Log.d(TAG, "Incorrect size of trust root certificate SHA-256 fingerprint: "
+ + certFingerprint.length);
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ public static final @android.annotation.NonNull Creator<PasspointConfiguration> CREATOR =
+ new Creator<PasspointConfiguration>() {
+ @Override
+ public PasspointConfiguration createFromParcel(Parcel in) {
+ PasspointConfiguration config = new PasspointConfiguration();
+ config.setHomeSp(in.readParcelable(null));
+ config.setCredential(in.readParcelable(null));
+ config.setPolicy(in.readParcelable(null));
+ config.setSubscriptionUpdate(in.readParcelable(null));
+ config.setTrustRootCertList(readTrustRootCerts(in));
+ config.setUpdateIdentifier(in.readInt());
+ config.setCredentialPriority(in.readInt());
+ config.setSubscriptionCreationTimeInMillis(in.readLong());
+ config.setSubscriptionExpirationTimeInMillis(in.readLong());
+ config.setSubscriptionType(in.readString());
+ config.setUsageLimitUsageTimePeriodInMinutes(in.readLong());
+ config.setUsageLimitStartTimeInMillis(in.readLong());
+ config.setUsageLimitDataLimit(in.readLong());
+ config.setUsageLimitTimeLimitInMinutes(in.readLong());
+ Bundle bundle = in.readBundle();
+ Map<String, String> friendlyNamesMap = (HashMap) bundle.getSerializable(
+ "serviceFriendlyNames");
+ config.setServiceFriendlyNames(friendlyNamesMap);
+ return config;
+ }
+
+ @Override
+ public PasspointConfiguration[] newArray(int size) {
+ return new PasspointConfiguration[size];
+ }
+
+ /**
+ * Helper function for reading trust root certificate info list from a Parcel.
+ *
+ * @param in The Parcel to read from
+ * @return The list of trust root certificate URL with the corresponding certificate
+ * fingerprint
+ */
+ private Map<String, byte[]> readTrustRootCerts(Parcel in) {
+ int size = in.readInt();
+ if (size == NULL_VALUE) {
+ return null;
+ }
+ Map<String, byte[]> trustRootCerts = new HashMap<>(size);
+ for (int i = 0; i < size; i++) {
+ String key = in.readString();
+ byte[] value = in.createByteArray();
+ trustRootCerts.put(key, value);
+ }
+ return trustRootCerts;
+ }
+ };
+
+ /**
+ * Helper function for writing trust root certificate information list.
+ *
+ * @param dest The Parcel to write to
+ * @param trustRootCerts The list of trust root certificate URL with the corresponding
+ * certificate fingerprint
+ */
+ private static void writeTrustRootCerts(Parcel dest, Map<String, byte[]> trustRootCerts) {
+ if (trustRootCerts == null) {
+ dest.writeInt(NULL_VALUE);
+ return;
+ }
+ dest.writeInt(trustRootCerts.size());
+ for (Map.Entry<String, byte[]> entry : trustRootCerts.entrySet()) {
+ dest.writeString(entry.getKey());
+ dest.writeByteArray(entry.getValue());
+ }
+ }
+
+ /**
+ * Helper function for comparing two trust root certificate list. Cannot use Map#equals
+ * method since the value type (byte[]) doesn't override equals method.
+ *
+ * @param list1 The first trust root certificate list
+ * @param list2 The second trust root certificate list
+ * @return true if the two list are equal
+ */
+ private static boolean isTrustRootCertListEquals(Map<String, byte[]> list1,
+ Map<String, byte[]> list2) {
+ if (list1 == null || list2 == null) {
+ return list1 == list2;
+ }
+ if (list1.size() != list2.size()) {
+ return false;
+ }
+ for (Map.Entry<String, byte[]> entry : list1.entrySet()) {
+ if (!Arrays.equals(entry.getValue(), list2.get(entry.getKey()))) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/android/net/wifi/hotspot2/ProvisioningCallback.java b/android/net/wifi/hotspot2/ProvisioningCallback.java
new file mode 100644
index 0000000..1d499b6
--- /dev/null
+++ b/android/net/wifi/hotspot2/ProvisioningCallback.java
@@ -0,0 +1,235 @@
+/*
+ * 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 android.net.wifi.hotspot2;
+
+import android.annotation.SystemApi;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+
+/**
+ * Base class for provisioning callbacks. Should be extended by applications and set when calling
+ * {@link WifiManager#startSubscriptionProvisioning(OsuProvider, ProvisioningCallback, Handler)}.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class ProvisioningCallback {
+
+ /**
+ * The reason code for Provisioning Failure due to connection failure to OSU AP.
+ */
+ public static final int OSU_FAILURE_AP_CONNECTION = 1;
+
+ /**
+ * The reason code for invalid server URL address.
+ */
+ public static final int OSU_FAILURE_SERVER_URL_INVALID = 2;
+
+ /**
+ * The reason code for provisioning failure due to connection failure to the server.
+ */
+ public static final int OSU_FAILURE_SERVER_CONNECTION = 3;
+
+ /**
+ * The reason code for provisioning failure due to invalid server certificate.
+ */
+ public static final int OSU_FAILURE_SERVER_VALIDATION = 4;
+
+ /**
+ * The reason code for provisioning failure due to invalid service provider.
+ */
+ public static final int OSU_FAILURE_SERVICE_PROVIDER_VERIFICATION = 5;
+
+ /**
+ * The reason code for provisioning failure when a provisioning flow is aborted.
+ */
+ public static final int OSU_FAILURE_PROVISIONING_ABORTED = 6;
+
+ /**
+ * The reason code for provisioning failure when a provisioning flow is not possible.
+ */
+ public static final int OSU_FAILURE_PROVISIONING_NOT_AVAILABLE = 7;
+
+ /**
+ * The reason code for provisioning failure due to invalid web url format for an OSU web page.
+ */
+ public static final int OSU_FAILURE_INVALID_URL_FORMAT_FOR_OSU = 8;
+
+ /**
+ * The reason code for provisioning failure when a command received is not the expected command
+ * type.
+ */
+ public static final int OSU_FAILURE_UNEXPECTED_COMMAND_TYPE = 9;
+
+ /**
+ * The reason code for provisioning failure when a SOAP message is not the expected message
+ * type.
+ */
+ public static final int OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_TYPE = 10;
+
+ /**
+ * The reason code for provisioning failure when a SOAP message exchange fails.
+ */
+ public static final int OSU_FAILURE_SOAP_MESSAGE_EXCHANGE = 11;
+
+ /**
+ * The reason code for provisioning failure when a redirect listener fails to start.
+ */
+ public static final int OSU_FAILURE_START_REDIRECT_LISTENER = 12;
+
+ /**
+ * The reason code for provisioning failure when a redirect listener timed out to receive a HTTP
+ * redirect response.
+ */
+ public static final int OSU_FAILURE_TIMED_OUT_REDIRECT_LISTENER = 13;
+
+ /**
+ * The reason code for provisioning failure when there is no OSU activity to listen to
+ * {@link WifiManager#ACTION_PASSPOINT_LAUNCH_OSU_VIEW} intent.
+ */
+ public static final int OSU_FAILURE_NO_OSU_ACTIVITY_FOUND = 14;
+
+ /**
+ * The reason code for provisioning failure when the status of a SOAP message is not the
+ * expected message status.
+ */
+ public static final int OSU_FAILURE_UNEXPECTED_SOAP_MESSAGE_STATUS = 15;
+
+ /**
+ * The reason code for provisioning failure when there is no PPS MO.
+ * MO.
+ */
+ public static final int OSU_FAILURE_NO_PPS_MO = 16;
+
+ /**
+ * The reason code for provisioning failure when there is no AAAServerTrustRoot node in a PPS
+ * MO.
+ */
+ public static final int OSU_FAILURE_NO_AAA_SERVER_TRUST_ROOT_NODE = 17;
+
+ /**
+ * The reason code for provisioning failure when there is no TrustRoot node for remediation
+ * server in a PPS MO.
+ */
+ public static final int OSU_FAILURE_NO_REMEDIATION_SERVER_TRUST_ROOT_NODE = 18;
+
+ /**
+ * The reason code for provisioning failure when there is no TrustRoot node for policy server in
+ * a PPS MO.
+ */
+ public static final int OSU_FAILURE_NO_POLICY_SERVER_TRUST_ROOT_NODE = 19;
+
+ /**
+ * The reason code for provisioning failure when failing to retrieve trust root certificates
+ * used for validating server certificate for AAA, Remediation and Policy server.
+ */
+ public static final int OSU_FAILURE_RETRIEVE_TRUST_ROOT_CERTIFICATES = 20;
+
+ /**
+ * The reason code for provisioning failure when there is no trust root certificate for AAA
+ * server.
+ */
+ public static final int OSU_FAILURE_NO_AAA_TRUST_ROOT_CERTIFICATE = 21;
+
+ /**
+ * The reason code for provisioning failure when a {@link PasspointConfiguration} is failed to
+ * install.
+ */
+ public static final int OSU_FAILURE_ADD_PASSPOINT_CONFIGURATION = 22;
+
+ /**
+ * The reason code for provisioning failure when an {@link OsuProvider} is not found for
+ * provisioning.
+ */
+ public static final int OSU_FAILURE_OSU_PROVIDER_NOT_FOUND = 23;
+
+ /**
+ * The status code for provisioning flow to indicate connecting to OSU AP
+ */
+ public static final int OSU_STATUS_AP_CONNECTING = 1;
+
+ /**
+ * The status code for provisioning flow to indicate the OSU AP is connected.
+ */
+ public static final int OSU_STATUS_AP_CONNECTED = 2;
+
+ /**
+ * The status code for provisioning flow to indicate connecting to the server.
+ */
+ public static final int OSU_STATUS_SERVER_CONNECTING = 3;
+
+ /**
+ * The status code for provisioning flow to indicate the server certificate is validated.
+ */
+ public static final int OSU_STATUS_SERVER_VALIDATED = 4;
+
+ /**
+ * The status code for provisioning flow to indicate the server is connected
+ */
+ public static final int OSU_STATUS_SERVER_CONNECTED = 5;
+
+ /**
+ * The status code for provisioning flow to indicate starting the first SOAP exchange.
+ */
+ public static final int OSU_STATUS_INIT_SOAP_EXCHANGE = 6;
+
+ /**
+ * The status code for provisioning flow to indicate waiting for a HTTP redirect response.
+ */
+ public static final int OSU_STATUS_WAITING_FOR_REDIRECT_RESPONSE = 7;
+
+ /**
+ * The status code for provisioning flow to indicate a HTTP redirect response is received.
+ */
+ public static final int OSU_STATUS_REDIRECT_RESPONSE_RECEIVED = 8;
+
+ /**
+ * The status code for provisioning flow to indicate starting the second SOAP exchange.
+ */
+ public static final int OSU_STATUS_SECOND_SOAP_EXCHANGE = 9;
+
+ /**
+ * The status code for provisioning flow to indicate starting the third SOAP exchange.
+ */
+ public static final int OSU_STATUS_THIRD_SOAP_EXCHANGE = 10;
+
+ /**
+ * The status code for provisioning flow to indicate starting a step retrieving trust root
+ * certs.
+ */
+ public static final int OSU_STATUS_RETRIEVING_TRUST_ROOT_CERTS = 11;
+
+ /**
+ * Provisioning status for OSU failure
+ *
+ * @param status indicates error condition
+ */
+ public abstract void onProvisioningFailure(int status);
+
+ /**
+ * Provisioning status when OSU is in progress
+ *
+ * @param status indicates status of OSU flow
+ */
+ public abstract void onProvisioningStatus(int status);
+
+ /**
+ * Provisioning complete when provisioning/remediation flow completes
+ */
+ public abstract void onProvisioningComplete();
+}
+
diff --git a/android/net/wifi/hotspot2/omadm/PpsMoParser.java b/android/net/wifi/hotspot2/omadm/PpsMoParser.java
new file mode 100644
index 0000000..984cf7d
--- /dev/null
+++ b/android/net/wifi/hotspot2/omadm/PpsMoParser.java
@@ -0,0 +1,1658 @@
+/**
+ * Copyright (c) 2016, 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.hotspot2.omadm;
+
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.HomeSp;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.xml.sax.SAXException;
+
+/**
+ * Utility class for converting OMA-DM (Open Mobile Alliance's Device Management)
+ * PPS-MO (PerProviderSubscription Management Object) XML tree to a
+ * {@link PasspointConfiguration} object.
+ *
+ * Currently this only supports PerProviderSubscription/HomeSP and
+ * PerProviderSubscription/Credential subtree for Hotspot 2.0 Release 1 support.
+ *
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ *
+ * Below is a sample XML string for a Release 1 PPS MO tree:
+ *
+ * <MgmtTree xmlns="syncml:dmddf1.2">
+ * <VerDTD>1.2</VerDTD>
+ * <Node>
+ * <NodeName>PerProviderSubscription</NodeName>
+ * <RTProperties>
+ * <Type>
+ * <DDFName>urn:wfa:mo:hotspot2dot0perprovidersubscription:1.0</DDFName>
+ * </Type>
+ * </RTProperties>
+ * <Node>
+ * <NodeName>i001</NodeName>
+ * <Node>
+ * <NodeName>HomeSP</NodeName>
+ * <Node>
+ * <NodeName>FriendlyName</NodeName>
+ * <Value>Century House</Value>
+ * </Node>
+ * <Node>
+ * <NodeName>FQDN</NodeName>
+ * <Value>mi6.co.uk</Value>
+ * </Node>
+ * <Node>
+ * <NodeName>RoamingConsortiumOI</NodeName>
+ * <Value>112233,445566</Value>
+ * </Node>
+ * </Node>
+ * <Node>
+ * <NodeName>Credential</NodeName>
+ * <Node>
+ * <NodeName>Realm</NodeName>
+ * <Value>shaken.stirred.com</Value>
+ * </Node>
+ * <Node>
+ * <NodeName>UsernamePassword</NodeName>
+ * <Node>
+ * <NodeName>Username</NodeName>
+ * <Value>james</Value>
+ * </Node>
+ * <Node>
+ * <NodeName>Password</NodeName>
+ * <Value>Ym9uZDAwNw==</Value>
+ * </Node>
+ * <Node>
+ * <NodeName>EAPMethod</NodeName>
+ * <Node>
+ * <NodeName>EAPType</NodeName>
+ * <Value>21</Value>
+ * </Node>
+ * <Node>
+ * <NodeName>InnerMethod</NodeName>
+ * <Value>MS-CHAP-V2</Value>
+ * </Node>
+ * </Node>
+ * </Node>
+ * </Node>
+ * </Node>
+ * </Node>
+ * </MgmtTree>
+ */
+public final class PpsMoParser {
+ private static final String TAG = "PpsMoParser";
+
+ /**
+ * XML tags expected in the PPS MO (PerProviderSubscription Management Object) XML tree.
+ */
+ private static final String TAG_MANAGEMENT_TREE = "MgmtTree";
+ private static final String TAG_VER_DTD = "VerDTD";
+ private static final String TAG_NODE = "Node";
+ private static final String TAG_NODE_NAME = "NodeName";
+ private static final String TAG_RT_PROPERTIES = "RTProperties";
+ private static final String TAG_TYPE = "Type";
+ private static final String TAG_DDF_NAME = "DDFName";
+ private static final String TAG_VALUE = "Value";
+
+ /**
+ * Name for PerProviderSubscription node.
+ */
+ private static final String NODE_PER_PROVIDER_SUBSCRIPTION = "PerProviderSubscription";
+
+ /**
+ * Fields under PerProviderSubscription.
+ */
+ private static final String NODE_UPDATE_IDENTIFIER = "UpdateIdentifier";
+ private static final String NODE_AAA_SERVER_TRUST_ROOT = "AAAServerTrustRoot";
+ private static final String NODE_SUBSCRIPTION_UPDATE = "SubscriptionUpdate";
+ private static final String NODE_SUBSCRIPTION_PARAMETER = "SubscriptionParameters";
+ private static final String NODE_TYPE_OF_SUBSCRIPTION = "TypeOfSubscription";
+ private static final String NODE_USAGE_LIMITS = "UsageLimits";
+ private static final String NODE_DATA_LIMIT = "DataLimit";
+ private static final String NODE_START_DATE = "StartDate";
+ private static final String NODE_TIME_LIMIT = "TimeLimit";
+ private static final String NODE_USAGE_TIME_PERIOD = "UsageTimePeriod";
+ private static final String NODE_CREDENTIAL_PRIORITY = "CredentialPriority";
+ private static final String NODE_EXTENSION = "Extension";
+
+ /**
+ * Fields under HomeSP subtree.
+ */
+ private static final String NODE_HOMESP = "HomeSP";
+ private static final String NODE_FQDN = "FQDN";
+ private static final String NODE_FRIENDLY_NAME = "FriendlyName";
+ private static final String NODE_ROAMING_CONSORTIUM_OI = "RoamingConsortiumOI";
+ private static final String NODE_NETWORK_ID = "NetworkID";
+ private static final String NODE_SSID = "SSID";
+ private static final String NODE_HESSID = "HESSID";
+ private static final String NODE_ICON_URL = "IconURL";
+ private static final String NODE_HOME_OI_LIST = "HomeOIList";
+ private static final String NODE_HOME_OI = "HomeOI";
+ private static final String NODE_HOME_OI_REQUIRED = "HomeOIRequired";
+ private static final String NODE_OTHER_HOME_PARTNERS = "OtherHomePartners";
+
+ /**
+ * Fields under Credential subtree.
+ */
+ private static final String NODE_CREDENTIAL = "Credential";
+ private static final String NODE_CREATION_DATE = "CreationDate";
+ private static final String NODE_EXPIRATION_DATE = "ExpirationDate";
+ private static final String NODE_USERNAME_PASSWORD = "UsernamePassword";
+ private static final String NODE_USERNAME = "Username";
+ private static final String NODE_PASSWORD = "Password";
+ private static final String NODE_MACHINE_MANAGED = "MachineManaged";
+ private static final String NODE_SOFT_TOKEN_APP = "SoftTokenApp";
+ private static final String NODE_ABLE_TO_SHARE = "AbleToShare";
+ private static final String NODE_EAP_METHOD = "EAPMethod";
+ private static final String NODE_EAP_TYPE = "EAPType";
+ private static final String NODE_VENDOR_ID = "VendorId";
+ private static final String NODE_VENDOR_TYPE = "VendorType";
+ private static final String NODE_INNER_EAP_TYPE = "InnerEAPType";
+ private static final String NODE_INNER_VENDOR_ID = "InnerVendorID";
+ private static final String NODE_INNER_VENDOR_TYPE = "InnerVendorType";
+ private static final String NODE_INNER_METHOD = "InnerMethod";
+ private static final String NODE_DIGITAL_CERTIFICATE = "DigitalCertificate";
+ private static final String NODE_CERTIFICATE_TYPE = "CertificateType";
+ private static final String NODE_CERT_SHA256_FINGERPRINT = "CertSHA256Fingerprint";
+ private static final String NODE_REALM = "Realm";
+ private static final String NODE_SIM = "SIM";
+ private static final String NODE_SIM_IMSI = "IMSI";
+ private static final String NODE_CHECK_AAA_SERVER_CERT_STATUS = "CheckAAAServerCertStatus";
+
+ /**
+ * Fields under Policy subtree.
+ */
+ private static final String NODE_POLICY = "Policy";
+ private static final String NODE_PREFERRED_ROAMING_PARTNER_LIST =
+ "PreferredRoamingPartnerList";
+ private static final String NODE_FQDN_MATCH = "FQDN_Match";
+ private static final String NODE_PRIORITY = "Priority";
+ private static final String NODE_COUNTRY = "Country";
+ private static final String NODE_MIN_BACKHAUL_THRESHOLD = "MinBackhaulThreshold";
+ private static final String NODE_NETWORK_TYPE = "NetworkType";
+ private static final String NODE_DOWNLINK_BANDWIDTH = "DLBandwidth";
+ private static final String NODE_UPLINK_BANDWIDTH = "ULBandwidth";
+ private static final String NODE_POLICY_UPDATE = "PolicyUpdate";
+ private static final String NODE_UPDATE_INTERVAL = "UpdateInterval";
+ private static final String NODE_UPDATE_METHOD = "UpdateMethod";
+ private static final String NODE_RESTRICTION = "Restriction";
+ private static final String NODE_URI = "URI";
+ private static final String NODE_TRUST_ROOT = "TrustRoot";
+ private static final String NODE_CERT_URL = "CertURL";
+ private static final String NODE_SP_EXCLUSION_LIST = "SPExclusionList";
+ private static final String NODE_REQUIRED_PROTO_PORT_TUPLE = "RequiredProtoPortTuple";
+ private static final String NODE_IP_PROTOCOL = "IPProtocol";
+ private static final String NODE_PORT_NUMBER = "PortNumber";
+ private static final String NODE_MAXIMUM_BSS_LOAD_VALUE = "MaximumBSSLoadValue";
+ private static final String NODE_OTHER = "Other";
+
+ /**
+ * URN (Unique Resource Name) for PerProviderSubscription Management Object Tree.
+ */
+ private static final String PPS_MO_URN =
+ "urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0";
+
+ /**
+ * Exception for generic parsing errors.
+ */
+ private static class ParsingException extends Exception {
+ public ParsingException(String message) {
+ super(message);
+ }
+ }
+
+ /**
+ * Class representing a node within the PerProviderSubscription tree.
+ * This is used to flatten out and eliminate the extra layering in the XMLNode tree,
+ * to make the data parsing easier and cleaner.
+ *
+ * A PPSNode can be an internal or a leaf node, but not both.
+ *
+ */
+ private static abstract class PPSNode {
+ private final String mName;
+ public PPSNode(String name) {
+ mName = name;
+ }
+
+ /**
+ * @return the name of the node
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Applies for internal node only.
+ *
+ * @return the list of children nodes.
+ */
+ public abstract List<PPSNode> getChildren();
+
+ /**
+ * Applies for leaf node only.
+ *
+ * @return the string value of the node
+ */
+ public abstract String getValue();
+
+ /**
+ * @return a flag indicating if this is a leaf or an internal node
+ */
+ public abstract boolean isLeaf();
+ }
+
+ /**
+ * Class representing a leaf node in a PPS (PerProviderSubscription) tree.
+ */
+ private static class LeafNode extends PPSNode {
+ private final String mValue;
+ public LeafNode(String nodeName, String value) {
+ super(nodeName);
+ mValue = value;
+ }
+
+ @Override
+ public String getValue() {
+ return mValue;
+ }
+
+ @Override
+ public List<PPSNode> getChildren() {
+ return null;
+ }
+
+ @Override
+ public boolean isLeaf() {
+ return true;
+ }
+ }
+
+ /**
+ * Class representing an internal node in a PPS (PerProviderSubscription) tree.
+ */
+ private static class InternalNode extends PPSNode {
+ private final List<PPSNode> mChildren;
+ public InternalNode(String nodeName, List<PPSNode> children) {
+ super(nodeName);
+ mChildren = children;
+ }
+
+ @Override
+ public String getValue() {
+ return null;
+ }
+
+ @Override
+ public List<PPSNode> getChildren() {
+ return mChildren;
+ }
+
+ @Override
+ public boolean isLeaf() {
+ return false;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public PpsMoParser() {}
+
+ /**
+ * Convert a XML string representation of a PPS MO (PerProviderSubscription
+ * Management Object) tree to a {@link PasspointConfiguration} object.
+ *
+ * @param xmlString XML string representation of a PPS MO tree
+ * @return {@link PasspointConfiguration} or null
+ */
+ public static PasspointConfiguration parseMoText(String xmlString) {
+ // Convert the XML string to a XML tree.
+ XMLParser xmlParser = new XMLParser();
+ XMLNode root = null;
+ try {
+ root = xmlParser.parse(xmlString);
+ } catch(IOException | SAXException e) {
+ return null;
+ }
+ if (root == null) {
+ return null;
+ }
+
+ // Verify root node is a "MgmtTree" node.
+ if (root.getTag() != TAG_MANAGEMENT_TREE) {
+ Log.e(TAG, "Root is not a MgmtTree");
+ return null;
+ }
+
+ String verDtd = null; // Used for detecting duplicate VerDTD element.
+ PasspointConfiguration config = null;
+ for (XMLNode child : root.getChildren()) {
+ switch(child.getTag()) {
+ case TAG_VER_DTD:
+ if (verDtd != null) {
+ Log.e(TAG, "Duplicate VerDTD element");
+ return null;
+ }
+ verDtd = child.getText();
+ break;
+ case TAG_NODE:
+ if (config != null) {
+ Log.e(TAG, "Unexpected multiple Node element under MgmtTree");
+ return null;
+ }
+ try {
+ config = parsePpsNode(child);
+ } catch (ParsingException e) {
+ Log.e(TAG, e.getMessage());
+ return null;
+ }
+ break;
+ default:
+ Log.e(TAG, "Unknown node: " + child.getTag());
+ return null;
+ }
+ }
+ return config;
+ }
+
+ /**
+ * Parse a PerProviderSubscription node. Below is the format of the XML tree (with
+ * each XML element represent a node in the tree):
+ *
+ * <Node>
+ * <NodeName>PerProviderSubscription</NodeName>
+ * <RTProperties>
+ * ...
+ * </RTPProperties>
+ * <Node>
+ * <NodeName>UpdateIdentifier</NodeName>
+ * <Value>...</Value>
+ * </Node>
+ * <Node>
+ * ...
+ * </Node>
+ * </Node>
+ *
+ * @param node XMLNode that contains PerProviderSubscription node.
+ * @return PasspointConfiguration or null
+ * @throws ParsingException
+ */
+ private static PasspointConfiguration parsePpsNode(XMLNode node)
+ throws ParsingException {
+ PasspointConfiguration config = null;
+ String nodeName = null;
+ int updateIdentifier = Integer.MIN_VALUE;
+ for (XMLNode child : node.getChildren()) {
+ switch (child.getTag()) {
+ case TAG_NODE_NAME:
+ if (nodeName != null) {
+ throw new ParsingException("Duplicate NodeName: " + child.getText());
+ }
+ nodeName = child.getText();
+ if (!TextUtils.equals(nodeName, NODE_PER_PROVIDER_SUBSCRIPTION)) {
+ throw new ParsingException("Unexpected NodeName: " + nodeName);
+ }
+ break;
+ case TAG_NODE:
+ // A node can be either an UpdateIdentifier node or a PerProviderSubscription
+ // instance node. Flatten out the XML tree first by converting it to a PPS
+ // tree to reduce the complexity of the parsing code.
+ PPSNode ppsNodeRoot = buildPpsNode(child);
+ if (TextUtils.equals(ppsNodeRoot.getName(), NODE_UPDATE_IDENTIFIER)) {
+ if (updateIdentifier != Integer.MIN_VALUE) {
+ throw new ParsingException("Multiple node for UpdateIdentifier");
+ }
+ updateIdentifier = parseInteger(getPpsNodeValue(ppsNodeRoot));
+ } else {
+ // Only one PerProviderSubscription instance is expected and allowed.
+ if (config != null) {
+ throw new ParsingException("Multiple PPS instance");
+ }
+ config = parsePpsInstance(ppsNodeRoot);
+ }
+ break;
+ case TAG_RT_PROPERTIES:
+ // Parse and verify URN stored in the RT (Run Time) Properties.
+ String urn = parseUrn(child);
+ if (!TextUtils.equals(urn, PPS_MO_URN)) {
+ throw new ParsingException("Unknown URN: " + urn);
+ }
+ break;
+ default:
+ throw new ParsingException("Unknown tag under PPS node: " + child.getTag());
+ }
+ }
+ if (config != null && updateIdentifier != Integer.MIN_VALUE) {
+ config.setUpdateIdentifier(updateIdentifier);
+ }
+ return config;
+ }
+
+ /**
+ * Parse the URN stored in the RTProperties. Below is the format of the RTPProperties node:
+ *
+ * <RTProperties>
+ * <Type>
+ * <DDFName>urn:...</DDFName>
+ * </Type>
+ * </RTProperties>
+ *
+ * @param node XMLNode that contains RTProperties node.
+ * @return URN String of URN.
+ * @throws ParsingException
+ */
+ private static String parseUrn(XMLNode node) throws ParsingException {
+ if (node.getChildren().size() != 1)
+ throw new ParsingException("Expect RTPProperties node to only have one child");
+
+ XMLNode typeNode = node.getChildren().get(0);
+ if (typeNode.getChildren().size() != 1) {
+ throw new ParsingException("Expect Type node to only have one child");
+ }
+ if (!TextUtils.equals(typeNode.getTag(), TAG_TYPE)) {
+ throw new ParsingException("Unexpected tag for Type: " + typeNode.getTag());
+ }
+
+ XMLNode ddfNameNode = typeNode.getChildren().get(0);
+ if (!ddfNameNode.getChildren().isEmpty()) {
+ throw new ParsingException("Expect DDFName node to have no child");
+ }
+ if (!TextUtils.equals(ddfNameNode.getTag(), TAG_DDF_NAME)) {
+ throw new ParsingException("Unexpected tag for DDFName: " + ddfNameNode.getTag());
+ }
+
+ return ddfNameNode.getText();
+ }
+
+ /**
+ * Convert a XML tree represented by XMLNode to a PPS (PerProviderSubscription) instance tree
+ * represented by PPSNode. This flattens out the XML tree to allow easier and cleaner parsing
+ * of the PPS configuration data. Only three types of XML tag are expected: "NodeName",
+ * "Node", and "Value".
+ *
+ * The original XML tree (each XML element represent a node):
+ *
+ * <Node>
+ * <NodeName>root</NodeName>
+ * <Node>
+ * <NodeName>child1</NodeName>
+ * <Value>value1</Value>
+ * </Node>
+ * <Node>
+ * <NodeName>child2</NodeName>
+ * <Node>
+ * <NodeName>grandchild1</NodeName>
+ * ...
+ * </Node>
+ * </Node>
+ * ...
+ * </Node>
+ *
+ * The converted PPS tree:
+ *
+ * [root] --- [child1, value1]
+ * |
+ * ---------[child2] --------[grandchild1] --- ...
+ *
+ * @param node XMLNode pointed to the root of a XML tree
+ * @return PPSNode pointing to the root of a PPS tree
+ * @throws ParsingException
+ */
+ private static PPSNode buildPpsNode(XMLNode node) throws ParsingException {
+ String nodeName = null;
+ String nodeValue = null;
+ List<PPSNode> childNodes = new ArrayList<PPSNode>();
+ // Names of parsed child nodes, use for detecting multiple child nodes with the same name.
+ Set<String> parsedNodes = new HashSet<String>();
+
+ for (XMLNode child : node.getChildren()) {
+ String tag = child.getTag();
+ if (TextUtils.equals(tag, TAG_NODE_NAME)) {
+ if (nodeName != null) {
+ throw new ParsingException("Duplicate NodeName node");
+ }
+ nodeName = child.getText();
+ } else if (TextUtils.equals(tag, TAG_NODE)) {
+ PPSNode ppsNode = buildPpsNode(child);
+ if (parsedNodes.contains(ppsNode.getName())) {
+ throw new ParsingException("Duplicate node: " + ppsNode.getName());
+ }
+ parsedNodes.add(ppsNode.getName());
+ childNodes.add(ppsNode);
+ } else if (TextUtils.equals(tag, TAG_VALUE)) {
+ if (nodeValue != null) {
+ throw new ParsingException("Duplicate Value node");
+ }
+ nodeValue = child.getText();
+ } else {
+ throw new ParsingException("Unknown tag: " + tag);
+ }
+ }
+
+ if (nodeName == null) {
+ throw new ParsingException("Invalid node: missing NodeName");
+ }
+ if (nodeValue == null && childNodes.size() == 0) {
+ throw new ParsingException("Invalid node: " + nodeName +
+ " missing both value and children");
+ }
+ if (nodeValue != null && childNodes.size() > 0) {
+ throw new ParsingException("Invalid node: " + nodeName +
+ " contained both value and children");
+ }
+
+ if (nodeValue != null) {
+ return new LeafNode(nodeName, nodeValue);
+ }
+ return new InternalNode(nodeName, childNodes);
+ }
+
+ /**
+ * Return the value of a PPSNode. An exception will be thrown if the given node
+ * is not a leaf node.
+ *
+ * @param node PPSNode to retrieve the value from
+ * @return String representing the value of the node
+ * @throws ParsingException
+ */
+ private static String getPpsNodeValue(PPSNode node) throws ParsingException {
+ if (!node.isLeaf()) {
+ throw new ParsingException("Cannot get value from a non-leaf node: " + node.getName());
+ }
+ return node.getValue();
+ }
+
+ /**
+ * Parse a PPS (PerProviderSubscription) configurations from a PPS tree.
+ *
+ * @param root PPSNode representing the root of the PPS tree
+ * @return PasspointConfiguration
+ * @throws ParsingException
+ */
+ private static PasspointConfiguration parsePpsInstance(PPSNode root)
+ throws ParsingException {
+ if (root.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for PPS instance");
+ }
+
+ PasspointConfiguration config = new PasspointConfiguration();
+ for (PPSNode child : root.getChildren()) {
+ switch(child.getName()) {
+ case NODE_HOMESP:
+ config.setHomeSp(parseHomeSP(child));
+ break;
+ case NODE_CREDENTIAL:
+ config.setCredential(parseCredential(child));
+ break;
+ case NODE_POLICY:
+ config.setPolicy(parsePolicy(child));
+ break;
+ case NODE_AAA_SERVER_TRUST_ROOT:
+ config.setTrustRootCertList(parseAAAServerTrustRootList(child));
+ break;
+ case NODE_SUBSCRIPTION_UPDATE:
+ config.setSubscriptionUpdate(parseUpdateParameter(child));
+ break;
+ case NODE_SUBSCRIPTION_PARAMETER:
+ parseSubscriptionParameter(child, config);
+ break;
+ case NODE_CREDENTIAL_PRIORITY:
+ config.setCredentialPriority(parseInteger(getPpsNodeValue(child)));
+ break;
+ case NODE_EXTENSION:
+ // All vendor specific information will be under this node.
+ Log.d(TAG, "Ignore Extension node for vendor specific information");
+ break;
+ default:
+ throw new ParsingException("Unknown node: " + child.getName());
+ }
+ }
+ return config;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/HomeSP subtree.
+ *
+ * @param node PPSNode representing the root of the PerProviderSubscription/HomeSP subtree
+ * @return HomeSP
+ * @throws ParsingException
+ */
+ private static HomeSp parseHomeSP(PPSNode node) throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for HomeSP");
+ }
+
+ HomeSp homeSp = new HomeSp();
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_FQDN:
+ homeSp.setFqdn(getPpsNodeValue(child));
+ break;
+ case NODE_FRIENDLY_NAME:
+ homeSp.setFriendlyName(getPpsNodeValue(child));
+ break;
+ case NODE_ROAMING_CONSORTIUM_OI:
+ homeSp.setRoamingConsortiumOis(
+ parseRoamingConsortiumOI(getPpsNodeValue(child)));
+ break;
+ case NODE_ICON_URL:
+ homeSp.setIconUrl(getPpsNodeValue(child));
+ break;
+ case NODE_NETWORK_ID:
+ homeSp.setHomeNetworkIds(parseNetworkIds(child));
+ break;
+ case NODE_HOME_OI_LIST:
+ Pair<List<Long>, List<Long>> homeOIs = parseHomeOIList(child);
+ homeSp.setMatchAllOis(convertFromLongList(homeOIs.first));
+ homeSp.setMatchAnyOis(convertFromLongList(homeOIs.second));
+ break;
+ case NODE_OTHER_HOME_PARTNERS:
+ homeSp.setOtherHomePartners(parseOtherHomePartners(child));
+ break;
+ default:
+ throw new ParsingException("Unknown node under HomeSP: " + child.getName());
+ }
+ }
+ return homeSp;
+ }
+
+ /**
+ * Parse the roaming consortium OI string, which contains a list of OIs separated by ",".
+ *
+ * @param oiStr string containing list of OIs (Organization Identifiers) separated by ","
+ * @return long[]
+ * @throws ParsingException
+ */
+ private static long[] parseRoamingConsortiumOI(String oiStr)
+ throws ParsingException {
+ String[] oiStrArray = oiStr.split(",");
+ long[] oiArray = new long[oiStrArray.length];
+ for (int i = 0; i < oiStrArray.length; i++) {
+ oiArray[i] = parseLong(oiStrArray[i], 16);
+ }
+ return oiArray;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/HomeSP/NetworkID subtree.
+ *
+ * @param node PPSNode representing the root of the PerProviderSubscription/HomeSP/NetworkID
+ * subtree
+ * @return HashMap<String, Long> representing list of <SSID, HESSID> pair.
+ * @throws ParsingException
+ */
+ static private Map<String, Long> parseNetworkIds(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for NetworkID");
+ }
+
+ Map<String, Long> networkIds = new HashMap<>();
+ for (PPSNode child : node.getChildren()) {
+ Pair<String, Long> networkId = parseNetworkIdInstance(child);
+ networkIds.put(networkId.first, networkId.second);
+ }
+ return networkIds;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/HomeSP/NetworkID/<X+> subtree.
+ * The instance name (<X+>) is irrelevant and must be unique for each instance, which
+ * is verified when the PPS tree is constructed {@link #buildPpsNode}.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/HomeSP/NetworkID/<X+> subtree
+ * @return Pair<String, Long> representing <SSID, HESSID> pair.
+ * @throws ParsingException
+ */
+ static private Pair<String, Long> parseNetworkIdInstance(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for NetworkID instance");
+ }
+
+ String ssid = null;
+ Long hessid = null;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_SSID:
+ ssid = getPpsNodeValue(child);
+ break;
+ case NODE_HESSID:
+ hessid = parseLong(getPpsNodeValue(child), 16);
+ break;
+ default:
+ throw new ParsingException("Unknown node under NetworkID instance: " +
+ child.getName());
+ }
+ }
+ if (ssid == null)
+ throw new ParsingException("NetworkID instance missing SSID");
+
+ return new Pair<String, Long>(ssid, hessid);
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/HomeSP/HomeOIList subtree.
+ *
+ * @param node PPSNode representing the root of the PerProviderSubscription/HomeSP/HomeOIList
+ * subtree
+ * @return Pair<List<Long>, List<Long>> containing both MatchAllOIs and MatchAnyOIs list.
+ * @throws ParsingException
+ */
+ private static Pair<List<Long>, List<Long>> parseHomeOIList(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for HomeOIList");
+ }
+
+ List<Long> matchAllOIs = new ArrayList<Long>();
+ List<Long> matchAnyOIs = new ArrayList<Long>();
+ for (PPSNode child : node.getChildren()) {
+ Pair<Long, Boolean> homeOI = parseHomeOIInstance(child);
+ if (homeOI.second.booleanValue()) {
+ matchAllOIs.add(homeOI.first);
+ } else {
+ matchAnyOIs.add(homeOI.first);
+ }
+ }
+ return new Pair<List<Long>, List<Long>>(matchAllOIs, matchAnyOIs);
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/HomeSP/HomeOIList/<X+> subtree.
+ * The instance name (<X+>) is irrelevant and must be unique for each instance, which
+ * is verified when the PPS tree is constructed {@link #buildPpsNode}.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/HomeSP/HomeOIList/<X+> subtree
+ * @return Pair<Long, Boolean> containing a HomeOI and a HomeOIRequired flag
+ * @throws ParsingException
+ */
+ private static Pair<Long, Boolean> parseHomeOIInstance(PPSNode node) throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for HomeOI instance");
+ }
+
+ Long oi = null;
+ Boolean required = null;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_HOME_OI:
+ try {
+ oi = Long.valueOf(getPpsNodeValue(child), 16);
+ } catch (NumberFormatException e) {
+ throw new ParsingException("Invalid HomeOI: " + getPpsNodeValue(child));
+ }
+ break;
+ case NODE_HOME_OI_REQUIRED:
+ required = Boolean.valueOf(getPpsNodeValue(child));
+ break;
+ default:
+ throw new ParsingException("Unknown node under NetworkID instance: " +
+ child.getName());
+ }
+ }
+ if (oi == null) {
+ throw new ParsingException("HomeOI instance missing OI field");
+ }
+ if (required == null) {
+ throw new ParsingException("HomeOI instance missing required field");
+ }
+ return new Pair<Long, Boolean>(oi, required);
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/HomeSP/OtherHomePartners subtree.
+ * This contains a list of FQDN (Fully Qualified Domain Name) that are considered
+ * home partners.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/HomeSP/OtherHomePartners subtree
+ * @return String[] list of partner's FQDN
+ * @throws ParsingException
+ */
+ private static String[] parseOtherHomePartners(PPSNode node) throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for OtherHomePartners");
+ }
+ List<String> otherHomePartners = new ArrayList<String>();
+ for (PPSNode child : node.getChildren()) {
+ String fqdn = parseOtherHomePartnerInstance(child);
+ otherHomePartners.add(fqdn);
+ }
+ return otherHomePartners.toArray(new String[otherHomePartners.size()]);
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/HomeSP/OtherHomePartners/<X+> subtree.
+ * The instance name (<X+>) is irrelevant and must be unique for each instance, which
+ * is verified when the PPS tree is constructed {@link #buildPpsNode}.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/HomeSP/OtherHomePartners/<X+> subtree
+ * @return String FQDN of the partner
+ * @throws ParsingException
+ */
+ private static String parseOtherHomePartnerInstance(PPSNode node) throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for OtherHomePartner instance");
+ }
+ String fqdn = null;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_FQDN:
+ fqdn = getPpsNodeValue(child);
+ break;
+ default:
+ throw new ParsingException(
+ "Unknown node under OtherHomePartner instance: " + child.getName());
+ }
+ }
+ if (fqdn == null) {
+ throw new ParsingException("OtherHomePartner instance missing FQDN field");
+ }
+ return fqdn;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Credential subtree.
+ *
+ * @param node PPSNode representing the root of the PerProviderSubscription/Credential subtree
+ * @return Credential
+ * @throws ParsingException
+ */
+ private static Credential parseCredential(PPSNode node) throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for HomeSP");
+ }
+
+ Credential credential = new Credential();
+ for (PPSNode child: node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_CREATION_DATE:
+ credential.setCreationTimeInMillis(parseDate(getPpsNodeValue(child)));
+ break;
+ case NODE_EXPIRATION_DATE:
+ credential.setExpirationTimeInMillis(parseDate(getPpsNodeValue(child)));
+ break;
+ case NODE_USERNAME_PASSWORD:
+ credential.setUserCredential(parseUserCredential(child));
+ break;
+ case NODE_DIGITAL_CERTIFICATE:
+ credential.setCertCredential(parseCertificateCredential(child));
+ break;
+ case NODE_REALM:
+ credential.setRealm(getPpsNodeValue(child));
+ break;
+ case NODE_CHECK_AAA_SERVER_CERT_STATUS:
+ credential.setCheckAaaServerCertStatus(
+ Boolean.parseBoolean(getPpsNodeValue(child)));
+ break;
+ case NODE_SIM:
+ credential.setSimCredential(parseSimCredential(child));
+ break;
+ default:
+ throw new ParsingException("Unknown node under Credential: " +
+ child.getName());
+ }
+ }
+ return credential;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Credential/UsernamePassword subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Credential/UsernamePassword subtree
+ * @return Credential.UserCredential
+ * @throws ParsingException
+ */
+ private static Credential.UserCredential parseUserCredential(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for UsernamePassword");
+ }
+
+ Credential.UserCredential userCred = new Credential.UserCredential();
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_USERNAME:
+ userCred.setUsername(getPpsNodeValue(child));
+ break;
+ case NODE_PASSWORD:
+ userCred.setPassword(getPpsNodeValue(child));
+ break;
+ case NODE_MACHINE_MANAGED:
+ userCred.setMachineManaged(Boolean.parseBoolean(getPpsNodeValue(child)));
+ break;
+ case NODE_SOFT_TOKEN_APP:
+ userCred.setSoftTokenApp(getPpsNodeValue(child));
+ break;
+ case NODE_ABLE_TO_SHARE:
+ userCred.setAbleToShare(Boolean.parseBoolean(getPpsNodeValue(child)));
+ break;
+ case NODE_EAP_METHOD:
+ parseEAPMethod(child, userCred);
+ break;
+ default:
+ throw new ParsingException("Unknown node under UsernamPassword: " +
+ child.getName());
+ }
+ }
+ return userCred;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Credential/UsernamePassword/EAPMethod
+ * subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Credential/UsernamePassword/EAPMethod subtree
+ * @param userCred UserCredential to be updated with EAP method values.
+ * @throws ParsingException
+ */
+ private static void parseEAPMethod(PPSNode node, Credential.UserCredential userCred)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for EAPMethod");
+ }
+
+ for (PPSNode child : node.getChildren()) {
+ switch(child.getName()) {
+ case NODE_EAP_TYPE:
+ userCred.setEapType(parseInteger(getPpsNodeValue(child)));
+ break;
+ case NODE_INNER_METHOD:
+ userCred.setNonEapInnerMethod(getPpsNodeValue(child));
+ break;
+ case NODE_VENDOR_ID:
+ case NODE_VENDOR_TYPE:
+ case NODE_INNER_EAP_TYPE:
+ case NODE_INNER_VENDOR_ID:
+ case NODE_INNER_VENDOR_TYPE:
+ // Only EAP-TTLS is currently supported for user credential, which doesn't
+ // use any of these parameters.
+ Log.d(TAG, "Ignore unsupported EAP method parameter: " + child.getName());
+ break;
+ default:
+ throw new ParsingException("Unknown node under EAPMethod: " + child.getName());
+ }
+ }
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Credential/DigitalCertificate subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Credential/DigitalCertificate subtree
+ * @return Credential.CertificateCredential
+ * @throws ParsingException
+ */
+ private static Credential.CertificateCredential parseCertificateCredential(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for DigitalCertificate");
+ }
+
+ Credential.CertificateCredential certCred = new Credential.CertificateCredential();
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_CERTIFICATE_TYPE:
+ certCred.setCertType(getPpsNodeValue(child));
+ break;
+ case NODE_CERT_SHA256_FINGERPRINT:
+ certCred.setCertSha256Fingerprint(parseHexString(getPpsNodeValue(child)));
+ break;
+ default:
+ throw new ParsingException("Unknown node under DigitalCertificate: " +
+ child.getName());
+ }
+ }
+ return certCred;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Credential/SIM subtree.
+ *
+ * @param node PPSNode representing the root of the PerProviderSubscription/Credential/SIM
+ * subtree
+ * @return Credential.SimCredential
+ * @throws ParsingException
+ */
+ private static Credential.SimCredential parseSimCredential(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for SIM");
+ }
+
+ Credential.SimCredential simCred = new Credential.SimCredential();
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_SIM_IMSI:
+ simCred.setImsi(getPpsNodeValue(child));
+ break;
+ case NODE_EAP_TYPE:
+ simCred.setEapType(parseInteger(getPpsNodeValue(child)));
+ break;
+ default:
+ throw new ParsingException("Unknown node under SIM: " + child.getName());
+ }
+ }
+ return simCred;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy subtree.
+ *
+ * @param node PPSNode representing the root of the PerProviderSubscription/Policy subtree
+ * @return {@link Policy}
+ * @throws ParsingException
+ */
+ private static Policy parsePolicy(PPSNode node) throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for Policy");
+ }
+
+ Policy policy = new Policy();
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_PREFERRED_ROAMING_PARTNER_LIST:
+ policy.setPreferredRoamingPartnerList(parsePreferredRoamingPartnerList(child));
+ break;
+ case NODE_MIN_BACKHAUL_THRESHOLD:
+ parseMinBackhaulThreshold(child, policy);
+ break;
+ case NODE_POLICY_UPDATE:
+ policy.setPolicyUpdate(parseUpdateParameter(child));
+ break;
+ case NODE_SP_EXCLUSION_LIST:
+ policy.setExcludedSsidList(parseSpExclusionList(child));
+ break;
+ case NODE_REQUIRED_PROTO_PORT_TUPLE:
+ policy.setRequiredProtoPortMap(parseRequiredProtoPortTuple(child));
+ break;
+ case NODE_MAXIMUM_BSS_LOAD_VALUE:
+ policy.setMaximumBssLoadValue(parseInteger(getPpsNodeValue(child)));
+ break;
+ default:
+ throw new ParsingException("Unknown node under Policy: " + child.getName());
+ }
+ }
+ return policy;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/PreferredRoamingPartnerList
+ * subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/PreferredRoamingPartnerList subtree
+ * @return List of {@link Policy#RoamingPartner}
+ * @throws ParsingException
+ */
+ private static List<Policy.RoamingPartner> parsePreferredRoamingPartnerList(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for PreferredRoamingPartnerList");
+ }
+ List<Policy.RoamingPartner> partnerList = new ArrayList<>();
+ for (PPSNode child : node.getChildren()) {
+ partnerList.add(parsePreferredRoamingPartner(child));
+ }
+ return partnerList;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/PreferredRoamingPartnerList/<X+>
+ * subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/PreferredRoamingPartnerList/<X+> subtree
+ * @return {@link Policy#RoamingPartner}
+ * @throws ParsingException
+ */
+ private static Policy.RoamingPartner parsePreferredRoamingPartner(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for PreferredRoamingPartner "
+ + "instance");
+ }
+
+ Policy.RoamingPartner roamingPartner = new Policy.RoamingPartner();
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_FQDN_MATCH:
+ // FQDN_Match field is in the format of "[FQDN],[MatchInfo]", where [MatchInfo]
+ // is either "exactMatch" for exact match of FQDN or "includeSubdomains" for
+ // matching all FQDNs with the same sub-domain.
+ String fqdnMatch = getPpsNodeValue(child);
+ String[] fqdnMatchArray = fqdnMatch.split(",");
+ if (fqdnMatchArray.length != 2) {
+ throw new ParsingException("Invalid FQDN_Match: " + fqdnMatch);
+ }
+ roamingPartner.setFqdn(fqdnMatchArray[0]);
+ if (TextUtils.equals(fqdnMatchArray[1], "exactMatch")) {
+ roamingPartner.setFqdnExactMatch(true);
+ } else if (TextUtils.equals(fqdnMatchArray[1], "includeSubdomains")) {
+ roamingPartner.setFqdnExactMatch(false);
+ } else {
+ throw new ParsingException("Invalid FQDN_Match: " + fqdnMatch);
+ }
+ break;
+ case NODE_PRIORITY:
+ roamingPartner.setPriority(parseInteger(getPpsNodeValue(child)));
+ break;
+ case NODE_COUNTRY:
+ roamingPartner.setCountries(getPpsNodeValue(child));
+ break;
+ default:
+ throw new ParsingException("Unknown node under PreferredRoamingPartnerList "
+ + "instance " + child.getName());
+ }
+ }
+ return roamingPartner;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/MinBackhaulThreshold subtree
+ * into the given policy.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/MinBackhaulThreshold subtree
+ * @param policy The policy to store the MinBackhualThreshold configuration
+ * @throws ParsingException
+ */
+ private static void parseMinBackhaulThreshold(PPSNode node, Policy policy)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for MinBackhaulThreshold");
+ }
+ for (PPSNode child : node.getChildren()) {
+ parseMinBackhaulThresholdInstance(child, policy);
+ }
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/MinBackhaulThreshold/<X+> subtree
+ * into the given policy.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/MinBackhaulThreshold/<X+> subtree
+ * @param policy The policy to store the MinBackhaulThreshold configuration
+ * @throws ParsingException
+ */
+ private static void parseMinBackhaulThresholdInstance(PPSNode node, Policy policy)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for MinBackhaulThreshold instance");
+ }
+ String networkType = null;
+ long downlinkBandwidth = Long.MIN_VALUE;
+ long uplinkBandwidth = Long.MIN_VALUE;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_NETWORK_TYPE:
+ networkType = getPpsNodeValue(child);
+ break;
+ case NODE_DOWNLINK_BANDWIDTH:
+ downlinkBandwidth = parseLong(getPpsNodeValue(child), 10);
+ break;
+ case NODE_UPLINK_BANDWIDTH:
+ uplinkBandwidth = parseLong(getPpsNodeValue(child), 10);
+ break;
+ default:
+ throw new ParsingException("Unknown node under MinBackhaulThreshold instance "
+ + child.getName());
+ }
+ }
+ if (networkType == null) {
+ throw new ParsingException("Missing NetworkType field");
+ }
+
+ if (TextUtils.equals(networkType, "home")) {
+ policy.setMinHomeDownlinkBandwidth(downlinkBandwidth);
+ policy.setMinHomeUplinkBandwidth(uplinkBandwidth);
+ } else if (TextUtils.equals(networkType, "roaming")) {
+ policy.setMinRoamingDownlinkBandwidth(downlinkBandwidth);
+ policy.setMinRoamingUplinkBandwidth(uplinkBandwidth);
+ } else {
+ throw new ParsingException("Invalid network type: " + networkType);
+ }
+ }
+
+ /**
+ * Parse update parameters. This contained configurations from either
+ * PerProviderSubscription/Policy/PolicyUpdate or PerProviderSubscription/SubscriptionUpdate
+ * subtree.
+ *
+ * @param node PPSNode representing the root of the PerProviderSubscription/Policy/PolicyUpdate
+ * or PerProviderSubscription/SubscriptionUpdate subtree
+ * @return {@link UpdateParameter}
+ * @throws ParsingException
+ */
+ private static UpdateParameter parseUpdateParameter(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for Update Parameters");
+ }
+
+ UpdateParameter updateParam = new UpdateParameter();
+ for (PPSNode child : node.getChildren()) {
+ switch(child.getName()) {
+ case NODE_UPDATE_INTERVAL:
+ updateParam.setUpdateIntervalInMinutes(parseLong(getPpsNodeValue(child), 10));
+ break;
+ case NODE_UPDATE_METHOD:
+ updateParam.setUpdateMethod(getPpsNodeValue(child));
+ break;
+ case NODE_RESTRICTION:
+ updateParam.setRestriction(getPpsNodeValue(child));
+ break;
+ case NODE_URI:
+ updateParam.setServerUri(getPpsNodeValue(child));
+ break;
+ case NODE_USERNAME_PASSWORD:
+ Pair<String, String> usernamePassword = parseUpdateUserCredential(child);
+ updateParam.setUsername(usernamePassword.first);
+ updateParam.setBase64EncodedPassword(usernamePassword.second);
+ break;
+ case NODE_TRUST_ROOT:
+ Pair<String, byte[]> trustRoot = parseTrustRoot(child);
+ updateParam.setTrustRootCertUrl(trustRoot.first);
+ updateParam.setTrustRootCertSha256Fingerprint(trustRoot.second);
+ break;
+ case NODE_OTHER:
+ Log.d(TAG, "Ignore unsupported paramter: " + child.getName());
+ break;
+ default:
+ throw new ParsingException("Unknown node under Update Parameters: "
+ + child.getName());
+ }
+ }
+ return updateParam;
+ }
+
+ /**
+ * Parse username and password parameters associated with policy or subscription update.
+ * This contained configurations under either
+ * PerProviderSubscription/Policy/PolicyUpdate/UsernamePassword or
+ * PerProviderSubscription/SubscriptionUpdate/UsernamePassword subtree.
+ *
+ * @param node PPSNode representing the root of the UsernamePassword subtree
+ * @return Pair of username and password
+ * @throws ParsingException
+ */
+ private static Pair<String, String> parseUpdateUserCredential(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for UsernamePassword");
+ }
+
+ String username = null;
+ String password = null;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_USERNAME:
+ username = getPpsNodeValue(child);
+ break;
+ case NODE_PASSWORD:
+ password = getPpsNodeValue(child);
+ break;
+ default:
+ throw new ParsingException("Unknown node under UsernamePassword: "
+ + child.getName());
+ }
+ }
+ return Pair.create(username, password);
+ }
+
+ /**
+ * Parse the trust root parameters associated with policy update, subscription update, or AAA
+ * server trust root.
+ *
+ * This contained configurations under either
+ * PerProviderSubscription/Policy/PolicyUpdate/TrustRoot or
+ * PerProviderSubscription/SubscriptionUpdate/TrustRoot or
+ * PerProviderSubscription/AAAServerTrustRoot/<X+> subtree.
+ *
+ * @param node PPSNode representing the root of the TrustRoot subtree
+ * @return Pair of Certificate URL and fingerprint
+ * @throws ParsingException
+ */
+ private static Pair<String, byte[]> parseTrustRoot(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for TrustRoot");
+ }
+
+ String certUrl = null;
+ byte[] certFingerprint = null;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_CERT_URL:
+ certUrl = getPpsNodeValue(child);
+ break;
+ case NODE_CERT_SHA256_FINGERPRINT:
+ certFingerprint = parseHexString(getPpsNodeValue(child));
+ break;
+ default:
+ throw new ParsingException("Unknown node under TrustRoot: "
+ + child.getName());
+ }
+ }
+ return Pair.create(certUrl, certFingerprint);
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/SPExclusionList subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/SPExclusionList subtree
+ * @return Array of excluded SSIDs
+ * @throws ParsingException
+ */
+ private static String[] parseSpExclusionList(PPSNode node) throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for SPExclusionList");
+ }
+ List<String> ssidList = new ArrayList<>();
+ for (PPSNode child : node.getChildren()) {
+ ssidList.add(parseSpExclusionInstance(child));
+ }
+ return ssidList.toArray(new String[ssidList.size()]);
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/SPExclusionList/<X+> subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/SPExclusionList/<X+> subtree
+ * @return String
+ * @throws ParsingException
+ */
+ private static String parseSpExclusionInstance(PPSNode node) throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for SPExclusion instance");
+ }
+ String ssid = null;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_SSID:
+ ssid = getPpsNodeValue(child);
+ break;
+ default:
+ throw new ParsingException("Unknown node under SPExclusion instance");
+ }
+ }
+ return ssid;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/RequiredProtoPortTuple subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/RequiredProtoPortTuple subtree
+ * @return Map of IP Protocol to Port Number tuples
+ * @throws ParsingException
+ */
+ private static Map<Integer, String> parseRequiredProtoPortTuple(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for RequiredProtoPortTuple");
+ }
+ Map<Integer, String> protoPortTupleMap = new HashMap<>();
+ for (PPSNode child : node.getChildren()) {
+ Pair<Integer, String> protoPortTuple = parseProtoPortTuple(child);
+ protoPortTupleMap.put(protoPortTuple.first, protoPortTuple.second);
+ }
+ return protoPortTupleMap;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/RequiredProtoPortTuple/<X+>
+ * subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/RequiredProtoPortTuple/<X+> subtree
+ * @return Pair of IP Protocol to Port Number tuple
+ * @throws ParsingException
+ */
+ private static Pair<Integer, String> parseProtoPortTuple(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for RequiredProtoPortTuple "
+ + "instance");
+ }
+ int proto = Integer.MIN_VALUE;
+ String ports = null;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_IP_PROTOCOL:
+ proto = parseInteger(getPpsNodeValue(child));
+ break;
+ case NODE_PORT_NUMBER:
+ ports = getPpsNodeValue(child);
+ break;
+ default:
+ throw new ParsingException("Unknown node under RequiredProtoPortTuple instance"
+ + child.getName());
+ }
+ }
+ if (proto == Integer.MIN_VALUE) {
+ throw new ParsingException("Missing IPProtocol field");
+ }
+ if (ports == null) {
+ throw new ParsingException("Missing PortNumber field");
+ }
+ return Pair.create(proto, ports);
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/AAAServerTrustRoot subtree.
+ *
+ * @param node PPSNode representing the root of PerProviderSubscription/AAAServerTrustRoot
+ * subtree
+ * @return Map of certificate URL with the corresponding certificate fingerprint
+ * @throws ParsingException
+ */
+ private static Map<String, byte[]> parseAAAServerTrustRootList(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for AAAServerTrustRoot");
+ }
+ Map<String, byte[]> certList = new HashMap<>();
+ for (PPSNode child : node.getChildren()) {
+ Pair<String, byte[]> certTuple = parseTrustRoot(child);
+ certList.put(certTuple.first, certTuple.second);
+ }
+ return certList;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/SubscriptionParameter subtree.
+ *
+ * @param node PPSNode representing the root of PerProviderSubscription/SubscriptionParameter
+ * subtree
+ * @param config Instance of {@link PasspointConfiguration}
+ * @throws ParsingException
+ */
+ private static void parseSubscriptionParameter(PPSNode node, PasspointConfiguration config)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for SubscriptionParameter");
+ }
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_CREATION_DATE:
+ config.setSubscriptionCreationTimeInMillis(parseDate(getPpsNodeValue(child)));
+ break;
+ case NODE_EXPIRATION_DATE:
+ config.setSubscriptionExpirationTimeInMillis(parseDate(getPpsNodeValue(child)));
+ break;
+ case NODE_TYPE_OF_SUBSCRIPTION:
+ config.setSubscriptionType(getPpsNodeValue(child));
+ break;
+ case NODE_USAGE_LIMITS:
+ parseUsageLimits(child, config);
+ break;
+ default:
+ throw new ParsingException("Unknown node under SubscriptionParameter"
+ + child.getName());
+ }
+ }
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/SubscriptionParameter/UsageLimits
+ * subtree.
+ *
+ * @param node PPSNode representing the root of
+ * PerProviderSubscription/SubscriptionParameter/UsageLimits subtree
+ * @param config Instance of {@link PasspointConfiguration}
+ * @throws ParsingException
+ */
+ private static void parseUsageLimits(PPSNode node, PasspointConfiguration config)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for UsageLimits");
+ }
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_DATA_LIMIT:
+ config.setUsageLimitDataLimit(parseLong(getPpsNodeValue(child), 10));
+ break;
+ case NODE_START_DATE:
+ config.setUsageLimitStartTimeInMillis(parseDate(getPpsNodeValue(child)));
+ break;
+ case NODE_TIME_LIMIT:
+ config.setUsageLimitTimeLimitInMinutes(parseLong(getPpsNodeValue(child), 10));
+ break;
+ case NODE_USAGE_TIME_PERIOD:
+ config.setUsageLimitUsageTimePeriodInMinutes(
+ parseLong(getPpsNodeValue(child), 10));
+ break;
+ default:
+ throw new ParsingException("Unknown node under UsageLimits"
+ + child.getName());
+ }
+ }
+ }
+
+ /**
+ * Convert a hex string to a byte array.
+ *
+ * @param str String containing hex values
+ * @return byte[]
+ * @throws ParsingException
+ */
+ private static byte[] parseHexString(String str) throws ParsingException {
+ if ((str.length() & 1) == 1) {
+ throw new ParsingException("Odd length hex string: " + str.length());
+ }
+
+ byte[] result = new byte[str.length() / 2];
+ for (int i = 0; i < result.length; i++) {
+ int index = i * 2;
+ try {
+ result[i] = (byte) Integer.parseInt(str.substring(index, index + 2), 16);
+ } catch (NumberFormatException e) {
+ throw new ParsingException("Invalid hex string: " + str);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Convert a date string to the number of milliseconds since January 1, 1970, 00:00:00 GMT.
+ *
+ * @param dateStr String in the format of yyyy-MM-dd'T'HH:mm:ss'Z'
+ * @return number of milliseconds
+ * @throws ParsingException
+ */
+ private static long parseDate(String dateStr) throws ParsingException {
+ try {
+ DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+ return format.parse(dateStr).getTime();
+ } catch (ParseException pe) {
+ throw new ParsingException("Badly formatted time: " + dateStr);
+ }
+ }
+
+ /**
+ * Parse an integer string.
+ *
+ * @param value String of integer value
+ * @return int
+ * @throws ParsingException
+ */
+ private static int parseInteger(String value) throws ParsingException {
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ throw new ParsingException("Invalid integer value: " + value);
+ }
+ }
+
+ /**
+ * Parse a string representing a long integer.
+ *
+ * @param value String of long integer value
+ * @return long
+ * @throws ParsingException
+ */
+ private static long parseLong(String value, int radix) throws ParsingException {
+ try {
+ return Long.parseLong(value, radix);
+ } catch (NumberFormatException e) {
+ throw new ParsingException("Invalid long integer value: " + value);
+ }
+ }
+
+ /**
+ * Convert a List<Long> to a primitive long array long[].
+ *
+ * @param list List to be converted
+ * @return long[]
+ */
+ private static long[] convertFromLongList(List<Long> list) {
+ Long[] objectArray = list.toArray(new Long[list.size()]);
+ long[] primitiveArray = new long[objectArray.length];
+ for (int i = 0; i < objectArray.length; i++) {
+ primitiveArray[i] = objectArray[i].longValue();
+ }
+ return primitiveArray;
+ }
+}
diff --git a/android/net/wifi/hotspot2/omadm/XMLNode.java b/android/net/wifi/hotspot2/omadm/XMLNode.java
new file mode 100644
index 0000000..959d505
--- /dev/null
+++ b/android/net/wifi/hotspot2/omadm/XMLNode.java
@@ -0,0 +1,109 @@
+/**
+ * Copyright (c) 2016, 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.hotspot2.omadm;
+
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A class represent a node in an XML tree. Each node is an XML element.
+ * Used by {@link XMLParser} for parsing/converting each XML element to XMLNode.
+ *
+ * @hide
+ */
+public class XMLNode {
+ private final String mTag;
+ private final List<XMLNode> mChildren;
+ private final XMLNode mParent;
+ private StringBuilder mTextBuilder;
+ private String mText;
+
+ public XMLNode(XMLNode parent, String tag) {
+ mTag = tag;
+ mParent = parent;
+ mChildren = new ArrayList<>();
+ mTextBuilder = new StringBuilder();
+ mText = null;
+ }
+
+ /**
+ * Adding a text to this node. Invoked by {@link XMLParser#characters}.
+ *
+ * @param text String to be added
+ */
+ public void addText(String text) {
+ mTextBuilder.append(text);
+ }
+
+ /**
+ * Adding a child node to this node. Invoked by {@link XMLParser#startElement}.
+ *
+ * @param child XMLNode to be added
+ */
+ public void addChild(XMLNode child) {
+ mChildren.add(child);
+ }
+
+ /**
+ * Invoked when the end of the XML element is detected. Used for further processing
+ * of the text enclosed within this XML element. Invoked by {@link XMLParser#endElement}.
+ */
+ public void close() {
+ // Remove the leading and the trailing whitespaces.
+ mText = mTextBuilder.toString().trim();
+ mTextBuilder = null;
+ }
+
+ public String getTag() {
+ return mTag;
+ }
+
+ public XMLNode getParent() {
+ return mParent;
+ }
+
+ public String getText() {
+ return mText;
+ }
+
+ public List<XMLNode> getChildren() {
+ return mChildren;
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof XMLNode)) {
+ return false;
+ }
+ XMLNode that = (XMLNode) thatObject;
+
+ return TextUtils.equals(mTag, that.mTag) &&
+ TextUtils.equals(mText, that.mText) &&
+ mChildren.equals(that.mChildren);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTag, mText, mChildren);
+ }
+}
diff --git a/android/net/wifi/hotspot2/omadm/XMLParser.java b/android/net/wifi/hotspot2/omadm/XMLParser.java
new file mode 100644
index 0000000..948052c
--- /dev/null
+++ b/android/net/wifi/hotspot2/omadm/XMLParser.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2016 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.hotspot2.omadm;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import android.text.TextUtils;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+/**
+ * Class for parsing an XML string to an XML tree represented by {@link XMLNode}.
+ *
+ * The original XML string:
+ * <root>
+ * <tag1>text1</tag1>
+ * <tag2>
+ * <tag3>text3</tag3>
+ * </tag2>
+ * </root>
+ *
+ * The XML tree representation:
+ * [root]
+ * |
+ * |
+ * [tag1, text1]-----|-----[tag2]
+ * |
+ * |
+ * [tag3, text3]
+ *
+ * @hide
+ */
+public class XMLParser extends DefaultHandler {
+ private XMLNode mRoot = null;
+ private XMLNode mCurrent = null;
+
+ public XMLNode parse(String text) throws IOException, SAXException {
+ if (TextUtils.isEmpty(text)) {
+ throw new IOException("XML string not provided");
+ }
+
+ // Reset pointers.
+ mRoot = null;
+ mCurrent = null;
+
+ try {
+ SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
+ parser.parse(new InputSource(new StringReader(text)), this);
+ return mRoot;
+ } catch (ParserConfigurationException pce) {
+ throw new SAXException(pce);
+ }
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes attributes)
+ throws SAXException {
+ XMLNode parent = mCurrent;
+
+ mCurrent = new XMLNode(parent, qName);
+
+ if (mRoot == null) {
+ mRoot = mCurrent;
+ } else if (parent == null) {
+ throw new SAXException("More than one root nodes");
+ } else {
+ parent.addChild(mCurrent);
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ if (!qName.equals(mCurrent.getTag())) {
+ throw new SAXException("End tag '" + qName + "' doesn't match current node: " +
+ mCurrent);
+ }
+
+ mCurrent.close();
+ mCurrent = mCurrent.getParent();
+ }
+
+ @Override
+ public void characters(char[] ch, int start, int length) throws SAXException {
+ mCurrent.addText(new String(ch, start, length));
+ }
+}
diff --git a/android/net/wifi/hotspot2/pps/Credential.java b/android/net/wifi/hotspot2/pps/Credential.java
new file mode 100644
index 0000000..9409c03
--- /dev/null
+++ b/android/net/wifi/hotspot2/pps/Credential.java
@@ -0,0 +1,1296 @@
+/**
+ * Copyright (c) 2016, 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.hotspot2.pps;
+
+import android.net.wifi.EAPConstants;
+import android.net.wifi.ParcelUtil;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Class representing Credential subtree in the PerProviderSubscription (PPS)
+ * Management Object (MO) tree.
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ *
+ * In addition to the fields in the Credential subtree, this will also maintain necessary
+ * information for the private key and certificates associated with this credential.
+ */
+public final class Credential implements Parcelable {
+ private static final String TAG = "Credential";
+
+ /**
+ * Max string length for realm. Refer to Credential/Realm node in Hotspot 2.0 Release 2
+ * Technical Specification Section 9.1 for more info.
+ */
+ private static final int MAX_REALM_BYTES = 253;
+
+ /**
+ * The time this credential is created. It is in the format of number
+ * of milliseconds since January 1, 1970, 00:00:00 GMT.
+ * Using Long.MIN_VALUE to indicate unset value.
+ */
+ private long mCreationTimeInMillis = Long.MIN_VALUE;
+ /**
+ * @hide
+ */
+ public void setCreationTimeInMillis(long creationTimeInMillis) {
+ mCreationTimeInMillis = creationTimeInMillis;
+ }
+ /**
+ * @hide
+ */
+ public long getCreationTimeInMillis() {
+ return mCreationTimeInMillis;
+ }
+
+ /**
+ * The time this credential will expire. It is in the format of number
+ * of milliseconds since January 1, 1970, 00:00:00 GMT.
+ * Using Long.MIN_VALUE to indicate unset value.
+ */
+ private long mExpirationTimeInMillis = Long.MIN_VALUE;
+ /**
+ * @hide
+ */
+ public void setExpirationTimeInMillis(long expirationTimeInMillis) {
+ mExpirationTimeInMillis = expirationTimeInMillis;
+ }
+ /**
+ * @hide
+ */
+ public long getExpirationTimeInMillis() {
+ return mExpirationTimeInMillis;
+ }
+
+ /**
+ * The realm associated with this credential. It will be used to determine
+ * if this credential can be used to authenticate with a given hotspot by
+ * comparing the realm specified in that hotspot's ANQP element.
+ */
+ private String mRealm = null;
+ /**
+ * Set the realm associated with this credential.
+ *
+ * @param realm The realm to set to
+ */
+ public void setRealm(String realm) {
+ mRealm = realm;
+ }
+ /**
+ * Get the realm associated with this credential.
+ *
+ * @return the realm associated with this credential
+ */
+ public String getRealm() {
+ return mRealm;
+ }
+
+ /**
+ * When set to true, the device should check AAA (Authentication, Authorization,
+ * and Accounting) server's certificate during EAP (Extensible Authentication
+ * Protocol) authentication.
+ */
+ private boolean mCheckAaaServerCertStatus = false;
+ /**
+ * @hide
+ */
+ public void setCheckAaaServerCertStatus(boolean checkAaaServerCertStatus) {
+ mCheckAaaServerCertStatus = checkAaaServerCertStatus;
+ }
+ /**
+ * @hide
+ */
+ public boolean getCheckAaaServerCertStatus() {
+ return mCheckAaaServerCertStatus;
+ }
+
+ /**
+ * Username-password based credential.
+ * Contains the fields under PerProviderSubscription/Credential/UsernamePassword subtree.
+ */
+ public static final class UserCredential implements Parcelable {
+ /**
+ * Maximum string length for username. Refer to Credential/UsernamePassword/Username
+ * node in Hotspot 2.0 Release 2 Technical Specification Section 9.1 for more info.
+ */
+ private static final int MAX_USERNAME_BYTES = 63;
+
+ /**
+ * Maximum string length for password. Refer to Credential/UsernamePassword/Password
+ * in Hotspot 2.0 Release 2 Technical Specification Section 9.1 for more info.
+ */
+ private static final int MAX_PASSWORD_BYTES = 255;
+
+ /**
+ * Supported authentication methods.
+ * @hide
+ */
+ public static final String AUTH_METHOD_PAP = "PAP";
+ /** @hide */
+ public static final String AUTH_METHOD_MSCHAP = "MS-CHAP";
+ /** @hide */
+ public static final String AUTH_METHOD_MSCHAPV2 = "MS-CHAP-V2";
+
+ /**
+ * Supported Non-EAP inner methods. Refer to
+ * Credential/UsernamePassword/EAPMethod/InnerEAPType in Hotspot 2.0 Release 2 Technical
+ * Specification Section 9.1 for more info.
+ */
+ private static final Set<String> SUPPORTED_AUTH = new HashSet<String>(
+ Arrays.asList(AUTH_METHOD_PAP, AUTH_METHOD_MSCHAP, AUTH_METHOD_MSCHAPV2));
+
+ /**
+ * Username of the credential.
+ */
+ private String mUsername = null;
+ /**
+ * Set the username associated with this user credential.
+ *
+ * @param username The username to set to
+ */
+ public void setUsername(String username) {
+ mUsername = username;
+ }
+ /**
+ * Get the username associated with this user credential.
+ *
+ * @return the username associated with this user credential
+ */
+ public String getUsername() {
+ return mUsername;
+ }
+
+ /**
+ * Base64-encoded password.
+ */
+ private String mPassword = null;
+ /**
+ * Set the Base64-encoded password associated with this user credential.
+ *
+ * @param password The password to set to
+ */
+ public void setPassword(String password) {
+ mPassword = password;
+ }
+ /**
+ * Get the Base64-encoded password associated with this user credential.
+ *
+ * @return the Base64-encoded password associated with this user credential
+ */
+ public String getPassword() {
+ return mPassword;
+ }
+
+ /**
+ * Flag indicating if the password is machine managed.
+ */
+ private boolean mMachineManaged = false;
+ /**
+ * @hide
+ */
+ public void setMachineManaged(boolean machineManaged) {
+ mMachineManaged = machineManaged;
+ }
+ /**
+ * @hide
+ */
+ public boolean getMachineManaged() {
+ return mMachineManaged;
+ }
+
+ /**
+ * The name of the application used to generate the password.
+ */
+ private String mSoftTokenApp = null;
+ /**
+ * @hide
+ */
+ public void setSoftTokenApp(String softTokenApp) {
+ mSoftTokenApp = softTokenApp;
+ }
+ /**
+ * @hide
+ */
+ public String getSoftTokenApp() {
+ return mSoftTokenApp;
+ }
+
+ /**
+ * Flag indicating if this credential is usable on other mobile devices as well.
+ */
+ private boolean mAbleToShare = false;
+ /**
+ * @hide
+ */
+ public void setAbleToShare(boolean ableToShare) {
+ mAbleToShare = ableToShare;
+ }
+ /**
+ * @hide
+ */
+ public boolean getAbleToShare() {
+ return mAbleToShare;
+ }
+
+ /**
+ * EAP (Extensible Authentication Protocol) method type.
+ * Refer to
+ * <a href="http://www.iana.org/assignments/eap-numbers/eap-numbers.xml#eap-numbers-4">
+ * EAP Numbers</a> for valid values.
+ * Using Integer.MIN_VALUE to indicate unset value.
+ */
+ private int mEapType = Integer.MIN_VALUE;
+ /**
+ * Set the EAP (Extensible Authentication Protocol) method type associated with this
+ * user credential.
+ * Refer to
+ * <a href="http://www.iana.org/assignments/eap-numbers/eap-numbers.xml#eap-numbers-4">
+ * EAP Numbers</a> for valid values.
+ *
+ * @param eapType The EAP method type associated with this user credential
+ */
+ public void setEapType(int eapType) {
+ mEapType = eapType;
+ }
+ /**
+ * Get the EAP (Extensible Authentication Protocol) method type associated with this
+ * user credential.
+ *
+ * @return EAP method type
+ */
+ public int getEapType() {
+ return mEapType;
+ }
+
+ /**
+ * Non-EAP inner authentication method.
+ */
+ private String mNonEapInnerMethod = null;
+ /**
+ * Set the inner non-EAP method associated with this user credential.
+ *
+ * @param nonEapInnerMethod The non-EAP inner method to set to
+ */
+ public void setNonEapInnerMethod(String nonEapInnerMethod) {
+ mNonEapInnerMethod = nonEapInnerMethod;
+ }
+ /**
+ * Get the inner non-EAP method associated with this user credential.
+ *
+ * @return Non-EAP inner method associated with this user credential
+ */
+ public String getNonEapInnerMethod() {
+ return mNonEapInnerMethod;
+ }
+
+ /**
+ * Constructor for creating UserCredential with default values.
+ */
+ public UserCredential() {}
+
+ /**
+ * Copy constructor.
+ *
+ * @param source The source to copy from
+ */
+ public UserCredential(UserCredential source) {
+ if (source != null) {
+ mUsername = source.mUsername;
+ mPassword = source.mPassword;
+ mMachineManaged = source.mMachineManaged;
+ mSoftTokenApp = source.mSoftTokenApp;
+ mAbleToShare = source.mAbleToShare;
+ mEapType = source.mEapType;
+ mNonEapInnerMethod = source.mNonEapInnerMethod;
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mUsername);
+ dest.writeString(mPassword);
+ dest.writeInt(mMachineManaged ? 1 : 0);
+ dest.writeString(mSoftTokenApp);
+ dest.writeInt(mAbleToShare ? 1 : 0);
+ dest.writeInt(mEapType);
+ dest.writeString(mNonEapInnerMethod);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof UserCredential)) {
+ return false;
+ }
+
+ UserCredential that = (UserCredential) thatObject;
+ return TextUtils.equals(mUsername, that.mUsername)
+ && TextUtils.equals(mPassword, that.mPassword)
+ && mMachineManaged == that.mMachineManaged
+ && TextUtils.equals(mSoftTokenApp, that.mSoftTokenApp)
+ && mAbleToShare == that.mAbleToShare
+ && mEapType == that.mEapType
+ && TextUtils.equals(mNonEapInnerMethod, that.mNonEapInnerMethod);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUsername, mPassword, mMachineManaged, mSoftTokenApp,
+ mAbleToShare, mEapType, mNonEapInnerMethod);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Username: ").append(mUsername).append("\n");
+ builder.append("MachineManaged: ").append(mMachineManaged).append("\n");
+ builder.append("SoftTokenApp: ").append(mSoftTokenApp).append("\n");
+ builder.append("AbleToShare: ").append(mAbleToShare).append("\n");
+ builder.append("EAPType: ").append(mEapType).append("\n");
+ builder.append("AuthMethod: ").append(mNonEapInnerMethod).append("\n");
+ return builder.toString();
+ }
+
+ /**
+ * Validate the configuration data.
+ *
+ * @return true on success or false on failure
+ * @hide
+ */
+ public boolean validate() {
+ if (TextUtils.isEmpty(mUsername)) {
+ Log.d(TAG, "Missing username");
+ return false;
+ }
+ if (mUsername.getBytes(StandardCharsets.UTF_8).length > MAX_USERNAME_BYTES) {
+ Log.d(TAG, "username exceeding maximum length: "
+ + mUsername.getBytes(StandardCharsets.UTF_8).length);
+ return false;
+ }
+
+ if (TextUtils.isEmpty(mPassword)) {
+ Log.d(TAG, "Missing password");
+ return false;
+ }
+ if (mPassword.getBytes(StandardCharsets.UTF_8).length > MAX_PASSWORD_BYTES) {
+ Log.d(TAG, "password exceeding maximum length: "
+ + mPassword.getBytes(StandardCharsets.UTF_8).length);
+ return false;
+ }
+
+ // Only supports EAP-TTLS for user credential.
+ if (mEapType != EAPConstants.EAP_TTLS) {
+ Log.d(TAG, "Invalid EAP Type for user credential: " + mEapType);
+ return false;
+ }
+
+ // Verify Non-EAP inner method for EAP-TTLS.
+ if (!SUPPORTED_AUTH.contains(mNonEapInnerMethod)) {
+ Log.d(TAG, "Invalid non-EAP inner method for EAP-TTLS: " + mNonEapInnerMethod);
+ return false;
+ }
+ return true;
+ }
+
+ public static final @android.annotation.NonNull Creator<UserCredential> CREATOR =
+ new Creator<UserCredential>() {
+ @Override
+ public UserCredential createFromParcel(Parcel in) {
+ UserCredential userCredential = new UserCredential();
+ userCredential.setUsername(in.readString());
+ userCredential.setPassword(in.readString());
+ userCredential.setMachineManaged(in.readInt() != 0);
+ userCredential.setSoftTokenApp(in.readString());
+ userCredential.setAbleToShare(in.readInt() != 0);
+ userCredential.setEapType(in.readInt());
+ userCredential.setNonEapInnerMethod(in.readString());
+ return userCredential;
+ }
+
+ @Override
+ public UserCredential[] newArray(int size) {
+ return new UserCredential[size];
+ }
+ };
+ }
+ private UserCredential mUserCredential = null;
+ /**
+ * Set the user credential information.
+ *
+ * @param userCredential The user credential to set to
+ */
+ public void setUserCredential(UserCredential userCredential) {
+ mUserCredential = userCredential;
+ }
+ /**
+ * Get the user credential information.
+ *
+ * @return user credential information
+ */
+ public UserCredential getUserCredential() {
+ return mUserCredential;
+ }
+
+ /**
+ * Certificate based credential. This is used for EAP-TLS.
+ * Contains fields under PerProviderSubscription/Credential/DigitalCertificate subtree.
+ */
+ public static final class CertificateCredential implements Parcelable {
+ /**
+ * Supported certificate types.
+ * @hide
+ */
+ public static final String CERT_TYPE_X509V3 = "x509v3";
+
+ /**
+ * Certificate SHA-256 fingerprint length.
+ */
+ private static final int CERT_SHA256_FINGER_PRINT_LENGTH = 32;
+
+ /**
+ * Certificate type.
+ */
+ private String mCertType = null;
+ /**
+ * Set the certificate type associated with this certificate credential.
+ *
+ * @param certType The certificate type to set to
+ */
+ public void setCertType(String certType) {
+ mCertType = certType;
+ }
+ /**
+ * Get the certificate type associated with this certificate credential.
+ *
+ * @return certificate type
+ */
+ public String getCertType() {
+ return mCertType;
+ }
+
+ /**
+ * The SHA-256 fingerprint of the certificate.
+ */
+ private byte[] mCertSha256Fingerprint = null;
+ /**
+ * Set the certificate SHA-256 fingerprint associated with this certificate credential.
+ *
+ * @param certSha256Fingerprint The certificate fingerprint to set to
+ */
+ public void setCertSha256Fingerprint(byte[] certSha256Fingerprint) {
+ mCertSha256Fingerprint = certSha256Fingerprint;
+ }
+ /**
+ * Get the certificate SHA-256 fingerprint associated with this certificate credential.
+ *
+ * @return certificate SHA-256 fingerprint
+ */
+ public byte[] getCertSha256Fingerprint() {
+ return mCertSha256Fingerprint;
+ }
+
+ /**
+ * Constructor for creating CertificateCredential with default values.
+ */
+ public CertificateCredential() {}
+
+ /**
+ * Copy constructor.
+ *
+ * @param source The source to copy from
+ */
+ public CertificateCredential(CertificateCredential source) {
+ if (source != null) {
+ mCertType = source.mCertType;
+ if (source.mCertSha256Fingerprint != null) {
+ mCertSha256Fingerprint = Arrays.copyOf(source.mCertSha256Fingerprint,
+ source.mCertSha256Fingerprint.length);
+ }
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mCertType);
+ dest.writeByteArray(mCertSha256Fingerprint);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof CertificateCredential)) {
+ return false;
+ }
+
+ CertificateCredential that = (CertificateCredential) thatObject;
+ return TextUtils.equals(mCertType, that.mCertType)
+ && Arrays.equals(mCertSha256Fingerprint, that.mCertSha256Fingerprint);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mCertType, Arrays.hashCode(mCertSha256Fingerprint));
+ }
+
+ @Override
+ public String toString() {
+ return "CertificateType: " + mCertType + "\n";
+ }
+
+ /**
+ * Validate the configuration data.
+ *
+ * @return true on success or false on failure
+ * @hide
+ */
+ public boolean validate() {
+ if (!TextUtils.equals(CERT_TYPE_X509V3, mCertType)) {
+ Log.d(TAG, "Unsupported certificate type: " + mCertType);
+ return false;
+ }
+ if (mCertSha256Fingerprint == null
+ || mCertSha256Fingerprint.length != CERT_SHA256_FINGER_PRINT_LENGTH) {
+ Log.d(TAG, "Invalid SHA-256 fingerprint");
+ return false;
+ }
+ return true;
+ }
+
+ public static final @android.annotation.NonNull Creator<CertificateCredential> CREATOR =
+ new Creator<CertificateCredential>() {
+ @Override
+ public CertificateCredential createFromParcel(Parcel in) {
+ CertificateCredential certCredential = new CertificateCredential();
+ certCredential.setCertType(in.readString());
+ certCredential.setCertSha256Fingerprint(in.createByteArray());
+ return certCredential;
+ }
+
+ @Override
+ public CertificateCredential[] newArray(int size) {
+ return new CertificateCredential[size];
+ }
+ };
+ }
+ private CertificateCredential mCertCredential = null;
+ /**
+ * Set the certificate credential information.
+ *
+ * @param certCredential The certificate credential to set to
+ */
+ public void setCertCredential(CertificateCredential certCredential) {
+ mCertCredential = certCredential;
+ }
+ /**
+ * Get the certificate credential information.
+ *
+ * @return certificate credential information
+ */
+ public CertificateCredential getCertCredential() {
+ return mCertCredential;
+ }
+
+ /**
+ * SIM (Subscriber Identify Module) based credential.
+ * Contains fields under PerProviderSubscription/Credential/SIM subtree.
+ */
+ public static final class SimCredential implements Parcelable {
+ /**
+ * Maximum string length for IMSI.
+ */
+ private static final int MAX_IMSI_LENGTH = 15;
+
+ /**
+ * International Mobile Subscriber Identity, is used to identify the user
+ * of a cellular network and is a unique identification associated with all
+ * cellular networks
+ */
+ private String mImsi = null;
+ /**
+ * Set the IMSI (International Mobile Subscriber Identity) associated with this SIM
+ * credential.
+ *
+ * @param imsi The IMSI to set to
+ */
+ public void setImsi(String imsi) {
+ mImsi = imsi;
+ }
+ /**
+ * Get the IMSI (International Mobile Subscriber Identity) associated with this SIM
+ * credential.
+ *
+ * @return IMSI associated with this SIM credential
+ */
+ public String getImsi() {
+ return mImsi;
+ }
+
+ /**
+ * EAP (Extensible Authentication Protocol) method type for using SIM credential.
+ * Refer to http://www.iana.org/assignments/eap-numbers/eap-numbers.xml#eap-numbers-4
+ * for valid values.
+ * Using Integer.MIN_VALUE to indicate unset value.
+ */
+ private int mEapType = Integer.MIN_VALUE;
+ /**
+ * Set the EAP (Extensible Authentication Protocol) method type associated with this
+ * SIM credential.
+ *
+ * @param eapType The EAP method type to set to
+ */
+ public void setEapType(int eapType) {
+ mEapType = eapType;
+ }
+ /**
+ * Get the EAP (Extensible Authentication Protocol) method type associated with this
+ * SIM credential.
+ *
+ * @return EAP method type associated with this SIM credential
+ */
+ public int getEapType() {
+ return mEapType;
+ }
+
+ /**
+ * Constructor for creating SimCredential with default values.
+ */
+ public SimCredential() {}
+
+ /**
+ * Copy constructor
+ *
+ * @param source The source to copy from
+ */
+ public SimCredential(SimCredential source) {
+ if (source != null) {
+ mImsi = source.mImsi;
+ mEapType = source.mEapType;
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof SimCredential)) {
+ return false;
+ }
+
+ SimCredential that = (SimCredential) thatObject;
+ return TextUtils.equals(mImsi, that.mImsi)
+ && mEapType == that.mEapType;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mImsi, mEapType);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("IMSI: ").append(mImsi).append("\n");
+ builder.append("EAPType: ").append(mEapType).append("\n");
+ return builder.toString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mImsi);
+ dest.writeInt(mEapType);
+ }
+
+ /**
+ * Validate the configuration data.
+ *
+ * @return true on success or false on failure
+ * @hide
+ */
+ public boolean validate() {
+ // Note: this only validate the format of IMSI string itself. Additional verification
+ // will be done by WifiService at the time of provisioning to verify against the IMSI
+ // of the SIM card installed in the device.
+ if (!verifyImsi()) {
+ return false;
+ }
+ if (mEapType != EAPConstants.EAP_SIM && mEapType != EAPConstants.EAP_AKA
+ && mEapType != EAPConstants.EAP_AKA_PRIME) {
+ Log.d(TAG, "Invalid EAP Type for SIM credential: " + mEapType);
+ return false;
+ }
+ return true;
+ }
+
+ public static final @android.annotation.NonNull Creator<SimCredential> CREATOR =
+ new Creator<SimCredential>() {
+ @Override
+ public SimCredential createFromParcel(Parcel in) {
+ SimCredential simCredential = new SimCredential();
+ simCredential.setImsi(in.readString());
+ simCredential.setEapType(in.readInt());
+ return simCredential;
+ }
+
+ @Override
+ public SimCredential[] newArray(int size) {
+ return new SimCredential[size];
+ }
+ };
+
+ /**
+ * Verify the IMSI (International Mobile Subscriber Identity) string. The string
+ * should contain zero or more numeric digits, and might ends with a "*" for prefix
+ * matching.
+ *
+ * @return true if IMSI is valid, false otherwise.
+ */
+ private boolean verifyImsi() {
+ if (TextUtils.isEmpty(mImsi)) {
+ Log.d(TAG, "Missing IMSI");
+ return false;
+ }
+ if (mImsi.length() > MAX_IMSI_LENGTH) {
+ Log.d(TAG, "IMSI exceeding maximum length: " + mImsi.length());
+ return false;
+ }
+
+ // Locate the first non-digit character.
+ int nonDigit;
+ char stopChar = '\0';
+ for (nonDigit = 0; nonDigit < mImsi.length(); nonDigit++) {
+ stopChar = mImsi.charAt(nonDigit);
+ if (stopChar < '0' || stopChar > '9') {
+ break;
+ }
+ }
+
+ if (nonDigit == mImsi.length()) {
+ return true;
+ }
+ else if (nonDigit == mImsi.length()-1 && stopChar == '*') {
+ // Prefix matching.
+ return true;
+ }
+ return false;
+ }
+ }
+ private SimCredential mSimCredential = null;
+ /**
+ * Set the SIM credential information.
+ *
+ * @param simCredential The SIM credential to set to
+ */
+ public void setSimCredential(SimCredential simCredential) {
+ mSimCredential = simCredential;
+ }
+ /**
+ * Get the SIM credential information.
+ *
+ * @return SIM credential information
+ */
+ public SimCredential getSimCredential() {
+ return mSimCredential;
+ }
+
+ /**
+ * CA (Certificate Authority) X509 certificates.
+ */
+ private X509Certificate[] mCaCertificates = null;
+
+ /**
+ * Set the CA (Certification Authority) certificate associated with this credential.
+ *
+ * @param caCertificate The CA certificate to set to
+ */
+ public void setCaCertificate(X509Certificate caCertificate) {
+ mCaCertificates = null;
+ if (caCertificate != null) {
+ mCaCertificates = new X509Certificate[] {caCertificate};
+ }
+ }
+
+ /**
+ * Set the CA (Certification Authority) certificates associated with this credential.
+ *
+ * @param caCertificates The list of CA certificates to set to
+ * @hide
+ */
+ public void setCaCertificates(X509Certificate[] caCertificates) {
+ mCaCertificates = caCertificates;
+ }
+
+ /**
+ * Get the CA (Certification Authority) certificate associated with this credential.
+ *
+ * @return CA certificate associated with this credential, {@code null} if certificate is not
+ * set or certificate is more than one.
+ */
+ public X509Certificate getCaCertificate() {
+ return mCaCertificates == null || mCaCertificates.length > 1 ? null : mCaCertificates[0];
+ }
+
+ /**
+ * Get the CA (Certification Authority) certificates associated with this credential.
+ *
+ * @return The list of CA certificates associated with this credential
+ * @hide
+ */
+ public X509Certificate[] getCaCertificates() {
+ return mCaCertificates;
+ }
+
+ /**
+ * Client side X509 certificate chain.
+ */
+ private X509Certificate[] mClientCertificateChain = null;
+ /**
+ * Set the client certificate chain associated with this credential.
+ *
+ * @param certificateChain The client certificate chain to set to
+ */
+ public void setClientCertificateChain(X509Certificate[] certificateChain) {
+ mClientCertificateChain = certificateChain;
+ }
+ /**
+ * Get the client certificate chain associated with this credential.
+ *
+ * @return client certificate chain associated with this credential
+ */
+ public X509Certificate[] getClientCertificateChain() {
+ return mClientCertificateChain;
+ }
+
+ /**
+ * Client side private key.
+ */
+ private PrivateKey mClientPrivateKey = null;
+ /**
+ * Set the client private key associated with this credential.
+ *
+ * @param clientPrivateKey the client private key to set to
+ */
+ public void setClientPrivateKey(PrivateKey clientPrivateKey) {
+ mClientPrivateKey = clientPrivateKey;
+ }
+ /**
+ * Get the client private key associated with this credential.
+ *
+ * @return client private key associated with this credential.
+ */
+ public PrivateKey getClientPrivateKey() {
+ return mClientPrivateKey;
+ }
+
+ /**
+ * Constructor for creating Credential with default values.
+ */
+ public Credential() {}
+
+ /**
+ * Copy constructor.
+ *
+ * @param source The source to copy from
+ */
+ public Credential(Credential source) {
+ if (source != null) {
+ mCreationTimeInMillis = source.mCreationTimeInMillis;
+ mExpirationTimeInMillis = source.mExpirationTimeInMillis;
+ mRealm = source.mRealm;
+ mCheckAaaServerCertStatus = source.mCheckAaaServerCertStatus;
+ if (source.mUserCredential != null) {
+ mUserCredential = new UserCredential(source.mUserCredential);
+ }
+ if (source.mCertCredential != null) {
+ mCertCredential = new CertificateCredential(source.mCertCredential);
+ }
+ if (source.mSimCredential != null) {
+ mSimCredential = new SimCredential(source.mSimCredential);
+ }
+ if (source.mClientCertificateChain != null) {
+ mClientCertificateChain = Arrays.copyOf(source.mClientCertificateChain,
+ source.mClientCertificateChain.length);
+ }
+ if (source.mCaCertificates != null) {
+ mCaCertificates = Arrays.copyOf(source.mCaCertificates,
+ source.mCaCertificates.length);
+ }
+
+ mClientPrivateKey = source.mClientPrivateKey;
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mCreationTimeInMillis);
+ dest.writeLong(mExpirationTimeInMillis);
+ dest.writeString(mRealm);
+ dest.writeInt(mCheckAaaServerCertStatus ? 1 : 0);
+ dest.writeParcelable(mUserCredential, flags);
+ dest.writeParcelable(mCertCredential, flags);
+ dest.writeParcelable(mSimCredential, flags);
+ ParcelUtil.writeCertificates(dest, mCaCertificates);
+ ParcelUtil.writeCertificates(dest, mClientCertificateChain);
+ ParcelUtil.writePrivateKey(dest, mClientPrivateKey);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof Credential)) {
+ return false;
+ }
+
+ Credential that = (Credential) thatObject;
+ return TextUtils.equals(mRealm, that.mRealm)
+ && mCreationTimeInMillis == that.mCreationTimeInMillis
+ && mExpirationTimeInMillis == that.mExpirationTimeInMillis
+ && mCheckAaaServerCertStatus == that.mCheckAaaServerCertStatus
+ && (mUserCredential == null ? that.mUserCredential == null
+ : mUserCredential.equals(that.mUserCredential))
+ && (mCertCredential == null ? that.mCertCredential == null
+ : mCertCredential.equals(that.mCertCredential))
+ && (mSimCredential == null ? that.mSimCredential == null
+ : mSimCredential.equals(that.mSimCredential))
+ && isX509CertificatesEquals(mCaCertificates, that.mCaCertificates)
+ && isX509CertificatesEquals(mClientCertificateChain, that.mClientCertificateChain)
+ && isPrivateKeyEquals(mClientPrivateKey, that.mClientPrivateKey);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mCreationTimeInMillis, mExpirationTimeInMillis, mRealm,
+ mCheckAaaServerCertStatus, mUserCredential, mCertCredential, mSimCredential,
+ mClientPrivateKey, Arrays.hashCode(mCaCertificates),
+ Arrays.hashCode(mClientCertificateChain));
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Realm: ").append(mRealm).append("\n");
+ builder.append("CreationTime: ").append(mCreationTimeInMillis != Long.MIN_VALUE
+ ? new Date(mCreationTimeInMillis) : "Not specified").append("\n");
+ builder.append("ExpirationTime: ").append(mExpirationTimeInMillis != Long.MIN_VALUE
+ ? new Date(mExpirationTimeInMillis) : "Not specified").append("\n");
+ builder.append("CheckAAAServerStatus: ").append(mCheckAaaServerCertStatus).append("\n");
+ if (mUserCredential != null) {
+ builder.append("UserCredential Begin ---\n");
+ builder.append(mUserCredential);
+ builder.append("UserCredential End ---\n");
+ }
+ if (mCertCredential != null) {
+ builder.append("CertificateCredential Begin ---\n");
+ builder.append(mCertCredential);
+ builder.append("CertificateCredential End ---\n");
+ }
+ if (mSimCredential != null) {
+ builder.append("SIMCredential Begin ---\n");
+ builder.append(mSimCredential);
+ builder.append("SIMCredential End ---\n");
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Validate the configuration data.
+ *
+ * @param isR1 {@code true} if the configuration is for R1
+ * @return true on success or false on failure
+ * @hide
+ */
+ public boolean validate(boolean isR1) {
+ if (TextUtils.isEmpty(mRealm)) {
+ Log.d(TAG, "Missing realm");
+ return false;
+ }
+ if (mRealm.getBytes(StandardCharsets.UTF_8).length > MAX_REALM_BYTES) {
+ Log.d(TAG, "realm exceeding maximum length: "
+ + mRealm.getBytes(StandardCharsets.UTF_8).length);
+ return false;
+ }
+
+ // Verify the credential.
+ if (mUserCredential != null) {
+ if (!verifyUserCredential(isR1)) {
+ return false;
+ }
+ } else if (mCertCredential != null) {
+ if (!verifyCertCredential(isR1)) {
+ return false;
+ }
+ } else if (mSimCredential != null) {
+ if (!verifySimCredential()) {
+ return false;
+ }
+ } else {
+ Log.d(TAG, "Missing required credential");
+ return false;
+ }
+
+ return true;
+ }
+
+ public static final @android.annotation.NonNull Creator<Credential> CREATOR =
+ new Creator<Credential>() {
+ @Override
+ public Credential createFromParcel(Parcel in) {
+ Credential credential = new Credential();
+ credential.setCreationTimeInMillis(in.readLong());
+ credential.setExpirationTimeInMillis(in.readLong());
+ credential.setRealm(in.readString());
+ credential.setCheckAaaServerCertStatus(in.readInt() != 0);
+ credential.setUserCredential(in.readParcelable(null));
+ credential.setCertCredential(in.readParcelable(null));
+ credential.setSimCredential(in.readParcelable(null));
+ credential.setCaCertificates(ParcelUtil.readCertificates(in));
+ credential.setClientCertificateChain(ParcelUtil.readCertificates(in));
+ credential.setClientPrivateKey(ParcelUtil.readPrivateKey(in));
+ return credential;
+ }
+
+ @Override
+ public Credential[] newArray(int size) {
+ return new Credential[size];
+ }
+ };
+
+ /**
+ * Verify user credential.
+ *
+ * @param isR1 {@code true} if credential is for R1
+ * @return true if user credential is valid, false otherwise.
+ */
+ private boolean verifyUserCredential(boolean isR1) {
+ if (mUserCredential == null) {
+ Log.d(TAG, "Missing user credential");
+ return false;
+ }
+ if (mCertCredential != null || mSimCredential != null) {
+ Log.d(TAG, "Contained more than one type of credential");
+ return false;
+ }
+ if (!mUserCredential.validate()) {
+ return false;
+ }
+
+ // CA certificate is required for R1 Passpoint profile.
+ // For R2, it is downloaded using cert URL provided in PPS MO after validation completes.
+ if (isR1 && mCaCertificates == null) {
+ Log.d(TAG, "Missing CA Certificate for user credential");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Verify certificate credential, which is used for EAP-TLS. This will verify
+ * that the necessary client key and certificates are provided.
+ *
+ * @param isR1 {@code true} if credential is for R1
+ * @return true if certificate credential is valid, false otherwise.
+ */
+ private boolean verifyCertCredential(boolean isR1) {
+ if (mCertCredential == null) {
+ Log.d(TAG, "Missing certificate credential");
+ return false;
+ }
+ if (mUserCredential != null || mSimCredential != null) {
+ Log.d(TAG, "Contained more than one type of credential");
+ return false;
+ }
+
+ if (!mCertCredential.validate()) {
+ return false;
+ }
+
+ // Verify required key and certificates for certificate credential.
+ // CA certificate is required for R1 Passpoint profile.
+ // For R2, it is downloaded using cert URL provided in PPS MO after validation completes.
+ if (isR1 && mCaCertificates == null) {
+ Log.d(TAG, "Missing CA Certificate for certificate credential");
+ return false;
+ }
+ if (mClientPrivateKey == null) {
+ Log.d(TAG, "Missing client private key for certificate credential");
+ return false;
+ }
+ try {
+ // Verify SHA-256 fingerprint for client certificate.
+ if (!verifySha256Fingerprint(mClientCertificateChain,
+ mCertCredential.getCertSha256Fingerprint())) {
+ Log.d(TAG, "SHA-256 fingerprint mismatch");
+ return false;
+ }
+ } catch (NoSuchAlgorithmException | CertificateEncodingException e) {
+ Log.d(TAG, "Failed to verify SHA-256 fingerprint: " + e.getMessage());
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Verify SIM credential.
+ *
+ * @return true if SIM credential is valid, false otherwise.
+ */
+ private boolean verifySimCredential() {
+ if (mSimCredential == null) {
+ Log.d(TAG, "Missing SIM credential");
+ return false;
+ }
+ if (mUserCredential != null || mCertCredential != null) {
+ Log.d(TAG, "Contained more than one type of credential");
+ return false;
+ }
+ return mSimCredential.validate();
+ }
+
+ private static boolean isPrivateKeyEquals(PrivateKey key1, PrivateKey key2) {
+ if (key1 == null && key2 == null) {
+ return true;
+ }
+
+ /* Return false if only one of them is null */
+ if (key1 == null || key2 == null) {
+ return false;
+ }
+
+ return TextUtils.equals(key1.getAlgorithm(), key2.getAlgorithm()) &&
+ Arrays.equals(key1.getEncoded(), key2.getEncoded());
+ }
+
+ /**
+ * Verify two X.509 certificates are identical.
+ *
+ * @param cert1 a certificate to compare
+ * @param cert2 a certificate to compare
+ * @return {@code true} if given certificates are the same each other, {@code false} otherwise.
+ * @hide
+ */
+ public static boolean isX509CertificateEquals(X509Certificate cert1, X509Certificate cert2) {
+ if (cert1 == null && cert2 == null) {
+ return true;
+ }
+
+ /* Return false if only one of them is null */
+ if (cert1 == null || cert2 == null) {
+ return false;
+ }
+
+ boolean result = false;
+ try {
+ result = Arrays.equals(cert1.getEncoded(), cert2.getEncoded());
+ } catch (CertificateEncodingException e) {
+ /* empty, return false. */
+ }
+ return result;
+ }
+
+ private static boolean isX509CertificatesEquals(X509Certificate[] certs1,
+ X509Certificate[] certs2) {
+ if (certs1 == null && certs2 == null) {
+ return true;
+ }
+
+ /* Return false if only one of them is null */
+ if (certs1 == null || certs2 == null) {
+ return false;
+ }
+
+ if (certs1.length != certs2.length) {
+ return false;
+ }
+
+ for (int i = 0; i < certs1.length; i++) {
+ if (!isX509CertificateEquals(certs1[i], certs2[i])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Verify that the digest for a certificate in the certificate chain matches expected
+ * fingerprint. The certificate that matches the fingerprint is the client certificate.
+ *
+ * @param certChain Chain of certificates
+ * @param expectedFingerprint The expected SHA-256 digest of the client certificate
+ * @return true if the certificate chain contains a matching certificate, false otherwise
+ * @throws NoSuchAlgorithmException
+ * @throws CertificateEncodingException
+ */
+ private static boolean verifySha256Fingerprint(X509Certificate[] certChain,
+ byte[] expectedFingerprint)
+ throws NoSuchAlgorithmException, CertificateEncodingException {
+ if (certChain == null) {
+ return false;
+ }
+ MessageDigest digester = MessageDigest.getInstance("SHA-256");
+ for (X509Certificate certificate : certChain) {
+ digester.reset();
+ byte[] fingerprint = digester.digest(certificate.getEncoded());
+ if (Arrays.equals(expectedFingerprint, fingerprint)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/android/net/wifi/hotspot2/pps/HomeSp.java b/android/net/wifi/hotspot2/pps/HomeSp.java
new file mode 100644
index 0000000..49a76c3
--- /dev/null
+++ b/android/net/wifi/hotspot2/pps/HomeSp.java
@@ -0,0 +1,415 @@
+/**
+ * Copyright (c) 2016, 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.hotspot2.pps;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Class representing HomeSP subtree in PerProviderSubscription (PPS)
+ * Management Object (MO) tree.
+ *
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ */
+public final class HomeSp implements Parcelable {
+ private static final String TAG = "HomeSp";
+
+ /**
+ * Maximum number of bytes allowed for a SSID.
+ */
+ private static final int MAX_SSID_BYTES = 32;
+
+ /**
+ * Integer value used for indicating null value in the Parcel.
+ */
+ private static final int NULL_VALUE = -1;
+
+ /**
+ * FQDN (Fully Qualified Domain Name) of this home service provider.
+ */
+ private String mFqdn = null;
+ /**
+ * Set the FQDN (Fully Qualified Domain Name) associated with this home service provider.
+ *
+ * @param fqdn The FQDN to set to
+ */
+ public void setFqdn(String fqdn) {
+ mFqdn = fqdn;
+ }
+ /**
+ * Get the FQDN (Fully Qualified Domain Name) associated with this home service provider.
+ *
+ * @return the FQDN associated with this home service provider
+ */
+ public String getFqdn() {
+ return mFqdn;
+ }
+
+ /**
+ * Friendly name of this home service provider.
+ */
+ private String mFriendlyName = null;
+ /**
+ * Set the friendly name associated with this home service provider.
+ *
+ * @param friendlyName The friendly name to set to
+ */
+ public void setFriendlyName(String friendlyName) {
+ mFriendlyName = friendlyName;
+ }
+ /**
+ * Get the friendly name associated with this home service provider.
+ *
+ * @return the friendly name associated with this home service provider
+ */
+ public String getFriendlyName() {
+ return mFriendlyName;
+ }
+
+ /**
+ * Icon URL of this home service provider.
+ */
+ private String mIconUrl = null;
+ /**
+ * @hide
+ */
+ public void setIconUrl(String iconUrl) {
+ mIconUrl = iconUrl;
+ }
+ /**
+ * @hide
+ */
+ public String getIconUrl() {
+ return mIconUrl;
+ }
+
+ /**
+ * <SSID, HESSID> duple of the networks that are consider home networks.
+ *
+ * According to the Section 9.1.2 of the Hotspot 2.0 Release 2 Technical Specification,
+ * all nodes in the PSS MO are encoded using UTF-8 unless stated otherwise. Thus, the SSID
+ * string is assumed to be encoded using UTF-8.
+ */
+ private Map<String, Long> mHomeNetworkIds = null;
+ /**
+ * @hide
+ */
+ public void setHomeNetworkIds(Map<String, Long> homeNetworkIds) {
+ mHomeNetworkIds = homeNetworkIds;
+ }
+ /**
+ * @hide
+ */
+ public Map<String, Long> getHomeNetworkIds() {
+ return mHomeNetworkIds;
+ }
+
+ /**
+ * Used for determining if this provider is a member of a given Hotspot provider.
+ * Every Organization Identifiers (OIs) in this list are required to match an OI in the
+ * the Roaming Consortium advertised by a Hotspot, in order to consider this provider
+ * as a member of that Hotspot provider (e.g. successful authentication with such Hotspot
+ * is possible).
+ *
+ * Refer to HomeSP/HomeOIList subtree in PerProviderSubscription (PPS) Management Object
+ * (MO) tree for more detail.
+ */
+ private long[] mMatchAllOis = null;
+ /**
+ * @hide
+ */
+ public void setMatchAllOis(long[] matchAllOis) {
+ mMatchAllOis = matchAllOis;
+ }
+ /**
+ * @hide
+ */
+ public long[] getMatchAllOis() {
+ return mMatchAllOis;
+ }
+
+ /**
+ * Used for determining if this provider is a member of a given Hotspot provider.
+ * Matching of any Organization Identifiers (OIs) in this list with an OI in the
+ * Roaming Consortium advertised by a Hotspot, will consider this provider as a member
+ * of that Hotspot provider (e.g. successful authentication with such Hotspot
+ * is possible).
+ *
+ * {@link #mMatchAllOIs} will have precedence over this one, meaning this list will
+ * only be used for matching if {@link #mMatchAllOIs} is null or empty.
+ *
+ * Refer to HomeSP/HomeOIList subtree in PerProviderSubscription (PPS) Management Object
+ * (MO) tree for more detail.
+ */
+ private long[] mMatchAnyOis = null;
+ /**
+ * @hide
+ */
+ public void setMatchAnyOis(long[] matchAnyOis) {
+ mMatchAnyOis = matchAnyOis;
+ }
+ /**
+ * @hide
+ */
+ public long[] getMatchAnyOis() {
+ return mMatchAnyOis;
+ }
+
+ /**
+ * List of FQDN (Fully Qualified Domain Name) of partner providers.
+ * These providers should also be regarded as home Hotspot operators.
+ * This relationship is most likely achieved via a commercial agreement or
+ * operator merges between the providers.
+ */
+ private String[] mOtherHomePartners = null;
+ /**
+ * @hide
+ */
+ public void setOtherHomePartners(String[] otherHomePartners) {
+ mOtherHomePartners = otherHomePartners;
+ }
+ /**
+ * @hide
+ */
+ public String[] getOtherHomePartners() {
+ return mOtherHomePartners;
+ }
+
+ /**
+ * List of Organization Identifiers (OIs) identifying a roaming consortium of
+ * which this provider is a member.
+ */
+ private long[] mRoamingConsortiumOis = null;
+ /**
+ * Set the Organization Identifiers (OIs) identifying a roaming consortium of which this
+ * provider is a member.
+ *
+ * @param roamingConsortiumOis Array of roaming consortium OIs
+ */
+ public void setRoamingConsortiumOis(long[] roamingConsortiumOis) {
+ mRoamingConsortiumOis = roamingConsortiumOis;
+ }
+ /**
+ * Get the Organization Identifiers (OIs) identifying a roaming consortium of which this
+ * provider is a member.
+ *
+ * @return array of roaming consortium OIs
+ */
+ public long[] getRoamingConsortiumOis() {
+ return mRoamingConsortiumOis;
+ }
+
+ /**
+ * Constructor for creating HomeSp with default values.
+ */
+ public HomeSp() {}
+
+ /**
+ * Copy constructor.
+ *
+ * @param source The source to copy from
+ */
+ public HomeSp(HomeSp source) {
+ if (source == null) {
+ return;
+ }
+ mFqdn = source.mFqdn;
+ mFriendlyName = source.mFriendlyName;
+ mIconUrl = source.mIconUrl;
+ if (source.mHomeNetworkIds != null) {
+ mHomeNetworkIds = Collections.unmodifiableMap(source.mHomeNetworkIds);
+ }
+ if (source.mMatchAllOis != null) {
+ mMatchAllOis = Arrays.copyOf(source.mMatchAllOis, source.mMatchAllOis.length);
+ }
+ if (source.mMatchAnyOis != null) {
+ mMatchAnyOis = Arrays.copyOf(source.mMatchAnyOis, source.mMatchAnyOis.length);
+ }
+ if (source.mOtherHomePartners != null) {
+ mOtherHomePartners = Arrays.copyOf(source.mOtherHomePartners,
+ source.mOtherHomePartners.length);
+ }
+ if (source.mRoamingConsortiumOis != null) {
+ mRoamingConsortiumOis = Arrays.copyOf(source.mRoamingConsortiumOis,
+ source.mRoamingConsortiumOis.length);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mFqdn);
+ dest.writeString(mFriendlyName);
+ dest.writeString(mIconUrl);
+ writeHomeNetworkIds(dest, mHomeNetworkIds);
+ dest.writeLongArray(mMatchAllOis);
+ dest.writeLongArray(mMatchAnyOis);
+ dest.writeStringArray(mOtherHomePartners);
+ dest.writeLongArray(mRoamingConsortiumOis);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof HomeSp)) {
+ return false;
+ }
+ HomeSp that = (HomeSp) thatObject;
+
+ return TextUtils.equals(mFqdn, that.mFqdn)
+ && TextUtils.equals(mFriendlyName, that.mFriendlyName)
+ && TextUtils.equals(mIconUrl, that.mIconUrl)
+ && (mHomeNetworkIds == null ? that.mHomeNetworkIds == null
+ : mHomeNetworkIds.equals(that.mHomeNetworkIds))
+ && Arrays.equals(mMatchAllOis, that.mMatchAllOis)
+ && Arrays.equals(mMatchAnyOis, that.mMatchAnyOis)
+ && Arrays.equals(mOtherHomePartners, that.mOtherHomePartners)
+ && Arrays.equals(mRoamingConsortiumOis, that.mRoamingConsortiumOis);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mFqdn, mFriendlyName, mIconUrl, mHomeNetworkIds, mMatchAllOis,
+ mMatchAnyOis, mOtherHomePartners, mRoamingConsortiumOis);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("FQDN: ").append(mFqdn).append("\n");
+ builder.append("FriendlyName: ").append(mFriendlyName).append("\n");
+ builder.append("IconURL: ").append(mIconUrl).append("\n");
+ builder.append("HomeNetworkIDs: ").append(mHomeNetworkIds).append("\n");
+ builder.append("MatchAllOIs: ").append(mMatchAllOis).append("\n");
+ builder.append("MatchAnyOIs: ").append(mMatchAnyOis).append("\n");
+ builder.append("OtherHomePartners: ").append(mOtherHomePartners).append("\n");
+ builder.append("RoamingConsortiumOIs: ").append(mRoamingConsortiumOis).append("\n");
+ return builder.toString();
+ }
+
+ /**
+ * Validate HomeSp data.
+ *
+ * @return true on success or false on failure
+ * @hide
+ */
+ public boolean validate() {
+ if (TextUtils.isEmpty(mFqdn)) {
+ Log.d(TAG, "Missing FQDN");
+ return false;
+ }
+ if (TextUtils.isEmpty(mFriendlyName)) {
+ Log.d(TAG, "Missing friendly name");
+ return false;
+ }
+ // Verify SSIDs specified in the NetworkID
+ if (mHomeNetworkIds != null) {
+ for (Map.Entry<String, Long> entry : mHomeNetworkIds.entrySet()) {
+ if (entry.getKey() == null ||
+ entry.getKey().getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) {
+ Log.d(TAG, "Invalid SSID in HomeNetworkIDs");
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ public static final @android.annotation.NonNull Creator<HomeSp> CREATOR =
+ new Creator<HomeSp>() {
+ @Override
+ public HomeSp createFromParcel(Parcel in) {
+ HomeSp homeSp = new HomeSp();
+ homeSp.setFqdn(in.readString());
+ homeSp.setFriendlyName(in.readString());
+ homeSp.setIconUrl(in.readString());
+ homeSp.setHomeNetworkIds(readHomeNetworkIds(in));
+ homeSp.setMatchAllOis(in.createLongArray());
+ homeSp.setMatchAnyOis(in.createLongArray());
+ homeSp.setOtherHomePartners(in.createStringArray());
+ homeSp.setRoamingConsortiumOis(in.createLongArray());
+ return homeSp;
+ }
+
+ @Override
+ public HomeSp[] newArray(int size) {
+ return new HomeSp[size];
+ }
+
+ /**
+ * Helper function for reading a Home Network IDs map from a Parcel.
+ *
+ * @param in The Parcel to read from
+ * @return Map of home network IDs
+ */
+ private Map<String, Long> readHomeNetworkIds(Parcel in) {
+ int size = in.readInt();
+ if (size == NULL_VALUE) {
+ return null;
+ }
+ Map<String, Long> networkIds = new HashMap<>(size);
+ for (int i = 0; i < size; i++) {
+ String key = in.readString();
+ Long value = null;
+ long readValue = in.readLong();
+ if (readValue != NULL_VALUE) {
+ value = Long.valueOf(readValue);
+ }
+ networkIds.put(key, value);
+ }
+ return networkIds;
+ }
+ };
+
+ /**
+ * Helper function for writing Home Network IDs map to a Parcel.
+ *
+ * @param dest The Parcel to write to
+ * @param networkIds The map of home network IDs
+ */
+ private static void writeHomeNetworkIds(Parcel dest, Map<String, Long> networkIds) {
+ if (networkIds == null) {
+ dest.writeInt(NULL_VALUE);
+ return;
+ }
+ dest.writeInt(networkIds.size());
+ for (Map.Entry<String, Long> entry : networkIds.entrySet()) {
+ dest.writeString(entry.getKey());
+ if (entry.getValue() == null) {
+ dest.writeLong(NULL_VALUE);
+ } else {
+ dest.writeLong(entry.getValue());
+ }
+ }
+ }
+}
diff --git a/android/net/wifi/hotspot2/pps/Policy.java b/android/net/wifi/hotspot2/pps/Policy.java
new file mode 100644
index 0000000..b0a2cc3
--- /dev/null
+++ b/android/net/wifi/hotspot2/pps/Policy.java
@@ -0,0 +1,576 @@
+/**
+ * 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 android.net.wifi.hotspot2.pps;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Class representing Policy subtree in PerProviderSubscription (PPS)
+ * Management Object (MO) tree.
+ *
+ * The Policy specifies additional criteria for Passpoint network selections, such as preferred
+ * roaming partner, minimum backhaul bandwidth, and etc. It also provides the meta data for
+ * updating the policy.
+ *
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ *
+ * @hide
+ */
+public final class Policy implements Parcelable {
+ private static final String TAG = "Policy";
+
+ /**
+ * Maximum number of SSIDs in the exclusion list.
+ */
+ private static final int MAX_EXCLUSION_SSIDS = 128;
+
+ /**
+ * Maximum byte for SSID.
+ */
+ private static final int MAX_SSID_BYTES = 32;
+
+ /**
+ * Maximum bytes for port string in {@link #requiredProtoPortMap}.
+ */
+ private static final int MAX_PORT_STRING_BYTES = 64;
+
+ /**
+ * Integer value used for indicating null value in the Parcel.
+ */
+ private static final int NULL_VALUE = -1;
+
+ /**
+ * Minimum available downlink/uplink bandwidth (in kilobits per second) required when
+ * selecting a network from home providers.
+ *
+ * The bandwidth is calculated as the LinkSpeed * (1 – LinkLoad/255), where LinkSpeed
+ * and LinkLoad parameters are drawn from the WAN Metrics ANQP element at that hotspot.
+ *
+ * Using Long.MIN_VALUE to indicate unset value.
+ */
+ private long mMinHomeDownlinkBandwidth = Long.MIN_VALUE;
+ public void setMinHomeDownlinkBandwidth(long minHomeDownlinkBandwidth) {
+ mMinHomeDownlinkBandwidth = minHomeDownlinkBandwidth;
+ }
+ public long getMinHomeDownlinkBandwidth() {
+ return mMinHomeDownlinkBandwidth;
+ }
+ private long mMinHomeUplinkBandwidth = Long.MIN_VALUE;
+ public void setMinHomeUplinkBandwidth(long minHomeUplinkBandwidth) {
+ mMinHomeUplinkBandwidth = minHomeUplinkBandwidth;
+ }
+ public long getMinHomeUplinkBandwidth() {
+ return mMinHomeUplinkBandwidth;
+ }
+
+ /**
+ * Minimum available downlink/uplink bandwidth (in kilobits per second) required when
+ * selecting a network from roaming providers.
+ *
+ * The bandwidth is calculated as the LinkSpeed * (1 – LinkLoad/255), where LinkSpeed
+ * and LinkLoad parameters are drawn from the WAN Metrics ANQP element at that hotspot.
+ *
+ * Using Long.MIN_VALUE to indicate unset value.
+ */
+ private long mMinRoamingDownlinkBandwidth = Long.MIN_VALUE;
+ public void setMinRoamingDownlinkBandwidth(long minRoamingDownlinkBandwidth) {
+ mMinRoamingDownlinkBandwidth = minRoamingDownlinkBandwidth;
+ }
+ public long getMinRoamingDownlinkBandwidth() {
+ return mMinRoamingDownlinkBandwidth;
+ }
+ private long mMinRoamingUplinkBandwidth = Long.MIN_VALUE;
+ public void setMinRoamingUplinkBandwidth(long minRoamingUplinkBandwidth) {
+ mMinRoamingUplinkBandwidth = minRoamingUplinkBandwidth;
+ }
+ public long getMinRoamingUplinkBandwidth() {
+ return mMinRoamingUplinkBandwidth;
+ }
+
+ /**
+ * List of SSIDs that are not preferred by the Home SP.
+ */
+ private String[] mExcludedSsidList = null;
+ public void setExcludedSsidList(String[] excludedSsidList) {
+ mExcludedSsidList = excludedSsidList;
+ }
+ public String[] getExcludedSsidList() {
+ return mExcludedSsidList;
+ }
+
+ /**
+ * List of IP protocol and port number required by one or more operator supported application.
+ * The port string contained one or more port numbers delimited by ",".
+ */
+ private Map<Integer, String> mRequiredProtoPortMap = null;
+ public void setRequiredProtoPortMap(Map<Integer, String> requiredProtoPortMap) {
+ mRequiredProtoPortMap = requiredProtoPortMap;
+ }
+ public Map<Integer, String> getRequiredProtoPortMap() {
+ return mRequiredProtoPortMap;
+ }
+
+ /**
+ * This specifies the maximum acceptable BSS load policy. This is used to prevent device
+ * from joining an AP whose channel is overly congested with traffic.
+ * Using Integer.MIN_VALUE to indicate unset value.
+ */
+ private int mMaximumBssLoadValue = Integer.MIN_VALUE;
+ public void setMaximumBssLoadValue(int maximumBssLoadValue) {
+ mMaximumBssLoadValue = maximumBssLoadValue;
+ }
+ public int getMaximumBssLoadValue() {
+ return mMaximumBssLoadValue;
+ }
+
+ /**
+ * Policy associated with a roaming provider. This specifies a priority associated
+ * with a roaming provider for given list of countries.
+ *
+ * Contains field under PerProviderSubscription/Policy/PreferredRoamingPartnerList.
+ */
+ public static final class RoamingPartner implements Parcelable {
+ /**
+ * FQDN of the roaming partner.
+ */
+ private String mFqdn = null;
+ public void setFqdn(String fqdn) {
+ mFqdn = fqdn;
+ }
+ public String getFqdn() {
+ return mFqdn;
+ }
+
+ /**
+ * Flag indicating the exact match of FQDN is required for FQDN matching.
+ *
+ * When this flag is set to false, sub-domain matching is used. For example, when
+ * {@link #fqdn} s set to "example.com", "host.example.com" would be a match.
+ */
+ private boolean mFqdnExactMatch = false;
+ public void setFqdnExactMatch(boolean fqdnExactMatch) {
+ mFqdnExactMatch = fqdnExactMatch;
+ }
+ public boolean getFqdnExactMatch() {
+ return mFqdnExactMatch;
+ }
+
+ /**
+ * Priority associated with this roaming partner policy.
+ * Using Integer.MIN_VALUE to indicate unset value.
+ */
+ private int mPriority = Integer.MIN_VALUE;
+ public void setPriority(int priority) {
+ mPriority = priority;
+ }
+ public int getPriority() {
+ return mPriority;
+ }
+
+ /**
+ * A string contained One or more, comma delimited (i.e., ",") ISO/IEC 3166-1 two
+ * character country strings or the country-independent value, "*".
+ */
+ private String mCountries = null;
+ public void setCountries(String countries) {
+ mCountries = countries;
+ }
+ public String getCountries() {
+ return mCountries;
+ }
+
+ public RoamingPartner() {}
+
+ public RoamingPartner(RoamingPartner source) {
+ if (source != null) {
+ mFqdn = source.mFqdn;
+ mFqdnExactMatch = source.mFqdnExactMatch;
+ mPriority = source.mPriority;
+ mCountries = source.mCountries;
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mFqdn);
+ dest.writeInt(mFqdnExactMatch ? 1 : 0);
+ dest.writeInt(mPriority);
+ dest.writeString(mCountries);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof RoamingPartner)) {
+ return false;
+ }
+
+ RoamingPartner that = (RoamingPartner) thatObject;
+ return TextUtils.equals(mFqdn, that.mFqdn)
+ && mFqdnExactMatch == that.mFqdnExactMatch
+ && mPriority == that.mPriority
+ && TextUtils.equals(mCountries, that.mCountries);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mFqdn, mFqdnExactMatch, mPriority, mCountries);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("FQDN: ").append(mFqdn).append("\n");
+ builder.append("ExactMatch: ").append("mFqdnExactMatch").append("\n");
+ builder.append("Priority: ").append(mPriority).append("\n");
+ builder.append("Countries: ").append(mCountries).append("\n");
+ return builder.toString();
+ }
+
+ /**
+ * Validate RoamingParnter data.
+ *
+ * @return true on success
+ * @hide
+ */
+ public boolean validate() {
+ if (TextUtils.isEmpty(mFqdn)) {
+ Log.d(TAG, "Missing FQDN");
+ return false;
+ }
+ if (TextUtils.isEmpty(mCountries)) {
+ Log.d(TAG, "Missing countries");
+ return false;
+ }
+ return true;
+ }
+
+ public static final @android.annotation.NonNull Creator<RoamingPartner> CREATOR =
+ new Creator<RoamingPartner>() {
+ @Override
+ public RoamingPartner createFromParcel(Parcel in) {
+ RoamingPartner roamingPartner = new RoamingPartner();
+ roamingPartner.setFqdn(in.readString());
+ roamingPartner.setFqdnExactMatch(in.readInt() != 0);
+ roamingPartner.setPriority(in.readInt());
+ roamingPartner.setCountries(in.readString());
+ return roamingPartner;
+ }
+
+ @Override
+ public RoamingPartner[] newArray(int size) {
+ return new RoamingPartner[size];
+ }
+ };
+ }
+ private List<RoamingPartner> mPreferredRoamingPartnerList = null;
+ public void setPreferredRoamingPartnerList(List<RoamingPartner> partnerList) {
+ mPreferredRoamingPartnerList = partnerList;
+ }
+ public List<RoamingPartner> getPreferredRoamingPartnerList() {
+ return mPreferredRoamingPartnerList;
+ }
+
+ /**
+ * Meta data used for policy update.
+ */
+ private UpdateParameter mPolicyUpdate = null;
+ public void setPolicyUpdate(UpdateParameter policyUpdate) {
+ mPolicyUpdate = policyUpdate;
+ }
+ public UpdateParameter getPolicyUpdate() {
+ return mPolicyUpdate;
+ }
+
+ /**
+ * Constructor for creating Policy with default values.
+ */
+ public Policy() {}
+
+ /**
+ * Copy constructor.
+ *
+ * @param source The source to copy from
+ */
+ public Policy(Policy source) {
+ if (source == null) {
+ return;
+ }
+ mMinHomeDownlinkBandwidth = source.mMinHomeDownlinkBandwidth;
+ mMinHomeUplinkBandwidth = source.mMinHomeUplinkBandwidth;
+ mMinRoamingDownlinkBandwidth = source.mMinRoamingDownlinkBandwidth;
+ mMinRoamingUplinkBandwidth = source.mMinRoamingUplinkBandwidth;
+ mMaximumBssLoadValue = source.mMaximumBssLoadValue;
+ if (source.mExcludedSsidList != null) {
+ mExcludedSsidList = Arrays.copyOf(source.mExcludedSsidList,
+ source.mExcludedSsidList.length);
+ }
+ if (source.mRequiredProtoPortMap != null) {
+ mRequiredProtoPortMap = Collections.unmodifiableMap(source.mRequiredProtoPortMap);
+ }
+ if (source.mPreferredRoamingPartnerList != null) {
+ mPreferredRoamingPartnerList = Collections.unmodifiableList(
+ source.mPreferredRoamingPartnerList);
+ }
+ if (source.mPolicyUpdate != null) {
+ mPolicyUpdate = new UpdateParameter(source.mPolicyUpdate);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mMinHomeDownlinkBandwidth);
+ dest.writeLong(mMinHomeUplinkBandwidth);
+ dest.writeLong(mMinRoamingDownlinkBandwidth);
+ dest.writeLong(mMinRoamingUplinkBandwidth);
+ dest.writeStringArray(mExcludedSsidList);
+ writeProtoPortMap(dest, mRequiredProtoPortMap);
+ dest.writeInt(mMaximumBssLoadValue);
+ writeRoamingPartnerList(dest, flags, mPreferredRoamingPartnerList);
+ dest.writeParcelable(mPolicyUpdate, flags);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof Policy)) {
+ return false;
+ }
+ Policy that = (Policy) thatObject;
+
+ return mMinHomeDownlinkBandwidth == that.mMinHomeDownlinkBandwidth
+ && mMinHomeUplinkBandwidth == that.mMinHomeUplinkBandwidth
+ && mMinRoamingDownlinkBandwidth == that.mMinRoamingDownlinkBandwidth
+ && mMinRoamingUplinkBandwidth == that.mMinRoamingUplinkBandwidth
+ && Arrays.equals(mExcludedSsidList, that.mExcludedSsidList)
+ && (mRequiredProtoPortMap == null ? that.mRequiredProtoPortMap == null
+ : mRequiredProtoPortMap.equals(that.mRequiredProtoPortMap))
+ && mMaximumBssLoadValue == that.mMaximumBssLoadValue
+ && (mPreferredRoamingPartnerList == null
+ ? that.mPreferredRoamingPartnerList == null
+ : mPreferredRoamingPartnerList.equals(that.mPreferredRoamingPartnerList))
+ && (mPolicyUpdate == null ? that.mPolicyUpdate == null
+ : mPolicyUpdate.equals(that.mPolicyUpdate));
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mMinHomeDownlinkBandwidth, mMinHomeUplinkBandwidth,
+ mMinRoamingDownlinkBandwidth, mMinRoamingUplinkBandwidth, mExcludedSsidList,
+ mRequiredProtoPortMap, mMaximumBssLoadValue, mPreferredRoamingPartnerList,
+ mPolicyUpdate);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("MinHomeDownlinkBandwidth: ").append(mMinHomeDownlinkBandwidth)
+ .append("\n");
+ builder.append("MinHomeUplinkBandwidth: ").append(mMinHomeUplinkBandwidth).append("\n");
+ builder.append("MinRoamingDownlinkBandwidth: ").append(mMinRoamingDownlinkBandwidth)
+ .append("\n");
+ builder.append("MinRoamingUplinkBandwidth: ").append(mMinRoamingUplinkBandwidth)
+ .append("\n");
+ builder.append("ExcludedSSIDList: ").append(mExcludedSsidList).append("\n");
+ builder.append("RequiredProtoPortMap: ").append(mRequiredProtoPortMap).append("\n");
+ builder.append("MaximumBSSLoadValue: ").append(mMaximumBssLoadValue).append("\n");
+ builder.append("PreferredRoamingPartnerList: ").append(mPreferredRoamingPartnerList)
+ .append("\n");
+ if (mPolicyUpdate != null) {
+ builder.append("PolicyUpdate Begin ---\n");
+ builder.append(mPolicyUpdate);
+ builder.append("PolicyUpdate End ---\n");
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Validate Policy data.
+ *
+ * @return true on success
+ * @hide
+ */
+ public boolean validate() {
+ if (mPolicyUpdate == null) {
+ Log.d(TAG, "PolicyUpdate not specified");
+ return false;
+ }
+ if (!mPolicyUpdate.validate()) {
+ return false;
+ }
+
+ // Validate SSID exclusion list.
+ if (mExcludedSsidList != null) {
+ if (mExcludedSsidList.length > MAX_EXCLUSION_SSIDS) {
+ Log.d(TAG, "SSID exclusion list size exceeded the max: "
+ + mExcludedSsidList.length);
+ return false;
+ }
+ for (String ssid : mExcludedSsidList) {
+ if (ssid.getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) {
+ Log.d(TAG, "Invalid SSID: " + ssid);
+ return false;
+ }
+ }
+ }
+ // Validate required protocol to port map.
+ if (mRequiredProtoPortMap != null) {
+ for (Map.Entry<Integer, String> entry : mRequiredProtoPortMap.entrySet()) {
+ String portNumber = entry.getValue();
+ if (portNumber.getBytes(StandardCharsets.UTF_8).length > MAX_PORT_STRING_BYTES) {
+ Log.d(TAG, "PortNumber string bytes exceeded the max: " + portNumber);
+ return false;
+ }
+ }
+ }
+ // Validate preferred roaming partner list.
+ if (mPreferredRoamingPartnerList != null) {
+ for (RoamingPartner partner : mPreferredRoamingPartnerList) {
+ if (!partner.validate()) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ public static final @android.annotation.NonNull Creator<Policy> CREATOR =
+ new Creator<Policy>() {
+ @Override
+ public Policy createFromParcel(Parcel in) {
+ Policy policy = new Policy();
+ policy.setMinHomeDownlinkBandwidth(in.readLong());
+ policy.setMinHomeUplinkBandwidth(in.readLong());
+ policy.setMinRoamingDownlinkBandwidth(in.readLong());
+ policy.setMinRoamingUplinkBandwidth(in.readLong());
+ policy.setExcludedSsidList(in.createStringArray());
+ policy.setRequiredProtoPortMap(readProtoPortMap(in));
+ policy.setMaximumBssLoadValue(in.readInt());
+ policy.setPreferredRoamingPartnerList(readRoamingPartnerList(in));
+ policy.setPolicyUpdate(in.readParcelable(null));
+ return policy;
+ }
+
+ @Override
+ public Policy[] newArray(int size) {
+ return new Policy[size];
+ }
+
+ /**
+ * Helper function for reading IP Protocol to Port Number map from a Parcel.
+ *
+ * @param in The Parcel to read from
+ * @return Map of IP protocol to port number
+ */
+ private Map<Integer, String> readProtoPortMap(Parcel in) {
+ int size = in.readInt();
+ if (size == NULL_VALUE) {
+ return null;
+ }
+ Map<Integer, String> protoPortMap = new HashMap<>(size);
+ for (int i = 0; i < size; i++) {
+ int key = in.readInt();
+ String value = in.readString();
+ protoPortMap.put(key, value);
+ }
+ return protoPortMap;
+ }
+
+ /**
+ * Helper function for reading roaming partner list from a Parcel.
+ *
+ * @param in The Parcel to read from
+ * @return List of roaming partners
+ */
+ private List<RoamingPartner> readRoamingPartnerList(Parcel in) {
+ int size = in.readInt();
+ if (size == NULL_VALUE) {
+ return null;
+ }
+ List<RoamingPartner> partnerList = new ArrayList<>();
+ for (int i = 0; i < size; i++) {
+ partnerList.add(in.readParcelable(null));
+ }
+ return partnerList;
+ }
+
+ };
+
+ /**
+ * Helper function for writing IP Protocol to Port Number map to a Parcel.
+ *
+ * @param dest The Parcel to write to
+ * @param protoPortMap The map to write
+ */
+ private static void writeProtoPortMap(Parcel dest, Map<Integer, String> protoPortMap) {
+ if (protoPortMap == null) {
+ dest.writeInt(NULL_VALUE);
+ return;
+ }
+ dest.writeInt(protoPortMap.size());
+ for (Map.Entry<Integer, String> entry : protoPortMap.entrySet()) {
+ dest.writeInt(entry.getKey());
+ dest.writeString(entry.getValue());
+ }
+ }
+
+ /**
+ * Helper function for writing roaming partner list to a Parcel.
+ *
+ * @param dest The Parcel to write to
+ * @param flags The flag about how the object should be written
+ * @param partnerList The partner list to write
+ */
+ private static void writeRoamingPartnerList(Parcel dest, int flags,
+ List<RoamingPartner> partnerList) {
+ if (partnerList == null) {
+ dest.writeInt(NULL_VALUE);
+ return;
+ }
+ dest.writeInt(partnerList.size());
+ for (RoamingPartner partner : partnerList) {
+ dest.writeParcelable(partner, flags);
+ }
+ }
+}
diff --git a/android/net/wifi/hotspot2/pps/UpdateParameter.java b/android/net/wifi/hotspot2/pps/UpdateParameter.java
new file mode 100644
index 0000000..4a8aa36
--- /dev/null
+++ b/android/net/wifi/hotspot2/pps/UpdateParameter.java
@@ -0,0 +1,403 @@
+/**
+ * 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 android.net.wifi.hotspot2.pps;
+
+import android.net.wifi.ParcelUtil;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import java.nio.charset.StandardCharsets;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Class representing configuration parameters for subscription or policy update in
+ * PerProviderSubscription (PPS) Management Object (MO) tree. This is used by both
+ * PerProviderSubscription/Policy/PolicyUpdate and PerProviderSubscription/SubscriptionUpdate
+ * subtree.
+ *
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ *
+ * @hide
+ */
+public final class UpdateParameter implements Parcelable {
+ private static final String TAG = "UpdateParameter";
+
+ /**
+ * Value indicating policy update is not applicable. Thus, never check with policy server
+ * for updates.
+ */
+ public static final long UPDATE_CHECK_INTERVAL_NEVER = 0xFFFFFFFFL;
+
+ /**
+ * Valid string for UpdateMethod.
+ */
+ public static final String UPDATE_METHOD_OMADM = "OMA-DM-ClientInitiated";
+ public static final String UPDATE_METHOD_SSP = "SSP-ClientInitiated";
+
+ /**
+ * Valid string for Restriction.
+ */
+ public static final String UPDATE_RESTRICTION_HOMESP = "HomeSP";
+ public static final String UPDATE_RESTRICTION_ROAMING_PARTNER = "RoamingPartner";
+ public static final String UPDATE_RESTRICTION_UNRESTRICTED = "Unrestricted";
+
+ /**
+ * Maximum bytes for URI string.
+ */
+ private static final int MAX_URI_BYTES = 1023;
+
+ /**
+ * Maximum bytes for URI string.
+ */
+ private static final int MAX_URL_BYTES = 1023;
+
+ /**
+ * Maximum bytes for username.
+ */
+ private static final int MAX_USERNAME_BYTES = 63;
+
+ /**
+ * Maximum bytes for password.
+ */
+ private static final int MAX_PASSWORD_BYTES = 255;
+
+ /**
+ * Number of bytes for certificate SHA-256 fingerprint byte array.
+ */
+ private static final int CERTIFICATE_SHA256_BYTES = 32;
+
+ /**
+ * This specifies how often the mobile device shall check with policy server for updates.
+ *
+ * Using Long.MIN_VALUE to indicate unset value.
+ */
+ private long mUpdateIntervalInMinutes = Long.MIN_VALUE;
+ public void setUpdateIntervalInMinutes(long updateIntervalInMinutes) {
+ mUpdateIntervalInMinutes = updateIntervalInMinutes;
+ }
+ public long getUpdateIntervalInMinutes() {
+ return mUpdateIntervalInMinutes;
+ }
+
+ /**
+ * The method used to update the policy. Permitted values are "OMA-DM-ClientInitiated"
+ * and "SPP-ClientInitiated".
+ */
+ private String mUpdateMethod = null;
+ public void setUpdateMethod(String updateMethod) {
+ mUpdateMethod = updateMethod;
+ }
+ public String getUpdateMethod() {
+ return mUpdateMethod;
+ }
+
+ /**
+ * This specifies the hotspots at which the subscription update is permitted. Permitted
+ * values are "HomeSP", "RoamingPartner", or "Unrestricted";
+ */
+ private String mRestriction = null;
+ public void setRestriction(String restriction) {
+ mRestriction = restriction;
+ }
+ public String getRestriction() {
+ return mRestriction;
+ }
+
+ /**
+ * The URI of the update server.
+ */
+ private String mServerUri = null;
+ public void setServerUri(String serverUri) {
+ mServerUri = serverUri;
+ }
+ public String getServerUri() {
+ return mServerUri;
+ }
+
+ /**
+ * Username used to authenticate with the policy server.
+ */
+ private String mUsername = null;
+ public void setUsername(String username) {
+ mUsername = username;
+ }
+ public String getUsername() {
+ return mUsername;
+ }
+
+ /**
+ * Base64 encoded password used to authenticate with the policy server.
+ */
+ private String mBase64EncodedPassword = null;
+ public void setBase64EncodedPassword(String password) {
+ mBase64EncodedPassword = password;
+ }
+ public String getBase64EncodedPassword() {
+ return mBase64EncodedPassword;
+ }
+
+ /**
+ * HTTPS URL for retrieving certificate for trust root. The trust root is used to validate
+ * policy server's identity.
+ */
+ private String mTrustRootCertUrl = null;
+ public void setTrustRootCertUrl(String trustRootCertUrl) {
+ mTrustRootCertUrl = trustRootCertUrl;
+ }
+ public String getTrustRootCertUrl() {
+ return mTrustRootCertUrl;
+ }
+
+ /**
+ * SHA-256 fingerprint of the certificate located at {@code mTrustRootCertUrl}
+ */
+ private byte[] mTrustRootCertSha256Fingerprint = null;
+ public void setTrustRootCertSha256Fingerprint(byte[] fingerprint) {
+ mTrustRootCertSha256Fingerprint = fingerprint;
+ }
+ public byte[] getTrustRootCertSha256Fingerprint() {
+ return mTrustRootCertSha256Fingerprint;
+ }
+
+ /**
+ * CA (Certificate Authority) X509 certificates.
+ */
+ private X509Certificate mCaCertificate;
+
+ /**
+ * Set the CA (Certification Authority) certificate associated with Policy/Subscription update.
+ *
+ * @param caCertificate The CA certificate to set
+ * @hide
+ */
+ public void setCaCertificate(X509Certificate caCertificate) {
+ mCaCertificate = caCertificate;
+ }
+
+ /**
+ * Get the CA (Certification Authority) certificate associated with Policy/Subscription update.
+ *
+ * @return CA certificate associated and {@code null} if certificate is not set.
+ * @hide
+ */
+ public X509Certificate getCaCertificate() {
+ return mCaCertificate;
+ }
+
+ /**
+ * Constructor for creating Policy with default values.
+ */
+ public UpdateParameter() {}
+
+ /**
+ * Copy constructor.
+ *
+ * @param source The source to copy from
+ */
+ public UpdateParameter(UpdateParameter source) {
+ if (source == null) {
+ return;
+ }
+ mUpdateIntervalInMinutes = source.mUpdateIntervalInMinutes;
+ mUpdateMethod = source.mUpdateMethod;
+ mRestriction = source.mRestriction;
+ mServerUri = source.mServerUri;
+ mUsername = source.mUsername;
+ mBase64EncodedPassword = source.mBase64EncodedPassword;
+ mTrustRootCertUrl = source.mTrustRootCertUrl;
+ if (source.mTrustRootCertSha256Fingerprint != null) {
+ mTrustRootCertSha256Fingerprint = Arrays.copyOf(source.mTrustRootCertSha256Fingerprint,
+ source.mTrustRootCertSha256Fingerprint.length);
+ }
+ mCaCertificate = source.mCaCertificate;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mUpdateIntervalInMinutes);
+ dest.writeString(mUpdateMethod);
+ dest.writeString(mRestriction);
+ dest.writeString(mServerUri);
+ dest.writeString(mUsername);
+ dest.writeString(mBase64EncodedPassword);
+ dest.writeString(mTrustRootCertUrl);
+ dest.writeByteArray(mTrustRootCertSha256Fingerprint);
+ ParcelUtil.writeCertificate(dest, mCaCertificate);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof UpdateParameter)) {
+ return false;
+ }
+ UpdateParameter that = (UpdateParameter) thatObject;
+
+ return mUpdateIntervalInMinutes == that.mUpdateIntervalInMinutes
+ && TextUtils.equals(mUpdateMethod, that.mUpdateMethod)
+ && TextUtils.equals(mRestriction, that.mRestriction)
+ && TextUtils.equals(mServerUri, that.mServerUri)
+ && TextUtils.equals(mUsername, that.mUsername)
+ && TextUtils.equals(mBase64EncodedPassword, that.mBase64EncodedPassword)
+ && TextUtils.equals(mTrustRootCertUrl, that.mTrustRootCertUrl)
+ && Arrays.equals(mTrustRootCertSha256Fingerprint,
+ that.mTrustRootCertSha256Fingerprint)
+ && Credential.isX509CertificateEquals(mCaCertificate, that.mCaCertificate);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUpdateIntervalInMinutes, mUpdateMethod, mRestriction, mServerUri,
+ mUsername, mBase64EncodedPassword, mTrustRootCertUrl,
+ Arrays.hashCode(mTrustRootCertSha256Fingerprint), mCaCertificate);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("UpdateInterval: ").append(mUpdateIntervalInMinutes).append("\n");
+ builder.append("UpdateMethod: ").append(mUpdateMethod).append("\n");
+ builder.append("Restriction: ").append(mRestriction).append("\n");
+ builder.append("ServerURI: ").append(mServerUri).append("\n");
+ builder.append("Username: ").append(mUsername).append("\n");
+ builder.append("TrustRootCertURL: ").append(mTrustRootCertUrl).append("\n");
+ return builder.toString();
+ }
+
+ /**
+ * Validate UpdateParameter data.
+ *
+ * @return true on success
+ * @hide
+ */
+ public boolean validate() {
+ if (mUpdateIntervalInMinutes == Long.MIN_VALUE) {
+ Log.d(TAG, "Update interval not specified");
+ return false;
+ }
+ // Update not applicable.
+ if (mUpdateIntervalInMinutes == UPDATE_CHECK_INTERVAL_NEVER) {
+ return true;
+ }
+
+ if (!TextUtils.equals(mUpdateMethod, UPDATE_METHOD_OMADM)
+ && !TextUtils.equals(mUpdateMethod, UPDATE_METHOD_SSP)) {
+ Log.d(TAG, "Unknown update method: " + mUpdateMethod);
+ return false;
+ }
+
+ if (!TextUtils.equals(mRestriction, UPDATE_RESTRICTION_HOMESP)
+ && !TextUtils.equals(mRestriction, UPDATE_RESTRICTION_ROAMING_PARTNER)
+ && !TextUtils.equals(mRestriction, UPDATE_RESTRICTION_UNRESTRICTED)) {
+ Log.d(TAG, "Unknown restriction: " + mRestriction);
+ return false;
+ }
+
+ if (TextUtils.isEmpty(mServerUri)) {
+ Log.d(TAG, "Missing update server URI");
+ return false;
+ }
+ if (mServerUri.getBytes(StandardCharsets.UTF_8).length > MAX_URI_BYTES) {
+ Log.d(TAG, "URI bytes exceeded the max: "
+ + mServerUri.getBytes(StandardCharsets.UTF_8).length);
+ return false;
+ }
+
+ if (TextUtils.isEmpty(mUsername)) {
+ Log.d(TAG, "Missing username");
+ return false;
+ }
+ if (mUsername.getBytes(StandardCharsets.UTF_8).length > MAX_USERNAME_BYTES) {
+ Log.d(TAG, "Username bytes exceeded the max: "
+ + mUsername.getBytes(StandardCharsets.UTF_8).length);
+ return false;
+ }
+
+ if (TextUtils.isEmpty(mBase64EncodedPassword)) {
+ Log.d(TAG, "Missing username");
+ return false;
+ }
+ if (mBase64EncodedPassword.getBytes(StandardCharsets.UTF_8).length > MAX_PASSWORD_BYTES) {
+ Log.d(TAG, "Password bytes exceeded the max: "
+ + mBase64EncodedPassword.getBytes(StandardCharsets.UTF_8).length);
+ return false;
+ }
+ try {
+ Base64.decode(mBase64EncodedPassword, Base64.DEFAULT);
+ } catch (IllegalArgumentException e) {
+ Log.d(TAG, "Invalid encoding for password: " + mBase64EncodedPassword);
+ return false;
+ }
+
+ if (TextUtils.isEmpty(mTrustRootCertUrl)) {
+ Log.d(TAG, "Missing trust root certificate URL");
+ return false;
+ }
+ if (mTrustRootCertUrl.getBytes(StandardCharsets.UTF_8).length > MAX_URL_BYTES) {
+ Log.d(TAG, "Trust root cert URL bytes exceeded the max: "
+ + mTrustRootCertUrl.getBytes(StandardCharsets.UTF_8).length);
+ return false;
+ }
+
+ if (mTrustRootCertSha256Fingerprint == null) {
+ Log.d(TAG, "Missing trust root certificate SHA-256 fingerprint");
+ return false;
+ }
+ if (mTrustRootCertSha256Fingerprint.length != CERTIFICATE_SHA256_BYTES) {
+ Log.d(TAG, "Incorrect size of trust root certificate SHA-256 fingerprint: "
+ + mTrustRootCertSha256Fingerprint.length);
+ return false;
+ }
+ return true;
+ }
+
+ public static final @android.annotation.NonNull Creator<UpdateParameter> CREATOR =
+ new Creator<UpdateParameter>() {
+ @Override
+ public UpdateParameter createFromParcel(Parcel in) {
+ UpdateParameter updateParam = new UpdateParameter();
+ updateParam.setUpdateIntervalInMinutes(in.readLong());
+ updateParam.setUpdateMethod(in.readString());
+ updateParam.setRestriction(in.readString());
+ updateParam.setServerUri(in.readString());
+ updateParam.setUsername(in.readString());
+ updateParam.setBase64EncodedPassword(in.readString());
+ updateParam.setTrustRootCertUrl(in.readString());
+ updateParam.setTrustRootCertSha256Fingerprint(in.createByteArray());
+ updateParam.setCaCertificate(ParcelUtil.readCertificate(in));
+ return updateParam;
+ }
+
+ @Override
+ public UpdateParameter[] newArray(int size) {
+ return new UpdateParameter[size];
+ }
+ };
+}
diff --git a/android/net/wifi/p2p/WifiP2pConfig.java b/android/net/wifi/p2p/WifiP2pConfig.java
new file mode 100644
index 0000000..c3cfb02
--- /dev/null
+++ b/android/net/wifi/p2p/WifiP2pConfig.java
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) 2011 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.p2p;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
+import android.net.MacAddress;
+import android.net.wifi.WpsInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * A class representing a Wi-Fi P2p configuration for setting up a connection
+ *
+ * {@see WifiP2pManager}
+ */
+public class WifiP2pConfig implements Parcelable {
+
+ /**
+ * The device MAC address uniquely identifies a Wi-Fi p2p device
+ */
+ public String deviceAddress = "";
+
+ /**
+ * Wi-Fi Protected Setup information
+ */
+ public WpsInfo wps;
+
+ /**
+ * The network name of a group, should be configured by helper method
+ */
+ /** @hide */
+ public String networkName = "";
+
+ /**
+ * The passphrase of a group, should be configured by helper method
+ */
+ /** @hide */
+ public String passphrase = "";
+
+ /**
+ * The required band for Group Owner
+ */
+ /** @hide */
+ public int groupOwnerBand = GROUP_OWNER_BAND_AUTO;
+
+ /** @hide */
+ public static final int MAX_GROUP_OWNER_INTENT = 15;
+ /** @hide */
+ @UnsupportedAppUsage
+ public static final int MIN_GROUP_OWNER_INTENT = 0;
+
+ /** @hide */
+ @IntDef(flag = false, prefix = { "GROUP_OWNER_BAND_" }, value = {
+ GROUP_OWNER_BAND_AUTO,
+ GROUP_OWNER_BAND_2GHZ,
+ GROUP_OWNER_BAND_5GHZ
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface GroupOperatingBandType {}
+
+ /**
+ * Allow the system to pick the operating frequency from all supported bands.
+ */
+ public static final int GROUP_OWNER_BAND_AUTO = 0;
+ /**
+ * Allow the system to pick the operating frequency from the 2.4 GHz band.
+ */
+ public static final int GROUP_OWNER_BAND_2GHZ = 1;
+ /**
+ * Allow the system to pick the operating frequency from the 5 GHz band.
+ */
+ public static final int GROUP_OWNER_BAND_5GHZ = 2;
+
+ /**
+ * This is an integer value between 0 and 15 where 0 indicates the least
+ * inclination to be a group owner and 15 indicates the highest inclination
+ * to be a group owner.
+ *
+ * A value of -1 indicates the system can choose an appropriate value.
+ */
+ public int groupOwnerIntent = -1;
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public int netId = WifiP2pGroup.PERSISTENT_NET_ID;
+
+ public WifiP2pConfig() {
+ //set defaults
+ wps = new WpsInfo();
+ wps.setup = WpsInfo.PBC;
+ }
+
+ /** @hide */
+ public void invalidate() {
+ deviceAddress = "";
+ }
+
+ /** P2P-GO-NEG-REQUEST 42:fc:89:a8:96:09 dev_passwd_id=4 {@hide}*/
+ @UnsupportedAppUsage
+ public WifiP2pConfig(String supplicantEvent) throws IllegalArgumentException {
+ String[] tokens = supplicantEvent.split(" ");
+
+ if (tokens.length < 2 || !tokens[0].equals("P2P-GO-NEG-REQUEST")) {
+ throw new IllegalArgumentException("Malformed supplicant event");
+ }
+
+ deviceAddress = tokens[1];
+ wps = new WpsInfo();
+
+ if (tokens.length > 2) {
+ String[] nameVal = tokens[2].split("=");
+ int devPasswdId;
+ try {
+ devPasswdId = Integer.parseInt(nameVal[1]);
+ } catch (NumberFormatException e) {
+ devPasswdId = 0;
+ }
+ //Based on definitions in wps/wps_defs.h
+ switch (devPasswdId) {
+ //DEV_PW_USER_SPECIFIED = 0x0001,
+ case 0x01:
+ wps.setup = WpsInfo.DISPLAY;
+ break;
+ //DEV_PW_PUSHBUTTON = 0x0004,
+ case 0x04:
+ wps.setup = WpsInfo.PBC;
+ break;
+ //DEV_PW_REGISTRAR_SPECIFIED = 0x0005
+ case 0x05:
+ wps.setup = WpsInfo.KEYPAD;
+ break;
+ default:
+ wps.setup = WpsInfo.PBC;
+ break;
+ }
+ }
+ }
+
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append("\n address: ").append(deviceAddress);
+ sbuf.append("\n wps: ").append(wps);
+ sbuf.append("\n groupOwnerIntent: ").append(groupOwnerIntent);
+ sbuf.append("\n persist: ").append(netId);
+ sbuf.append("\n networkName: ").append(networkName);
+ sbuf.append("\n passphrase: ").append(
+ TextUtils.isEmpty(passphrase) ? "<empty>" : "<non-empty>");
+ sbuf.append("\n groupOwnerBand: ").append(groupOwnerBand);
+ return sbuf.toString();
+ }
+
+ /** Implement the Parcelable interface */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** copy constructor */
+ public WifiP2pConfig(WifiP2pConfig source) {
+ if (source != null) {
+ deviceAddress = source.deviceAddress;
+ wps = new WpsInfo(source.wps);
+ groupOwnerIntent = source.groupOwnerIntent;
+ netId = source.netId;
+ networkName = source.networkName;
+ passphrase = source.passphrase;
+ groupOwnerBand = source.groupOwnerBand;
+ }
+ }
+
+ /** Implement the Parcelable interface */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(deviceAddress);
+ dest.writeParcelable(wps, flags);
+ dest.writeInt(groupOwnerIntent);
+ dest.writeInt(netId);
+ dest.writeString(networkName);
+ dest.writeString(passphrase);
+ dest.writeInt(groupOwnerBand);
+ }
+
+ /** Implement the Parcelable interface */
+ public static final @android.annotation.NonNull Creator<WifiP2pConfig> CREATOR =
+ new Creator<WifiP2pConfig>() {
+ public WifiP2pConfig createFromParcel(Parcel in) {
+ WifiP2pConfig config = new WifiP2pConfig();
+ config.deviceAddress = in.readString();
+ config.wps = (WpsInfo) in.readParcelable(null);
+ config.groupOwnerIntent = in.readInt();
+ config.netId = in.readInt();
+ config.networkName = in.readString();
+ config.passphrase = in.readString();
+ config.groupOwnerBand = in.readInt();
+ return config;
+ }
+
+ public WifiP2pConfig[] newArray(int size) {
+ return new WifiP2pConfig[size];
+ }
+ };
+
+ /**
+ * Builder used to build {@link WifiP2pConfig} objects for
+ * creating or joining a group.
+ */
+ public static final class Builder {
+
+ private static final MacAddress MAC_ANY_ADDRESS =
+ MacAddress.fromString("02:00:00:00:00:00");
+
+ private MacAddress mDeviceAddress = MAC_ANY_ADDRESS;
+ private String mNetworkName = "";
+ private String mPassphrase = "";
+ private int mGroupOperatingBand = GROUP_OWNER_BAND_AUTO;
+ private int mGroupOperatingFrequency = GROUP_OWNER_BAND_AUTO;
+ private int mNetId = WifiP2pGroup.TEMPORARY_NET_ID;
+
+ /**
+ * Specify the peer's MAC address. If not set, the device will
+ * try to find a peer whose SSID matches the network name as
+ * specified by {@link #setNetworkName(String)}. Specifying null will
+ * reset the peer's MAC address to "02:00:00:00:00:00".
+ * <p>
+ * Optional. "02:00:00:00:00:00" by default.
+ *
+ * @param deviceAddress the peer's MAC address.
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public @NonNull Builder setDeviceAddress(@Nullable MacAddress deviceAddress) {
+ if (deviceAddress == null) {
+ mDeviceAddress = MAC_ANY_ADDRESS;
+ } else {
+ mDeviceAddress = deviceAddress;
+ }
+ return this;
+ }
+
+ /**
+ * Specify the network name, a.k.a. group name,
+ * for creating or joining a group.
+ * <p>
+ * A network name shall begin with "DIRECT-xy". x and y are selected
+ * from the following character set: upper case letters, lower case
+ * letters and numbers. Any byte values allowed for an SSID according to
+ * IEEE802.11-2012 [1] may be included after the string "DIRECT-xy"
+ * (including none).
+ * <p>
+ * Must be called - an empty network name or an network name
+ * not conforming to the P2P Group ID naming rule is not valid.
+ *
+ * @param networkName network name of a group.
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public @NonNull Builder setNetworkName(@NonNull String networkName) {
+ if (TextUtils.isEmpty(networkName)) {
+ throw new IllegalArgumentException(
+ "network name must be non-empty.");
+ }
+ try {
+ if (!networkName.matches("^DIRECT-[a-zA-Z0-9]{2}.*")) {
+ throw new IllegalArgumentException(
+ "network name must starts with the prefix DIRECT-xy.");
+ }
+ } catch (PatternSyntaxException e) {
+ // can never happen (fixed pattern)
+ }
+ mNetworkName = networkName;
+ return this;
+ }
+
+ /**
+ * Specify the passphrase for creating or joining a group.
+ * <p>
+ * The passphrase must be an ASCII string whose length is between 8
+ * and 63.
+ * <p>
+ * Must be called - an empty passphrase is not valid.
+ *
+ * @param passphrase the passphrase of a group.
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public @NonNull Builder setPassphrase(@NonNull String passphrase) {
+ if (TextUtils.isEmpty(passphrase)) {
+ throw new IllegalArgumentException(
+ "passphrase must be non-empty.");
+ }
+ if (passphrase.length() < 8 || passphrase.length() > 63) {
+ throw new IllegalArgumentException(
+ "The length of a passphrase must be between 8 and 63.");
+ }
+ mPassphrase = passphrase;
+ return this;
+ }
+
+ /**
+ * Specify the band to use for creating the group or joining the group. The band should
+ * be {@link #GROUP_OWNER_BAND_2GHZ}, {@link #GROUP_OWNER_BAND_5GHZ} or
+ * {@link #GROUP_OWNER_BAND_AUTO}.
+ * <p>
+ * When creating a group as Group Owner using {@link
+ * WifiP2pManager#createGroup(WifiP2pManager.Channel,
+ * WifiP2pConfig, WifiP2pManager.ActionListener)},
+ * specifying {@link #GROUP_OWNER_BAND_AUTO} allows the system to pick the operating
+ * frequency from all supported bands.
+ * Specifying {@link #GROUP_OWNER_BAND_2GHZ} or {@link #GROUP_OWNER_BAND_5GHZ}
+ * only allows the system to pick the operating frequency in the specified band.
+ * If the Group Owner cannot create a group in the specified band, the operation will fail.
+ * <p>
+ * When joining a group as Group Client using {@link
+ * WifiP2pManager#connect(WifiP2pManager.Channel, WifiP2pConfig,
+ * WifiP2pManager.ActionListener)},
+ * specifying {@link #GROUP_OWNER_BAND_AUTO} allows the system to scan all supported
+ * frequencies to find the desired group. Specifying {@link #GROUP_OWNER_BAND_2GHZ} or
+ * {@link #GROUP_OWNER_BAND_5GHZ} only allows the system to scan the specified band.
+ * <p>
+ * {@link #setGroupOperatingBand(int)} and {@link #setGroupOperatingFrequency(int)} are
+ * mutually exclusive. Setting operating band and frequency both is invalid.
+ * <p>
+ * Optional. {@link #GROUP_OWNER_BAND_AUTO} by default.
+ *
+ * @param band the operating band of the group.
+ * This should be one of {@link #GROUP_OWNER_BAND_AUTO},
+ * {@link #GROUP_OWNER_BAND_2GHZ}, {@link #GROUP_OWNER_BAND_5GHZ}.
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public @NonNull Builder setGroupOperatingBand(@GroupOperatingBandType int band) {
+ switch (band) {
+ case GROUP_OWNER_BAND_AUTO:
+ case GROUP_OWNER_BAND_2GHZ:
+ case GROUP_OWNER_BAND_5GHZ:
+ mGroupOperatingBand = band;
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Invalid constant for the group operating band!");
+ }
+ return this;
+ }
+
+ /**
+ * Specify the frequency, in MHz, to use for creating the group or joining the group.
+ * <p>
+ * When creating a group as Group Owner using {@link WifiP2pManager#createGroup(
+ * WifiP2pManager.Channel, WifiP2pConfig, WifiP2pManager.ActionListener)},
+ * specifying a frequency only allows the system to pick the specified frequency.
+ * If the Group Owner cannot create a group at the specified frequency,
+ * the operation will fail.
+ * When not specifying a frequency, it allows the system to pick operating frequency
+ * from all supported bands.
+ * <p>
+ * When joining a group as Group Client using {@link WifiP2pManager#connect(
+ * WifiP2pManager.Channel, WifiP2pConfig, WifiP2pManager.ActionListener)},
+ * specifying a frequency only allows the system to scan the specified frequency.
+ * If the frequency is not supported or invalid, the operation will fail.
+ * When not specifying a frequency, it allows the system to scan all supported
+ * frequencies to find the desired group.
+ * <p>
+ * {@link #setGroupOperatingBand(int)} and {@link #setGroupOperatingFrequency(int)} are
+ * mutually exclusive. Setting operating band and frequency both is invalid.
+ * <p>
+ * Optional. 0 by default.
+ *
+ * @param frequency the operating frequency of the group.
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public @NonNull Builder setGroupOperatingFrequency(int frequency) {
+ if (frequency < 0) {
+ throw new IllegalArgumentException(
+ "Invalid group operating frequency!");
+ }
+ mGroupOperatingFrequency = frequency;
+ return this;
+ }
+
+ /**
+ * Specify that the group configuration be persisted (i.e. saved).
+ * By default the group configuration will not be saved.
+ * <p>
+ * Optional. false by default.
+ *
+ * @param persistent is this group persistent group.
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public @NonNull Builder enablePersistentMode(boolean persistent) {
+ if (persistent) {
+ mNetId = WifiP2pGroup.PERSISTENT_NET_ID;
+ } else {
+ mNetId = WifiP2pGroup.TEMPORARY_NET_ID;
+ }
+ return this;
+ }
+
+ /**
+ * Build {@link WifiP2pConfig} given the current requests made on the builder.
+ * @return {@link WifiP2pConfig} constructed based on builder method calls.
+ */
+ public @NonNull WifiP2pConfig build() {
+ if (TextUtils.isEmpty(mNetworkName)) {
+ throw new IllegalStateException(
+ "network name must be non-empty.");
+ }
+ if (TextUtils.isEmpty(mPassphrase)) {
+ throw new IllegalStateException(
+ "passphrase must be non-empty.");
+ }
+
+ if (mGroupOperatingFrequency > 0 && mGroupOperatingBand > 0) {
+ throw new IllegalStateException(
+ "Preferred frequency and band are mutually exclusive.");
+ }
+
+ WifiP2pConfig config = new WifiP2pConfig();
+ config.deviceAddress = mDeviceAddress.toString();
+ config.networkName = mNetworkName;
+ config.passphrase = mPassphrase;
+ config.groupOwnerBand = GROUP_OWNER_BAND_AUTO;
+ if (mGroupOperatingFrequency > 0) {
+ config.groupOwnerBand = mGroupOperatingFrequency;
+ } else if (mGroupOperatingBand > 0) {
+ config.groupOwnerBand = mGroupOperatingBand;
+ }
+ config.netId = mNetId;
+ return config;
+ }
+ }
+}
diff --git a/android/net/wifi/p2p/WifiP2pDevice.java b/android/net/wifi/p2p/WifiP2pDevice.java
new file mode 100644
index 0000000..c5318a9
--- /dev/null
+++ b/android/net/wifi/p2p/WifiP2pDevice.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2011 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.p2p;
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A class representing a Wi-Fi p2p device
+ *
+ * Note that the operations are not thread safe
+ * {@see WifiP2pManager}
+ */
+public class WifiP2pDevice implements Parcelable {
+
+ private static final String TAG = "WifiP2pDevice";
+
+ /**
+ * The device name is a user friendly string to identify a Wi-Fi p2p device
+ */
+ public String deviceName = "";
+
+ /**
+ * The device MAC address uniquely identifies a Wi-Fi p2p device
+ */
+ public String deviceAddress = "";
+
+ /**
+ * Primary device type identifies the type of device. For example, an application
+ * could filter the devices discovered to only display printers if the purpose is to
+ * enable a printing action from the user. See the Wi-Fi Direct technical specification
+ * for the full list of standard device types supported.
+ */
+ public String primaryDeviceType;
+
+ /**
+ * Secondary device type is an optional attribute that can be provided by a device in
+ * addition to the primary device type.
+ */
+ public String secondaryDeviceType;
+
+
+ // These definitions match the ones in wpa_supplicant
+ /* WPS config methods supported */
+ private static final int WPS_CONFIG_DISPLAY = 0x0008;
+ private static final int WPS_CONFIG_PUSHBUTTON = 0x0080;
+ private static final int WPS_CONFIG_KEYPAD = 0x0100;
+
+ /* Device Capability bitmap */
+ private static final int DEVICE_CAPAB_SERVICE_DISCOVERY = 1;
+ @SuppressWarnings("unused")
+ private static final int DEVICE_CAPAB_CLIENT_DISCOVERABILITY = 1<<1;
+ @SuppressWarnings("unused")
+ private static final int DEVICE_CAPAB_CONCURRENT_OPER = 1<<2;
+ @SuppressWarnings("unused")
+ private static final int DEVICE_CAPAB_INFRA_MANAGED = 1<<3;
+ @SuppressWarnings("unused")
+ private static final int DEVICE_CAPAB_DEVICE_LIMIT = 1<<4;
+ private static final int DEVICE_CAPAB_INVITATION_PROCEDURE = 1<<5;
+
+ /* Group Capability bitmap */
+ private static final int GROUP_CAPAB_GROUP_OWNER = 1;
+ @SuppressWarnings("unused")
+ private static final int GROUP_CAPAB_PERSISTENT_GROUP = 1<<1;
+ private static final int GROUP_CAPAB_GROUP_LIMIT = 1<<2;
+ @SuppressWarnings("unused")
+ private static final int GROUP_CAPAB_INTRA_BSS_DIST = 1<<3;
+ @SuppressWarnings("unused")
+ private static final int GROUP_CAPAB_CROSS_CONN = 1<<4;
+ @SuppressWarnings("unused")
+ private static final int GROUP_CAPAB_PERSISTENT_RECONN = 1<<5;
+ @SuppressWarnings("unused")
+ private static final int GROUP_CAPAB_GROUP_FORMATION = 1<<6;
+
+ /**
+ * WPS config methods supported
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public int wpsConfigMethodsSupported;
+
+ /**
+ * Device capability
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public int deviceCapability;
+
+ /**
+ * Group capability
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public int groupCapability;
+
+ public static final int CONNECTED = 0;
+ public static final int INVITED = 1;
+ public static final int FAILED = 2;
+ public static final int AVAILABLE = 3;
+ public static final int UNAVAILABLE = 4;
+
+ /** Device connection status */
+ public int status = UNAVAILABLE;
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public WifiP2pWfdInfo wfdInfo;
+
+ /** Detailed device string pattern with WFD info
+ * Example:
+ * P2P-DEVICE-FOUND 00:18:6b:de:a3:6e p2p_dev_addr=00:18:6b:de:a3:6e
+ * pri_dev_type=1-0050F204-1 name='DWD-300-DEA36E' config_methods=0x188
+ * dev_capab=0x21 group_capab=0x9
+ */
+ private static final Pattern detailedDevicePattern = Pattern.compile(
+ "((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) " +
+ "(\\d+ )?" +
+ "p2p_dev_addr=((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) " +
+ "pri_dev_type=(\\d+-[0-9a-fA-F]+-\\d+) " +
+ "name='(.*)' " +
+ "config_methods=(0x[0-9a-fA-F]+) " +
+ "dev_capab=(0x[0-9a-fA-F]+) " +
+ "group_capab=(0x[0-9a-fA-F]+)" +
+ "( wfd_dev_info=0x([0-9a-fA-F]{12}))?"
+ );
+
+ /** 2 token device address pattern
+ * Example:
+ * P2P-DEVICE-LOST p2p_dev_addr=fa:7b:7a:42:02:13
+ * AP-STA-DISCONNECTED 42:fc:89:a8:96:09
+ */
+ private static final Pattern twoTokenPattern = Pattern.compile(
+ "(p2p_dev_addr=)?((?:[0-9a-f]{2}:){5}[0-9a-f]{2})"
+ );
+
+ /** 3 token device address pattern
+ * Example:
+ * AP-STA-CONNECTED 42:fc:89:a8:96:09 p2p_dev_addr=fa:7b:7a:42:02:13
+ * AP-STA-DISCONNECTED 42:fc:89:a8:96:09 p2p_dev_addr=fa:7b:7a:42:02:13
+ */
+ private static final Pattern threeTokenPattern = Pattern.compile(
+ "(?:[0-9a-f]{2}:){5}[0-9a-f]{2} p2p_dev_addr=((?:[0-9a-f]{2}:){5}[0-9a-f]{2})"
+ );
+
+
+ public WifiP2pDevice() {
+ }
+
+ /**
+ * @param string formats supported include
+ * P2P-DEVICE-FOUND fa:7b:7a:42:02:13 p2p_dev_addr=fa:7b:7a:42:02:13
+ * pri_dev_type=1-0050F204-1 name='p2p-TEST1' config_methods=0x188 dev_capab=0x27
+ * group_capab=0x0 wfd_dev_info=000006015d022a0032
+ *
+ * P2P-DEVICE-LOST p2p_dev_addr=fa:7b:7a:42:02:13
+ *
+ * AP-STA-CONNECTED 42:fc:89:a8:96:09 [p2p_dev_addr=02:90:4c:a0:92:54]
+ *
+ * AP-STA-DISCONNECTED 42:fc:89:a8:96:09 [p2p_dev_addr=02:90:4c:a0:92:54]
+ *
+ * fa:7b:7a:42:02:13
+ *
+ * Note: The events formats can be looked up in the wpa_supplicant code
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public WifiP2pDevice(String string) throws IllegalArgumentException {
+ String[] tokens = string.split("[ \n]");
+ Matcher match;
+
+ if (tokens.length < 1) {
+ throw new IllegalArgumentException("Malformed supplicant event");
+ }
+
+ switch (tokens.length) {
+ case 1:
+ /* Just a device address */
+ deviceAddress = string;
+ return;
+ case 2:
+ match = twoTokenPattern.matcher(string);
+ if (!match.find()) {
+ throw new IllegalArgumentException("Malformed supplicant event");
+ }
+ deviceAddress = match.group(2);
+ return;
+ case 3:
+ match = threeTokenPattern.matcher(string);
+ if (!match.find()) {
+ throw new IllegalArgumentException("Malformed supplicant event");
+ }
+ deviceAddress = match.group(1);
+ return;
+ default:
+ match = detailedDevicePattern.matcher(string);
+ if (!match.find()) {
+ throw new IllegalArgumentException("Malformed supplicant event");
+ }
+
+ deviceAddress = match.group(3);
+ primaryDeviceType = match.group(4);
+ deviceName = match.group(5);
+ wpsConfigMethodsSupported = parseHex(match.group(6));
+ deviceCapability = parseHex(match.group(7));
+ groupCapability = parseHex(match.group(8));
+ if (match.group(9) != null) {
+ String str = match.group(10);
+ wfdInfo = new WifiP2pWfdInfo(parseHex(str.substring(0,4)),
+ parseHex(str.substring(4,8)),
+ parseHex(str.substring(8,12)));
+ }
+ break;
+ }
+
+ if (tokens[0].startsWith("P2P-DEVICE-FOUND")) {
+ status = AVAILABLE;
+ }
+ }
+
+ /** Returns true if WPS push button configuration is supported */
+ public boolean wpsPbcSupported() {
+ return (wpsConfigMethodsSupported & WPS_CONFIG_PUSHBUTTON) != 0;
+ }
+
+ /** Returns true if WPS keypad configuration is supported */
+ public boolean wpsKeypadSupported() {
+ return (wpsConfigMethodsSupported & WPS_CONFIG_KEYPAD) != 0;
+ }
+
+ /** Returns true if WPS display configuration is supported */
+ public boolean wpsDisplaySupported() {
+ return (wpsConfigMethodsSupported & WPS_CONFIG_DISPLAY) != 0;
+ }
+
+ /** Returns true if the device is capable of service discovery */
+ public boolean isServiceDiscoveryCapable() {
+ return (deviceCapability & DEVICE_CAPAB_SERVICE_DISCOVERY) != 0;
+ }
+
+ /** Returns true if the device is capable of invitation {@hide}*/
+ public boolean isInvitationCapable() {
+ return (deviceCapability & DEVICE_CAPAB_INVITATION_PROCEDURE) != 0;
+ }
+
+ /** Returns true if the device reaches the limit. {@hide}*/
+ public boolean isDeviceLimit() {
+ return (deviceCapability & DEVICE_CAPAB_DEVICE_LIMIT) != 0;
+ }
+
+ /** Returns true if the device is a group owner */
+ public boolean isGroupOwner() {
+ return (groupCapability & GROUP_CAPAB_GROUP_OWNER) != 0;
+ }
+
+ /** Returns true if the group reaches the limit. {@hide}*/
+ public boolean isGroupLimit() {
+ return (groupCapability & GROUP_CAPAB_GROUP_LIMIT) != 0;
+ }
+
+ /**
+ * Update device details. This will be throw an exception if the device address
+ * does not match.
+ * @param device to be updated
+ * @throws IllegalArgumentException if the device is null or device address does not match
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void update(WifiP2pDevice device) {
+ updateSupplicantDetails(device);
+ status = device.status;
+ }
+
+ /** Updates details obtained from supplicant @hide */
+ public void updateSupplicantDetails(WifiP2pDevice device) {
+ if (device == null) {
+ throw new IllegalArgumentException("device is null");
+ }
+ if (device.deviceAddress == null) {
+ throw new IllegalArgumentException("deviceAddress is null");
+ }
+ if (!deviceAddress.equals(device.deviceAddress)) {
+ throw new IllegalArgumentException("deviceAddress does not match");
+ }
+ deviceName = device.deviceName;
+ primaryDeviceType = device.primaryDeviceType;
+ secondaryDeviceType = device.secondaryDeviceType;
+ wpsConfigMethodsSupported = device.wpsConfigMethodsSupported;
+ deviceCapability = device.deviceCapability;
+ groupCapability = device.groupCapability;
+ wfdInfo = device.wfdInfo;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (!(obj instanceof WifiP2pDevice)) return false;
+
+ WifiP2pDevice other = (WifiP2pDevice) obj;
+ if (other == null || other.deviceAddress == null) {
+ return (deviceAddress == null);
+ }
+ return other.deviceAddress.equals(deviceAddress);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(deviceAddress);
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append("Device: ").append(deviceName);
+ sbuf.append("\n deviceAddress: ").append(deviceAddress);
+ sbuf.append("\n primary type: ").append(primaryDeviceType);
+ sbuf.append("\n secondary type: ").append(secondaryDeviceType);
+ sbuf.append("\n wps: ").append(wpsConfigMethodsSupported);
+ sbuf.append("\n grpcapab: ").append(groupCapability);
+ sbuf.append("\n devcapab: ").append(deviceCapability);
+ sbuf.append("\n status: ").append(status);
+ sbuf.append("\n wfdInfo: ").append(wfdInfo);
+ return sbuf.toString();
+ }
+
+ /** Implement the Parcelable interface */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** copy constructor */
+ public WifiP2pDevice(WifiP2pDevice source) {
+ if (source != null) {
+ deviceName = source.deviceName;
+ deviceAddress = source.deviceAddress;
+ primaryDeviceType = source.primaryDeviceType;
+ secondaryDeviceType = source.secondaryDeviceType;
+ wpsConfigMethodsSupported = source.wpsConfigMethodsSupported;
+ deviceCapability = source.deviceCapability;
+ groupCapability = source.groupCapability;
+ status = source.status;
+ if (source.wfdInfo != null) {
+ wfdInfo = new WifiP2pWfdInfo(source.wfdInfo);
+ }
+ }
+ }
+
+ /** Implement the Parcelable interface */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(deviceName);
+ dest.writeString(deviceAddress);
+ dest.writeString(primaryDeviceType);
+ dest.writeString(secondaryDeviceType);
+ dest.writeInt(wpsConfigMethodsSupported);
+ dest.writeInt(deviceCapability);
+ dest.writeInt(groupCapability);
+ dest.writeInt(status);
+ if (wfdInfo != null) {
+ dest.writeInt(1);
+ wfdInfo.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
+ }
+
+ /** Implement the Parcelable interface */
+ public static final @android.annotation.NonNull Creator<WifiP2pDevice> CREATOR =
+ new Creator<WifiP2pDevice>() {
+ @Override
+ public WifiP2pDevice createFromParcel(Parcel in) {
+ WifiP2pDevice device = new WifiP2pDevice();
+ device.deviceName = in.readString();
+ device.deviceAddress = in.readString();
+ device.primaryDeviceType = in.readString();
+ device.secondaryDeviceType = in.readString();
+ device.wpsConfigMethodsSupported = in.readInt();
+ device.deviceCapability = in.readInt();
+ device.groupCapability = in.readInt();
+ device.status = in.readInt();
+ if (in.readInt() == 1) {
+ device.wfdInfo = WifiP2pWfdInfo.CREATOR.createFromParcel(in);
+ }
+ return device;
+ }
+
+ @Override
+ public WifiP2pDevice[] newArray(int size) {
+ return new WifiP2pDevice[size];
+ }
+ };
+
+ //supported formats: 0x1abc, 0X1abc, 1abc
+ private int parseHex(String hexString) {
+ int num = 0;
+ if (hexString.startsWith("0x") || hexString.startsWith("0X")) {
+ hexString = hexString.substring(2);
+ }
+
+ try {
+ num = Integer.parseInt(hexString, 16);
+ } catch(NumberFormatException e) {
+ Log.e(TAG, "Failed to parse hex string " + hexString);
+ }
+ return num;
+ }
+}
diff --git a/android/net/wifi/p2p/WifiP2pDeviceList.java b/android/net/wifi/p2p/WifiP2pDeviceList.java
new file mode 100644
index 0000000..acf06fb
--- /dev/null
+++ b/android/net/wifi/p2p/WifiP2pDeviceList.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2011 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.p2p;
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcelable;
+import android.os.Parcel;
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+
+/**
+ * A class representing a Wi-Fi P2p device list.
+ *
+ * Note that the operations are not thread safe.
+ * {@see WifiP2pManager}
+ */
+public class WifiP2pDeviceList implements Parcelable {
+
+ private final HashMap<String, WifiP2pDevice> mDevices = new HashMap<String, WifiP2pDevice>();
+
+ public WifiP2pDeviceList() {
+ }
+
+ /** copy constructor */
+ public WifiP2pDeviceList(WifiP2pDeviceList source) {
+ if (source != null) {
+ for (WifiP2pDevice d : source.getDeviceList()) {
+ mDevices.put(d.deviceAddress, new WifiP2pDevice(d));
+ }
+ }
+ }
+
+ /** @hide */
+ public WifiP2pDeviceList(ArrayList<WifiP2pDevice> devices) {
+ for (WifiP2pDevice device : devices) {
+ if (device.deviceAddress != null) {
+ mDevices.put(device.deviceAddress, new WifiP2pDevice(device));
+ }
+ }
+ }
+
+ private void validateDevice(WifiP2pDevice device) {
+ if (device == null) throw new IllegalArgumentException("Null device");
+ if (TextUtils.isEmpty(device.deviceAddress)) {
+ throw new IllegalArgumentException("Empty deviceAddress");
+ }
+ }
+
+ private void validateDeviceAddress(String deviceAddress) {
+ if (TextUtils.isEmpty(deviceAddress)) {
+ throw new IllegalArgumentException("Empty deviceAddress");
+ }
+ }
+
+ /** Clear the list @hide */
+ public boolean clear() {
+ if (mDevices.isEmpty()) return false;
+ mDevices.clear();
+ return true;
+ }
+
+ /**
+ * Add/update a device to the list. If the device is not found, a new device entry
+ * is created. If the device is already found, the device details are updated
+ * @param device to be updated
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void update(WifiP2pDevice device) {
+ updateSupplicantDetails(device);
+ mDevices.get(device.deviceAddress).status = device.status;
+ }
+
+ /** Only updates details fetched from the supplicant @hide */
+ public void updateSupplicantDetails(WifiP2pDevice device) {
+ validateDevice(device);
+ WifiP2pDevice d = mDevices.get(device.deviceAddress);
+ if (d != null) {
+ d.deviceName = device.deviceName;
+ d.primaryDeviceType = device.primaryDeviceType;
+ d.secondaryDeviceType = device.secondaryDeviceType;
+ d.wpsConfigMethodsSupported = device.wpsConfigMethodsSupported;
+ d.deviceCapability = device.deviceCapability;
+ d.groupCapability = device.groupCapability;
+ d.wfdInfo = device.wfdInfo;
+ return;
+ }
+ //Not found, add a new one
+ mDevices.put(device.deviceAddress, device);
+ }
+
+ /** @hide */
+ public void updateGroupCapability(String deviceAddress, int groupCapab) {
+ validateDeviceAddress(deviceAddress);
+ WifiP2pDevice d = mDevices.get(deviceAddress);
+ if (d != null) {
+ d.groupCapability = groupCapab;
+ }
+ }
+
+ /** @hide */
+ public void updateStatus(String deviceAddress, int status) {
+ validateDeviceAddress(deviceAddress);
+ WifiP2pDevice d = mDevices.get(deviceAddress);
+ if (d != null) {
+ d.status = status;
+ }
+ }
+
+ /**
+ * Fetch a device from the list
+ * @param deviceAddress is the address of the device
+ * @return WifiP2pDevice device found, or null if none found
+ */
+ public WifiP2pDevice get(String deviceAddress) {
+ validateDeviceAddress(deviceAddress);
+ return mDevices.get(deviceAddress);
+ }
+
+ /** @hide */
+ public boolean remove(WifiP2pDevice device) {
+ validateDevice(device);
+ return mDevices.remove(device.deviceAddress) != null;
+ }
+
+ /**
+ * Remove a device from the list
+ * @param deviceAddress is the address of the device
+ * @return WifiP2pDevice device removed, or null if none removed
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public WifiP2pDevice remove(String deviceAddress) {
+ validateDeviceAddress(deviceAddress);
+ return mDevices.remove(deviceAddress);
+ }
+
+ /** Returns true if any device the list was removed @hide */
+ public boolean remove(WifiP2pDeviceList list) {
+ boolean ret = false;
+ for (WifiP2pDevice d : list.mDevices.values()) {
+ if (remove(d)) ret = true;
+ }
+ return ret;
+ }
+
+ /** Get the list of devices */
+ public Collection<WifiP2pDevice> getDeviceList() {
+ return Collections.unmodifiableCollection(mDevices.values());
+ }
+
+ /** @hide */
+ public boolean isGroupOwner(String deviceAddress) {
+ validateDeviceAddress(deviceAddress);
+ WifiP2pDevice device = mDevices.get(deviceAddress);
+ if (device == null) {
+ throw new IllegalArgumentException("Device not found " + deviceAddress);
+ }
+ return device.isGroupOwner();
+ }
+
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ for (WifiP2pDevice device : mDevices.values()) {
+ sbuf.append("\n").append(device);
+ }
+ return sbuf.toString();
+ }
+
+ /** Implement the Parcelable interface */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mDevices.size());
+ for(WifiP2pDevice device : mDevices.values()) {
+ dest.writeParcelable(device, flags);
+ }
+ }
+
+ /** Implement the Parcelable interface */
+ public static final @android.annotation.NonNull Creator<WifiP2pDeviceList> CREATOR =
+ new Creator<WifiP2pDeviceList>() {
+ public WifiP2pDeviceList createFromParcel(Parcel in) {
+ WifiP2pDeviceList deviceList = new WifiP2pDeviceList();
+
+ int deviceCount = in.readInt();
+ for (int i = 0; i < deviceCount; i++) {
+ deviceList.update((WifiP2pDevice)in.readParcelable(null));
+ }
+ return deviceList;
+ }
+
+ public WifiP2pDeviceList[] newArray(int size) {
+ return new WifiP2pDeviceList[size];
+ }
+ };
+}
diff --git a/android/net/wifi/p2p/WifiP2pGroup.java b/android/net/wifi/p2p/WifiP2pGroup.java
new file mode 100644
index 0000000..4866bd4
--- /dev/null
+++ b/android/net/wifi/p2p/WifiP2pGroup.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2011 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.p2p;
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A class representing a Wi-Fi P2p group. A p2p group consists of a single group
+ * owner and one or more clients. In the case of a group with only two devices, one
+ * will be the group owner and the other will be a group client.
+ *
+ * {@see WifiP2pManager}
+ */
+public class WifiP2pGroup implements Parcelable {
+
+ /** The temporary network id.
+ * {@hide} */
+ @UnsupportedAppUsage
+ public static final int TEMPORARY_NET_ID = -1;
+
+ /** The persistent network id.
+ * If a matching persistent profile is found, use it.
+ * Otherwise, create a new persistent profile.
+ * {@hide} */
+ public static final int PERSISTENT_NET_ID = -2;
+
+ /** The network name */
+ private String mNetworkName;
+
+ /** Group owner */
+ private WifiP2pDevice mOwner;
+
+ /** Device is group owner */
+ private boolean mIsGroupOwner;
+
+ /** Group clients */
+ private List<WifiP2pDevice> mClients = new ArrayList<WifiP2pDevice>();
+
+ /** The passphrase used for WPA2-PSK */
+ private String mPassphrase;
+
+ private String mInterface;
+
+ /** The network id in the wpa_supplicant */
+ private int mNetId;
+
+ /** The frequency (in MHz) used by this group */
+ private int mFrequency;
+
+ /** P2P group started string pattern */
+ private static final Pattern groupStartedPattern = Pattern.compile(
+ "ssid=\"(.+)\" " +
+ "freq=(\\d+) " +
+ "(?:psk=)?([0-9a-fA-F]{64})?" +
+ "(?:passphrase=)?(?:\"(.{0,63})\")? " +
+ "go_dev_addr=((?:[0-9a-f]{2}:){5}[0-9a-f]{2})" +
+ " ?(\\[PERSISTENT\\])?"
+ );
+
+ public WifiP2pGroup() {
+ }
+
+ /**
+ * @param supplicantEvent formats supported include
+ *
+ * P2P-GROUP-STARTED p2p-wlan0-0 [client|GO] ssid="DIRECT-W8" freq=2437
+ * [psk=2182b2e50e53f260d04f3c7b25ef33c965a3291b9b36b455a82d77fd82ca15bc|
+ * passphrase="fKG4jMe3"] go_dev_addr=fa:7b:7a:42:02:13 [PERSISTENT]
+ *
+ * P2P-GROUP-REMOVED p2p-wlan0-0 [client|GO] reason=REQUESTED
+ *
+ * P2P-INVITATION-RECEIVED sa=fa:7b:7a:42:02:13 go_dev_addr=f8:7b:7a:42:02:13
+ * bssid=fa:7b:7a:42:82:13 unknown-network
+ *
+ * P2P-INVITATION-RECEIVED sa=b8:f9:34:2a:c7:9d persistent=0
+ *
+ * Note: The events formats can be looked up in the wpa_supplicant code
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public WifiP2pGroup(String supplicantEvent) throws IllegalArgumentException {
+
+ String[] tokens = supplicantEvent.split(" ");
+
+ if (tokens.length < 3) {
+ throw new IllegalArgumentException("Malformed supplicant event");
+ }
+
+ if (tokens[0].startsWith("P2P-GROUP")) {
+ mInterface = tokens[1];
+ mIsGroupOwner = tokens[2].equals("GO");
+
+ Matcher match = groupStartedPattern.matcher(supplicantEvent);
+ if (!match.find()) {
+ return;
+ }
+
+ mNetworkName = match.group(1);
+ // It throws NumberFormatException if the string cannot be parsed as an integer.
+ mFrequency = Integer.parseInt(match.group(2));
+ // psk is unused right now
+ //String psk = match.group(3);
+ mPassphrase = match.group(4);
+ mOwner = new WifiP2pDevice(match.group(5));
+ if (match.group(6) != null) {
+ mNetId = PERSISTENT_NET_ID;
+ } else {
+ mNetId = TEMPORARY_NET_ID;
+ }
+ } else if (tokens[0].equals("P2P-INVITATION-RECEIVED")) {
+ String sa = null;
+ mNetId = PERSISTENT_NET_ID;
+ for (String token : tokens) {
+ String[] nameValue = token.split("=");
+ if (nameValue.length != 2) continue;
+
+ if (nameValue[0].equals("sa")) {
+ sa = nameValue[1];
+
+ // set source address into the client list.
+ WifiP2pDevice dev = new WifiP2pDevice();
+ dev.deviceAddress = nameValue[1];
+ mClients.add(dev);
+ continue;
+ }
+
+ if (nameValue[0].equals("go_dev_addr")) {
+ mOwner = new WifiP2pDevice(nameValue[1]);
+ continue;
+ }
+
+ if (nameValue[0].equals("persistent")) {
+ mNetId = Integer.parseInt(nameValue[1]);
+ continue;
+ }
+ }
+ } else {
+ throw new IllegalArgumentException("Malformed supplicant event");
+ }
+ }
+
+ /** @hide */
+ public void setNetworkName(String networkName) {
+ mNetworkName = networkName;
+ }
+
+ /**
+ * Get the network name (SSID) of the group. Legacy Wi-Fi clients will discover
+ * the p2p group using the network name.
+ */
+ public String getNetworkName() {
+ return mNetworkName;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public void setIsGroupOwner(boolean isGo) {
+ mIsGroupOwner = isGo;
+ }
+
+ /** Check whether this device is the group owner of the created p2p group */
+ public boolean isGroupOwner() {
+ return mIsGroupOwner;
+ }
+
+ /** @hide */
+ public void setOwner(WifiP2pDevice device) {
+ mOwner = device;
+ }
+
+ /** Get the details of the group owner as a {@link WifiP2pDevice} object */
+ public WifiP2pDevice getOwner() {
+ return mOwner;
+ }
+
+ /** @hide */
+ public void addClient(String address) {
+ addClient(new WifiP2pDevice(address));
+ }
+
+ /** @hide */
+ public void addClient(WifiP2pDevice device) {
+ for (WifiP2pDevice client : mClients) {
+ if (client.equals(device)) return;
+ }
+ mClients.add(device);
+ }
+
+ /** @hide */
+ public boolean removeClient(String address) {
+ return mClients.remove(new WifiP2pDevice(address));
+ }
+
+ /** @hide */
+ public boolean removeClient(WifiP2pDevice device) {
+ return mClients.remove(device);
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public boolean isClientListEmpty() {
+ return mClients.size() == 0;
+ }
+
+ /** @hide Returns {@code true} if the device is part of the group */
+ public boolean contains(WifiP2pDevice device) {
+ if (mOwner.equals(device) || mClients.contains(device)) return true;
+ return false;
+ }
+
+ /** Get the list of clients currently part of the p2p group */
+ public Collection<WifiP2pDevice> getClientList() {
+ return Collections.unmodifiableCollection(mClients);
+ }
+
+ /** @hide */
+ public void setPassphrase(String passphrase) {
+ mPassphrase = passphrase;
+ }
+
+ /**
+ * Get the passphrase of the group. This function will return a valid passphrase only
+ * at the group owner. Legacy Wi-Fi clients will need this passphrase alongside
+ * network name obtained from {@link #getNetworkName()} to join the group
+ */
+ public String getPassphrase() {
+ return mPassphrase;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public void setInterface(String intf) {
+ mInterface = intf;
+ }
+
+ /** Get the interface name on which the group is created */
+ public String getInterface() {
+ return mInterface;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public int getNetworkId() {
+ return mNetId;
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public void setNetworkId(int netId) {
+ this.mNetId = netId;
+ }
+
+ /** Get the operating frequency (in MHz) of the p2p group */
+ public int getFrequency() {
+ return mFrequency;
+ }
+
+ /** @hide */
+ public void setFrequency(int freq) {
+ this.mFrequency = freq;
+ }
+
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append("network: ").append(mNetworkName);
+ sbuf.append("\n isGO: ").append(mIsGroupOwner);
+ sbuf.append("\n GO: ").append(mOwner);
+ for (WifiP2pDevice client : mClients) {
+ sbuf.append("\n Client: ").append(client);
+ }
+ sbuf.append("\n interface: ").append(mInterface);
+ sbuf.append("\n networkId: ").append(mNetId);
+ sbuf.append("\n frequency: ").append(mFrequency);
+ return sbuf.toString();
+ }
+
+ /** Implement the Parcelable interface */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** copy constructor */
+ public WifiP2pGroup(WifiP2pGroup source) {
+ if (source != null) {
+ mNetworkName = source.getNetworkName();
+ mOwner = new WifiP2pDevice(source.getOwner());
+ mIsGroupOwner = source.mIsGroupOwner;
+ for (WifiP2pDevice d : source.getClientList()) mClients.add(d);
+ mPassphrase = source.getPassphrase();
+ mInterface = source.getInterface();
+ mNetId = source.getNetworkId();
+ mFrequency = source.getFrequency();
+ }
+ }
+
+ /** Implement the Parcelable interface */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mNetworkName);
+ dest.writeParcelable(mOwner, flags);
+ dest.writeByte(mIsGroupOwner ? (byte) 1: (byte) 0);
+ dest.writeInt(mClients.size());
+ for (WifiP2pDevice client : mClients) {
+ dest.writeParcelable(client, flags);
+ }
+ dest.writeString(mPassphrase);
+ dest.writeString(mInterface);
+ dest.writeInt(mNetId);
+ dest.writeInt(mFrequency);
+ }
+
+ /** Implement the Parcelable interface */
+ public static final @android.annotation.NonNull Creator<WifiP2pGroup> CREATOR =
+ new Creator<WifiP2pGroup>() {
+ public WifiP2pGroup createFromParcel(Parcel in) {
+ WifiP2pGroup group = new WifiP2pGroup();
+ group.setNetworkName(in.readString());
+ group.setOwner((WifiP2pDevice)in.readParcelable(null));
+ group.setIsGroupOwner(in.readByte() == (byte)1);
+ int clientCount = in.readInt();
+ for (int i=0; i<clientCount; i++) {
+ group.addClient((WifiP2pDevice) in.readParcelable(null));
+ }
+ group.setPassphrase(in.readString());
+ group.setInterface(in.readString());
+ group.setNetworkId(in.readInt());
+ group.setFrequency(in.readInt());
+ return group;
+ }
+
+ public WifiP2pGroup[] newArray(int size) {
+ return new WifiP2pGroup[size];
+ }
+ };
+}
diff --git a/android/net/wifi/p2p/WifiP2pGroupList.java b/android/net/wifi/p2p/WifiP2pGroupList.java
new file mode 100644
index 0000000..62524d9
--- /dev/null
+++ b/android/net/wifi/p2p/WifiP2pGroupList.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2012 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.p2p;
+
+import java.util.Collection;
+import java.util.Map;
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.LruCache;
+
+
+/**
+ * A class representing a Wi-Fi P2p group list
+ *
+ * {@see WifiP2pManager}
+ * @hide
+ */
+public class WifiP2pGroupList implements Parcelable {
+
+ private static final int CREDENTIAL_MAX_NUM = 32;
+
+ @UnsupportedAppUsage
+ private final LruCache<Integer, WifiP2pGroup> mGroups;
+ private final GroupDeleteListener mListener;
+
+ private boolean isClearCalled = false;
+
+ public interface GroupDeleteListener {
+ public void onDeleteGroup(int netId);
+ }
+
+ /** @hide */
+ public WifiP2pGroupList() {
+ this(null, null);
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public WifiP2pGroupList(WifiP2pGroupList source, GroupDeleteListener listener) {
+ mListener = listener;
+ mGroups = new LruCache<Integer, WifiP2pGroup>(CREDENTIAL_MAX_NUM) {
+ @Override
+ protected void entryRemoved(boolean evicted, Integer netId,
+ WifiP2pGroup oldValue, WifiP2pGroup newValue) {
+ if (mListener != null && !isClearCalled) {
+ mListener.onDeleteGroup(oldValue.getNetworkId());
+ }
+ }
+ };
+
+ if (source != null) {
+ for (Map.Entry<Integer, WifiP2pGroup> item : source.mGroups.snapshot().entrySet()) {
+ mGroups.put(item.getKey(), item.getValue());
+ }
+ }
+ }
+
+ /**
+ * Return the list of p2p group.
+ *
+ * @return the list of p2p group.
+ */
+ @UnsupportedAppUsage
+ public Collection<WifiP2pGroup> getGroupList() {
+ return mGroups.snapshot().values();
+ }
+
+ /**
+ * Add the specified group to this group list.
+ *
+ * @param group
+ * @hide
+ */
+ public void add(WifiP2pGroup group) {
+ mGroups.put(group.getNetworkId(), group);
+ }
+
+ /**
+ * Remove the group with the specified network id from this group list.
+ *
+ * @param netId
+ * @hide
+ */
+ public void remove(int netId) {
+ mGroups.remove(netId);
+ }
+
+ /**
+ * Remove the group with the specified device address from this group list.
+ *
+ * @param deviceAddress
+ */
+ void remove(String deviceAddress) {
+ remove(getNetworkId(deviceAddress));
+ }
+
+ /**
+ * Clear the group.
+ * @hide
+ */
+ public boolean clear() {
+ if (mGroups.size() == 0) return false;
+ isClearCalled = true;
+ mGroups.evictAll();
+ isClearCalled = false;
+ return true;
+ }
+
+ /**
+ * Return the network id of the group owner profile with the specified p2p device
+ * address.
+ * If more than one persistent group of the same address is present in the list,
+ * return the first one.
+ *
+ * @param deviceAddress p2p device address.
+ * @return the network id. if not found, return -1.
+ * @hide
+ */
+ public int getNetworkId(String deviceAddress) {
+ if (deviceAddress == null) return -1;
+
+ final Collection<WifiP2pGroup> groups = mGroups.snapshot().values();
+ for (WifiP2pGroup grp: groups) {
+ if (deviceAddress.equalsIgnoreCase(grp.getOwner().deviceAddress)) {
+ // update cache ordered.
+ mGroups.get(grp.getNetworkId());
+ return grp.getNetworkId();
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Return the network id of the group with the specified p2p device address
+ * and the ssid.
+ *
+ * @param deviceAddress p2p device address.
+ * @param ssid ssid.
+ * @return the network id. if not found, return -1.
+ * @hide
+ */
+ public int getNetworkId(String deviceAddress, String ssid) {
+ if (deviceAddress == null || ssid == null) {
+ return -1;
+ }
+
+ final Collection<WifiP2pGroup> groups = mGroups.snapshot().values();
+ for (WifiP2pGroup grp: groups) {
+ if (deviceAddress.equalsIgnoreCase(grp.getOwner().deviceAddress) &&
+ ssid.equals(grp.getNetworkName())) {
+ // update cache ordered.
+ mGroups.get(grp.getNetworkId());
+ return grp.getNetworkId();
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Return the group owner address of the group with the specified network id
+ *
+ * @param netId network id.
+ * @return the address. if not found, return null.
+ * @hide
+ */
+ public String getOwnerAddr(int netId) {
+ WifiP2pGroup grp = mGroups.get(netId);
+ if (grp != null) {
+ return grp.getOwner().deviceAddress;
+ }
+ return null;
+ }
+
+ /**
+ * Return true if this group list contains the specified network id.
+ * This function does NOT update LRU information.
+ * It means the internal queue is NOT reordered.
+ *
+ * @param netId network id.
+ * @return true if the specified network id is present in this group list.
+ * @hide
+ */
+ public boolean contains(int netId) {
+ final Collection<WifiP2pGroup> groups = mGroups.snapshot().values();
+ for (WifiP2pGroup grp: groups) {
+ if (netId == grp.getNetworkId()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+
+ final Collection<WifiP2pGroup> groups = mGroups.snapshot().values();
+ for (WifiP2pGroup grp: groups) {
+ sbuf.append(grp).append("\n");
+ }
+ return sbuf.toString();
+ }
+
+ /** Implement the Parcelable interface */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface */
+ public void writeToParcel(Parcel dest, int flags) {
+ final Collection<WifiP2pGroup> groups = mGroups.snapshot().values();
+ dest.writeInt(groups.size());
+ for(WifiP2pGroup group : groups) {
+ dest.writeParcelable(group, flags);
+ }
+ }
+
+ /** Implement the Parcelable interface */
+ public static final @android.annotation.NonNull Creator<WifiP2pGroupList> CREATOR =
+ new Creator<WifiP2pGroupList>() {
+ public WifiP2pGroupList createFromParcel(Parcel in) {
+ WifiP2pGroupList grpList = new WifiP2pGroupList();
+
+ int deviceCount = in.readInt();
+ for (int i = 0; i < deviceCount; i++) {
+ grpList.add((WifiP2pGroup)in.readParcelable(null));
+ }
+ return grpList;
+ }
+
+ public WifiP2pGroupList[] newArray(int size) {
+ return new WifiP2pGroupList[size];
+ }
+ };
+}
diff --git a/android/net/wifi/p2p/WifiP2pInfo.java b/android/net/wifi/p2p/WifiP2pInfo.java
new file mode 100644
index 0000000..33bc37e
--- /dev/null
+++ b/android/net/wifi/p2p/WifiP2pInfo.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2011 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.p2p;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * A class representing connection information about a Wi-Fi p2p group
+ *
+ * {@see WifiP2pManager}
+ */
+public class WifiP2pInfo implements Parcelable {
+
+ /** Indicates if a p2p group has been successfully formed */
+ public boolean groupFormed;
+
+ /** Indicates if the current device is the group owner */
+ public boolean isGroupOwner;
+
+ /** Group owner address */
+ public InetAddress groupOwnerAddress;
+
+ public WifiP2pInfo() {
+ }
+
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append("groupFormed: ").append(groupFormed)
+ .append(" isGroupOwner: ").append(isGroupOwner)
+ .append(" groupOwnerAddress: ").append(groupOwnerAddress);
+ return sbuf.toString();
+ }
+
+ /** Implement the Parcelable interface */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** copy constructor */
+ public WifiP2pInfo(WifiP2pInfo source) {
+ if (source != null) {
+ groupFormed = source.groupFormed;
+ isGroupOwner = source.isGroupOwner;
+ groupOwnerAddress = source.groupOwnerAddress;
+ }
+ }
+
+ /** Implement the Parcelable interface */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeByte(groupFormed ? (byte)1 : (byte)0);
+ dest.writeByte(isGroupOwner ? (byte)1 : (byte)0);
+
+ if (groupOwnerAddress != null) {
+ dest.writeByte((byte)1);
+ dest.writeByteArray(groupOwnerAddress.getAddress());
+ } else {
+ dest.writeByte((byte)0);
+ }
+ }
+
+ /** Implement the Parcelable interface */
+ public static final @android.annotation.NonNull Creator<WifiP2pInfo> CREATOR =
+ new Creator<WifiP2pInfo>() {
+ public WifiP2pInfo createFromParcel(Parcel in) {
+ WifiP2pInfo info = new WifiP2pInfo();
+ info.groupFormed = (in.readByte() == 1);
+ info.isGroupOwner = (in.readByte() == 1);
+ if (in.readByte() == 1) {
+ try {
+ info.groupOwnerAddress = InetAddress.getByAddress(in.createByteArray());
+ } catch (UnknownHostException e) {}
+ }
+ return info;
+ }
+
+ public WifiP2pInfo[] newArray(int size) {
+ return new WifiP2pInfo[size];
+ }
+ };
+}
diff --git a/android/net/wifi/p2p/WifiP2pManager.java b/android/net/wifi/p2p/WifiP2pManager.java
new file mode 100644
index 0000000..cd659e2
--- /dev/null
+++ b/android/net/wifi/p2p/WifiP2pManager.java
@@ -0,0 +1,1907 @@
+/*
+ * Copyright (C) 2011 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.p2p;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemService;
+import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.net.NetworkInfo;
+import android.net.wifi.WpsInfo;
+import android.net.wifi.p2p.nsd.WifiP2pDnsSdServiceInfo;
+import android.net.wifi.p2p.nsd.WifiP2pDnsSdServiceResponse;
+import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
+import android.net.wifi.p2p.nsd.WifiP2pServiceRequest;
+import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
+import android.net.wifi.p2p.nsd.WifiP2pUpnpServiceInfo;
+import android.net.wifi.p2p.nsd.WifiP2pUpnpServiceResponse;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
+
+import dalvik.system.CloseGuard;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class provides the API for managing Wi-Fi peer-to-peer connectivity. This lets an
+ * application discover available peers, setup connection to peers and query for the list of peers.
+ * When a p2p connection is formed over wifi, the device continues to maintain the uplink
+ * connection over mobile or any other available network for internet connectivity on the device.
+ *
+ * <p> The API is asynchronous and responses to requests from an application are on listener
+ * callbacks provided by the application. The application needs to do an initialization with
+ * {@link #initialize} before doing any p2p operation.
+ *
+ * <p> Most application calls need a {@link ActionListener} instance for receiving callbacks
+ * {@link ActionListener#onSuccess} or {@link ActionListener#onFailure}. Action callbacks
+ * indicate whether the initiation of the action was a success or a failure.
+ * Upon failure, the reason of failure can be one of {@link #ERROR}, {@link #P2P_UNSUPPORTED}
+ * or {@link #BUSY}.
+ *
+ * <p> An application can initiate discovery of peers with {@link #discoverPeers}. An initiated
+ * discovery request from an application stays active until the device starts connecting to a peer
+ * ,forms a p2p group or there is an explicit {@link #stopPeerDiscovery}.
+ * Applications can listen to {@link #WIFI_P2P_DISCOVERY_CHANGED_ACTION} to know if a peer-to-peer
+ * discovery is running or stopped. Additionally, {@link #WIFI_P2P_PEERS_CHANGED_ACTION} indicates
+ * if the peer list has changed.
+ *
+ * <p> When an application needs to fetch the current list of peers, it can request the list
+ * of peers with {@link #requestPeers}. When the peer list is available
+ * {@link PeerListListener#onPeersAvailable} is called with the device list.
+ *
+ * <p> An application can initiate a connection request to a peer through {@link #connect}. See
+ * {@link WifiP2pConfig} for details on setting up the configuration. For communication with legacy
+ * Wi-Fi devices that do not support p2p, an app can create a group using {@link #createGroup}
+ * which creates an access point whose details can be fetched with {@link #requestGroupInfo}.
+ *
+ * <p> After a successful group formation through {@link #createGroup} or through {@link #connect},
+ * use {@link #requestConnectionInfo} to fetch the connection details. The connection info
+ * {@link WifiP2pInfo} contains the address of the group owner
+ * {@link WifiP2pInfo#groupOwnerAddress} and a flag {@link WifiP2pInfo#isGroupOwner} to indicate
+ * if the current device is a p2p group owner. A p2p client can thus communicate with
+ * the p2p group owner through a socket connection. If the current device is the p2p group owner,
+ * {@link WifiP2pInfo#groupOwnerAddress} is anonymized unless the caller holds the
+ * {@code android.Manifest.permission#LOCAL_MAC_ADDRESS} permission.
+ *
+ * <p> With peer discovery using {@link #discoverPeers}, an application discovers the neighboring
+ * peers, but has no good way to figure out which peer to establish a connection with. For example,
+ * if a game application is interested in finding all the neighboring peers that are also running
+ * the same game, it has no way to find out until after the connection is setup. Pre-association
+ * service discovery is meant to address this issue of filtering the peers based on the running
+ * services.
+ *
+ * <p>With pre-association service discovery, an application can advertise a service for a
+ * application on a peer device prior to a connection setup between the devices.
+ * Currently, DNS based service discovery (Bonjour) and Upnp are the higher layer protocols
+ * supported. Get Bonjour resources at dns-sd.org and Upnp resources at upnp.org
+ * As an example, a video application can discover a Upnp capable media renderer
+ * prior to setting up a Wi-fi p2p connection with the device.
+ *
+ * <p> An application can advertise a Upnp or a Bonjour service with a call to
+ * {@link #addLocalService}. After a local service is added,
+ * the framework automatically responds to a peer application discovering the service prior
+ * to establishing a p2p connection. A call to {@link #removeLocalService} removes a local
+ * service and {@link #clearLocalServices} can be used to clear all local services.
+ *
+ * <p> An application that is looking for peer devices that support certain services
+ * can do so with a call to {@link #discoverServices}. Prior to initiating the discovery,
+ * application can add service discovery request with a call to {@link #addServiceRequest},
+ * remove a service discovery request with a call to {@link #removeServiceRequest} or clear
+ * all requests with a call to {@link #clearServiceRequests}. When no service requests remain,
+ * a previously running service discovery will stop.
+ *
+ * The application is notified of a result of service discovery request through listener callbacks
+ * set through {@link #setDnsSdResponseListeners} for Bonjour or
+ * {@link #setUpnpServiceResponseListener} for Upnp.
+ *
+ * <p class="note"><strong>Note:</strong>
+ * Registering an application handler with {@link #initialize} requires the permissions
+ * {@link android.Manifest.permission#ACCESS_WIFI_STATE} and
+ * {@link android.Manifest.permission#CHANGE_WIFI_STATE} to perform any further peer-to-peer
+ * operations.
+ *
+ * {@see WifiP2pConfig}
+ * {@see WifiP2pInfo}
+ * {@see WifiP2pGroup}
+ * {@see WifiP2pDevice}
+ * {@see WifiP2pDeviceList}
+ * {@see android.net.wifi.WpsInfo}
+ */
+@SystemService(Context.WIFI_P2P_SERVICE)
+public class WifiP2pManager {
+ private static final String TAG = "WifiP2pManager";
+ /**
+ * Broadcast intent action to indicate whether Wi-Fi p2p is enabled or disabled. An
+ * extra {@link #EXTRA_WIFI_STATE} provides the state information as int.
+ *
+ * @see #EXTRA_WIFI_STATE
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String WIFI_P2P_STATE_CHANGED_ACTION =
+ "android.net.wifi.p2p.STATE_CHANGED";
+
+ /**
+ * The lookup key for an int that indicates whether Wi-Fi p2p is enabled or disabled.
+ * Retrieve it with {@link android.content.Intent#getIntExtra(String,int)}.
+ *
+ * @see #WIFI_P2P_STATE_DISABLED
+ * @see #WIFI_P2P_STATE_ENABLED
+ */
+ public static final String EXTRA_WIFI_STATE = "wifi_p2p_state";
+
+ /** @hide */
+ @IntDef({
+ WIFI_P2P_STATE_DISABLED,
+ WIFI_P2P_STATE_ENABLED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface WifiP2pState {
+ }
+
+ /**
+ * Wi-Fi p2p is disabled.
+ *
+ * @see #WIFI_P2P_STATE_CHANGED_ACTION
+ */
+ public static final int WIFI_P2P_STATE_DISABLED = 1;
+
+ /**
+ * Wi-Fi p2p is enabled.
+ *
+ * @see #WIFI_P2P_STATE_CHANGED_ACTION
+ */
+ public static final int WIFI_P2P_STATE_ENABLED = 2;
+
+ /**
+ * Broadcast intent action indicating that the state of Wi-Fi p2p connectivity
+ * has changed. One extra {@link #EXTRA_WIFI_P2P_INFO} provides the p2p connection info in
+ * the form of a {@link WifiP2pInfo} object. Another extra {@link #EXTRA_NETWORK_INFO} provides
+ * the network info in the form of a {@link android.net.NetworkInfo}. A third extra provides
+ * the details of the group.
+ *
+ * All of these permissions are required to receive this broadcast:
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and
+ * {@link android.Manifest.permission#ACCESS_WIFI_STATE}
+ *
+ * @see #EXTRA_WIFI_P2P_INFO
+ * @see #EXTRA_NETWORK_INFO
+ * @see #EXTRA_WIFI_P2P_GROUP
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String WIFI_P2P_CONNECTION_CHANGED_ACTION =
+ "android.net.wifi.p2p.CONNECTION_STATE_CHANGE";
+
+ /**
+ * The lookup key for a {@link android.net.wifi.p2p.WifiP2pInfo} object
+ * Retrieve with {@link android.content.Intent#getParcelableExtra(String)}.
+ */
+ public static final String EXTRA_WIFI_P2P_INFO = "wifiP2pInfo";
+
+ /**
+ * The lookup key for a {@link android.net.NetworkInfo} object associated with the
+ * p2p network. Retrieve with
+ * {@link android.content.Intent#getParcelableExtra(String)}.
+ */
+ public static final String EXTRA_NETWORK_INFO = "networkInfo";
+
+ /**
+ * The lookup key for a {@link android.net.wifi.p2p.WifiP2pGroup} object
+ * associated with the p2p network. Retrieve with
+ * {@link android.content.Intent#getParcelableExtra(String)}.
+ */
+ public static final String EXTRA_WIFI_P2P_GROUP = "p2pGroupInfo";
+
+ /**
+ * Broadcast intent action indicating that the available peer list has changed. This
+ * can be sent as a result of peers being found, lost or updated.
+ *
+ * All of these permissions are required to receive this broadcast:
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and
+ * {@link android.Manifest.permission#ACCESS_WIFI_STATE}
+ *
+ * <p> An extra {@link #EXTRA_P2P_DEVICE_LIST} provides the full list of
+ * current peers. The full list of peers can also be obtained any time with
+ * {@link #requestPeers}.
+ *
+ * @see #EXTRA_P2P_DEVICE_LIST
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String WIFI_P2P_PEERS_CHANGED_ACTION =
+ "android.net.wifi.p2p.PEERS_CHANGED";
+
+ /**
+ * The lookup key for a {@link android.net.wifi.p2p.WifiP2pDeviceList} object representing
+ * the new peer list when {@link #WIFI_P2P_PEERS_CHANGED_ACTION} broadcast is sent.
+ *
+ * <p>Retrieve with {@link android.content.Intent#getParcelableExtra(String)}.
+ */
+ public static final String EXTRA_P2P_DEVICE_LIST = "wifiP2pDeviceList";
+
+ /**
+ * Broadcast intent action indicating that peer discovery has either started or stopped.
+ * One extra {@link #EXTRA_DISCOVERY_STATE} indicates whether discovery has started
+ * or stopped.
+ *
+ * <p>Note that discovery will be stopped during a connection setup. If the application tries
+ * to re-initiate discovery during this time, it can fail.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String WIFI_P2P_DISCOVERY_CHANGED_ACTION =
+ "android.net.wifi.p2p.DISCOVERY_STATE_CHANGE";
+
+ /**
+ * The lookup key for an int that indicates whether p2p discovery has started or stopped.
+ * Retrieve it with {@link android.content.Intent#getIntExtra(String,int)}.
+ *
+ * @see #WIFI_P2P_DISCOVERY_STARTED
+ * @see #WIFI_P2P_DISCOVERY_STOPPED
+ */
+ public static final String EXTRA_DISCOVERY_STATE = "discoveryState";
+
+ /** @hide */
+ @IntDef({
+ WIFI_P2P_DISCOVERY_STOPPED,
+ WIFI_P2P_DISCOVERY_STARTED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface WifiP2pDiscoveryState {
+ }
+
+ /**
+ * p2p discovery has stopped
+ *
+ * @see #WIFI_P2P_DISCOVERY_CHANGED_ACTION
+ */
+ public static final int WIFI_P2P_DISCOVERY_STOPPED = 1;
+
+ /**
+ * p2p discovery has started
+ *
+ * @see #WIFI_P2P_DISCOVERY_CHANGED_ACTION
+ */
+ public static final int WIFI_P2P_DISCOVERY_STARTED = 2;
+
+ /**
+ * Broadcast intent action indicating that this device details have changed.
+ *
+ * <p> An extra {@link #EXTRA_WIFI_P2P_DEVICE} provides this device details.
+ * The valid device details can also be obtained with
+ * {@link #requestDeviceInfo(Channel, DeviceInfoListener)} when p2p is enabled.
+ * To get information notifications on P2P getting enabled refers
+ * {@link #WIFI_P2P_STATE_ENABLED}.
+ *
+ * <p> The {@link #EXTRA_WIFI_P2P_DEVICE} extra contains an anonymized version of the device's
+ * MAC address. Callers holding the {@code android.Manifest.permission#LOCAL_MAC_ADDRESS}
+ * permission can use {@link #requestDeviceInfo} to obtain the actual MAC address of this
+ * device.
+ *
+ * All of these permissions are required to receive this broadcast:
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and
+ * {@link android.Manifest.permission#ACCESS_WIFI_STATE}
+ *
+ * @see #EXTRA_WIFI_P2P_DEVICE
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String WIFI_P2P_THIS_DEVICE_CHANGED_ACTION =
+ "android.net.wifi.p2p.THIS_DEVICE_CHANGED";
+
+ /**
+ * The lookup key for a {@link android.net.wifi.p2p.WifiP2pDevice} object
+ * Retrieve with {@link android.content.Intent#getParcelableExtra(String)}.
+ */
+ public static final String EXTRA_WIFI_P2P_DEVICE = "wifiP2pDevice";
+
+ /**
+ * Broadcast intent action indicating that remembered persistent groups have changed.
+ * @hide
+ */
+ public static final String WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION =
+ "android.net.wifi.p2p.PERSISTENT_GROUPS_CHANGED";
+
+ /**
+ * The lookup key for a handover message returned by the WifiP2pService.
+ * @hide
+ */
+ public static final String EXTRA_HANDOVER_MESSAGE =
+ "android.net.wifi.p2p.EXTRA_HANDOVER_MESSAGE";
+
+ /**
+ * The lookup key for a calling package name from WifiP2pManager
+ * @hide
+ */
+ public static final String CALLING_PACKAGE =
+ "android.net.wifi.p2p.CALLING_PACKAGE";
+
+ /**
+ * The lookup key for a calling package binder from WifiP2pManager
+ * @hide
+ */
+ public static final String CALLING_BINDER =
+ "android.net.wifi.p2p.CALLING_BINDER";
+
+ IWifiP2pManager mService;
+
+ private static final int BASE = Protocol.BASE_WIFI_P2P_MANAGER;
+
+ /** @hide */
+ public static final int DISCOVER_PEERS = BASE + 1;
+ /** @hide */
+ public static final int DISCOVER_PEERS_FAILED = BASE + 2;
+ /** @hide */
+ public static final int DISCOVER_PEERS_SUCCEEDED = BASE + 3;
+
+ /** @hide */
+ public static final int STOP_DISCOVERY = BASE + 4;
+ /** @hide */
+ public static final int STOP_DISCOVERY_FAILED = BASE + 5;
+ /** @hide */
+ public static final int STOP_DISCOVERY_SUCCEEDED = BASE + 6;
+
+ /** @hide */
+ public static final int CONNECT = BASE + 7;
+ /** @hide */
+ public static final int CONNECT_FAILED = BASE + 8;
+ /** @hide */
+ public static final int CONNECT_SUCCEEDED = BASE + 9;
+
+ /** @hide */
+ public static final int CANCEL_CONNECT = BASE + 10;
+ /** @hide */
+ public static final int CANCEL_CONNECT_FAILED = BASE + 11;
+ /** @hide */
+ public static final int CANCEL_CONNECT_SUCCEEDED = BASE + 12;
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public static final int CREATE_GROUP = BASE + 13;
+ /** @hide */
+ public static final int CREATE_GROUP_FAILED = BASE + 14;
+ /** @hide */
+ public static final int CREATE_GROUP_SUCCEEDED = BASE + 15;
+
+ /** @hide */
+ public static final int REMOVE_GROUP = BASE + 16;
+ /** @hide */
+ public static final int REMOVE_GROUP_FAILED = BASE + 17;
+ /** @hide */
+ public static final int REMOVE_GROUP_SUCCEEDED = BASE + 18;
+
+ /** @hide */
+ public static final int REQUEST_PEERS = BASE + 19;
+ /** @hide */
+ public static final int RESPONSE_PEERS = BASE + 20;
+
+ /** @hide */
+ public static final int REQUEST_CONNECTION_INFO = BASE + 21;
+ /** @hide */
+ public static final int RESPONSE_CONNECTION_INFO = BASE + 22;
+
+ /** @hide */
+ public static final int REQUEST_GROUP_INFO = BASE + 23;
+ /** @hide */
+ public static final int RESPONSE_GROUP_INFO = BASE + 24;
+
+ /** @hide */
+ public static final int ADD_LOCAL_SERVICE = BASE + 28;
+ /** @hide */
+ public static final int ADD_LOCAL_SERVICE_FAILED = BASE + 29;
+ /** @hide */
+ public static final int ADD_LOCAL_SERVICE_SUCCEEDED = BASE + 30;
+
+ /** @hide */
+ public static final int REMOVE_LOCAL_SERVICE = BASE + 31;
+ /** @hide */
+ public static final int REMOVE_LOCAL_SERVICE_FAILED = BASE + 32;
+ /** @hide */
+ public static final int REMOVE_LOCAL_SERVICE_SUCCEEDED = BASE + 33;
+
+ /** @hide */
+ public static final int CLEAR_LOCAL_SERVICES = BASE + 34;
+ /** @hide */
+ public static final int CLEAR_LOCAL_SERVICES_FAILED = BASE + 35;
+ /** @hide */
+ public static final int CLEAR_LOCAL_SERVICES_SUCCEEDED = BASE + 36;
+
+ /** @hide */
+ public static final int ADD_SERVICE_REQUEST = BASE + 37;
+ /** @hide */
+ public static final int ADD_SERVICE_REQUEST_FAILED = BASE + 38;
+ /** @hide */
+ public static final int ADD_SERVICE_REQUEST_SUCCEEDED = BASE + 39;
+
+ /** @hide */
+ public static final int REMOVE_SERVICE_REQUEST = BASE + 40;
+ /** @hide */
+ public static final int REMOVE_SERVICE_REQUEST_FAILED = BASE + 41;
+ /** @hide */
+ public static final int REMOVE_SERVICE_REQUEST_SUCCEEDED = BASE + 42;
+
+ /** @hide */
+ public static final int CLEAR_SERVICE_REQUESTS = BASE + 43;
+ /** @hide */
+ public static final int CLEAR_SERVICE_REQUESTS_FAILED = BASE + 44;
+ /** @hide */
+ public static final int CLEAR_SERVICE_REQUESTS_SUCCEEDED = BASE + 45;
+
+ /** @hide */
+ public static final int DISCOVER_SERVICES = BASE + 46;
+ /** @hide */
+ public static final int DISCOVER_SERVICES_FAILED = BASE + 47;
+ /** @hide */
+ public static final int DISCOVER_SERVICES_SUCCEEDED = BASE + 48;
+
+ /** @hide */
+ public static final int PING = BASE + 49;
+
+ /** @hide */
+ public static final int RESPONSE_SERVICE = BASE + 50;
+
+ /** @hide */
+ public static final int SET_DEVICE_NAME = BASE + 51;
+ /** @hide */
+ public static final int SET_DEVICE_NAME_FAILED = BASE + 52;
+ /** @hide */
+ public static final int SET_DEVICE_NAME_SUCCEEDED = BASE + 53;
+
+ /** @hide */
+ public static final int DELETE_PERSISTENT_GROUP = BASE + 54;
+ /** @hide */
+ public static final int DELETE_PERSISTENT_GROUP_FAILED = BASE + 55;
+ /** @hide */
+ public static final int DELETE_PERSISTENT_GROUP_SUCCEEDED = BASE + 56;
+
+ /** @hide */
+ public static final int REQUEST_PERSISTENT_GROUP_INFO = BASE + 57;
+ /** @hide */
+ public static final int RESPONSE_PERSISTENT_GROUP_INFO = BASE + 58;
+
+ /** @hide */
+ public static final int SET_WFD_INFO = BASE + 59;
+ /** @hide */
+ public static final int SET_WFD_INFO_FAILED = BASE + 60;
+ /** @hide */
+ public static final int SET_WFD_INFO_SUCCEEDED = BASE + 61;
+
+ /** @hide */
+ public static final int START_WPS = BASE + 62;
+ /** @hide */
+ public static final int START_WPS_FAILED = BASE + 63;
+ /** @hide */
+ public static final int START_WPS_SUCCEEDED = BASE + 64;
+
+ /** @hide */
+ public static final int START_LISTEN = BASE + 65;
+ /** @hide */
+ public static final int START_LISTEN_FAILED = BASE + 66;
+ /** @hide */
+ public static final int START_LISTEN_SUCCEEDED = BASE + 67;
+
+ /** @hide */
+ public static final int STOP_LISTEN = BASE + 68;
+ /** @hide */
+ public static final int STOP_LISTEN_FAILED = BASE + 69;
+ /** @hide */
+ public static final int STOP_LISTEN_SUCCEEDED = BASE + 70;
+
+ /** @hide */
+ public static final int SET_CHANNEL = BASE + 71;
+ /** @hide */
+ public static final int SET_CHANNEL_FAILED = BASE + 72;
+ /** @hide */
+ public static final int SET_CHANNEL_SUCCEEDED = BASE + 73;
+
+ /** @hide */
+ public static final int GET_HANDOVER_REQUEST = BASE + 75;
+ /** @hide */
+ public static final int GET_HANDOVER_SELECT = BASE + 76;
+ /** @hide */
+ public static final int RESPONSE_GET_HANDOVER_MESSAGE = BASE + 77;
+ /** @hide */
+ public static final int INITIATOR_REPORT_NFC_HANDOVER = BASE + 78;
+ /** @hide */
+ public static final int RESPONDER_REPORT_NFC_HANDOVER = BASE + 79;
+ /** @hide */
+ public static final int REPORT_NFC_HANDOVER_SUCCEEDED = BASE + 80;
+ /** @hide */
+ public static final int REPORT_NFC_HANDOVER_FAILED = BASE + 81;
+
+ /** @hide */
+ public static final int FACTORY_RESET = BASE + 82;
+ /** @hide */
+ public static final int FACTORY_RESET_FAILED = BASE + 83;
+ /** @hide */
+ public static final int FACTORY_RESET_SUCCEEDED = BASE + 84;
+
+ /** @hide */
+ public static final int REQUEST_ONGOING_PEER_CONFIG = BASE + 85;
+ /** @hide */
+ public static final int RESPONSE_ONGOING_PEER_CONFIG = BASE + 86;
+ /** @hide */
+ public static final int SET_ONGOING_PEER_CONFIG = BASE + 87;
+ /** @hide */
+ public static final int SET_ONGOING_PEER_CONFIG_FAILED = BASE + 88;
+ /** @hide */
+ public static final int SET_ONGOING_PEER_CONFIG_SUCCEEDED = BASE + 89;
+
+ /** @hide */
+ public static final int REQUEST_P2P_STATE = BASE + 90;
+ /** @hide */
+ public static final int RESPONSE_P2P_STATE = BASE + 91;
+
+ /** @hide */
+ public static final int REQUEST_DISCOVERY_STATE = BASE + 92;
+ /** @hide */
+ public static final int RESPONSE_DISCOVERY_STATE = BASE + 93;
+
+ /** @hide */
+ public static final int REQUEST_NETWORK_INFO = BASE + 94;
+ /** @hide */
+ public static final int RESPONSE_NETWORK_INFO = BASE + 95;
+
+ /** @hide */
+ public static final int UPDATE_CHANNEL_INFO = BASE + 96;
+
+ /** @hide */
+ public static final int REQUEST_DEVICE_INFO = BASE + 97;
+ /** @hide */
+ public static final int RESPONSE_DEVICE_INFO = BASE + 98;
+
+ /**
+ * Create a new WifiP2pManager instance. Applications use
+ * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
+ * the standard {@link android.content.Context#WIFI_P2P_SERVICE Context.WIFI_P2P_SERVICE}.
+ * @param service the Binder interface
+ * @hide - hide this because it takes in a parameter of type IWifiP2pManager, which
+ * is a system private class.
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public WifiP2pManager(IWifiP2pManager service) {
+ mService = service;
+ }
+
+ /**
+ * Passed with {@link ActionListener#onFailure}.
+ * Indicates that the operation failed due to an internal error.
+ */
+ public static final int ERROR = 0;
+
+ /**
+ * Passed with {@link ActionListener#onFailure}.
+ * Indicates that the operation failed because p2p is unsupported on the device.
+ */
+ public static final int P2P_UNSUPPORTED = 1;
+
+ /**
+ * Passed with {@link ActionListener#onFailure}.
+ * Indicates that the operation failed because the framework is busy and
+ * unable to service the request
+ */
+ public static final int BUSY = 2;
+
+ /**
+ * Passed with {@link ActionListener#onFailure}.
+ * Indicates that the {@link #discoverServices} failed because no service
+ * requests are added. Use {@link #addServiceRequest} to add a service
+ * request.
+ */
+ public static final int NO_SERVICE_REQUESTS = 3;
+
+ /** Interface for callback invocation when framework channel is lost */
+ public interface ChannelListener {
+ /**
+ * The channel to the framework has been disconnected.
+ * Application could try re-initializing using {@link #initialize}
+ */
+ public void onChannelDisconnected();
+ }
+
+ /** Interface for callback invocation on an application action */
+ public interface ActionListener {
+ /** The operation succeeded */
+ public void onSuccess();
+ /**
+ * The operation failed
+ * @param reason The reason for failure could be one of {@link #P2P_UNSUPPORTED},
+ * {@link #ERROR} or {@link #BUSY}
+ */
+ public void onFailure(int reason);
+ }
+
+ /** Interface for callback invocation when peer list is available */
+ public interface PeerListListener {
+ /**
+ * The requested peer list is available
+ * @param peers List of available peers
+ */
+ public void onPeersAvailable(WifiP2pDeviceList peers);
+ }
+
+ /** Interface for callback invocation when connection info is available */
+ public interface ConnectionInfoListener {
+ /**
+ * The requested connection info is available
+ * @param info Wi-Fi p2p connection info
+ */
+ public void onConnectionInfoAvailable(WifiP2pInfo info);
+ }
+
+ /** Interface for callback invocation when group info is available */
+ public interface GroupInfoListener {
+ /**
+ * The requested p2p group info is available
+ * @param group Wi-Fi p2p group info
+ */
+ public void onGroupInfoAvailable(WifiP2pGroup group);
+ }
+
+ /**
+ * Interface for callback invocation when service discovery response other than
+ * Upnp or Bonjour is received
+ */
+ public interface ServiceResponseListener {
+
+ /**
+ * The requested service response is available.
+ *
+ * @param protocolType protocol type. currently only
+ * {@link WifiP2pServiceInfo#SERVICE_TYPE_VENDOR_SPECIFIC}.
+ * @param responseData service discovery response data based on the requested
+ * service protocol type. The format depends on the service type.
+ * @param srcDevice source device.
+ */
+ public void onServiceAvailable(int protocolType,
+ byte[] responseData, WifiP2pDevice srcDevice);
+ }
+
+ /**
+ * Interface for callback invocation when Bonjour service discovery response
+ * is received
+ */
+ public interface DnsSdServiceResponseListener {
+
+ /**
+ * The requested Bonjour service response is available.
+ *
+ * <p>This function is invoked when the device with the specified Bonjour
+ * registration type returned the instance name.
+ * @param instanceName instance name.<br>
+ * e.g) "MyPrinter".
+ * @param registrationType <br>
+ * e.g) "_ipp._tcp.local."
+ * @param srcDevice source device.
+ */
+ public void onDnsSdServiceAvailable(String instanceName,
+ String registrationType, WifiP2pDevice srcDevice);
+
+ }
+
+ /**
+ * Interface for callback invocation when Bonjour TXT record is available
+ * for a service
+ */
+ public interface DnsSdTxtRecordListener {
+ /**
+ * The requested Bonjour service response is available.
+ *
+ * <p>This function is invoked when the device with the specified full
+ * service domain service returned TXT record.
+ *
+ * @param fullDomainName full domain name. <br>
+ * e.g) "MyPrinter._ipp._tcp.local.".
+ * @param txtRecordMap TXT record data as a map of key/value pairs
+ * @param srcDevice source device.
+ */
+ public void onDnsSdTxtRecordAvailable(String fullDomainName,
+ Map<String, String> txtRecordMap,
+ WifiP2pDevice srcDevice);
+ }
+
+ /**
+ * Interface for callback invocation when upnp service discovery response
+ * is received
+ * */
+ public interface UpnpServiceResponseListener {
+
+ /**
+ * The requested upnp service response is available.
+ *
+ * <p>This function is invoked when the specified device or service is found.
+ *
+ * @param uniqueServiceNames The list of unique service names.<br>
+ * e.g) uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp-org:device:
+ * MediaServer:1
+ * @param srcDevice source device.
+ */
+ public void onUpnpServiceAvailable(List<String> uniqueServiceNames,
+ WifiP2pDevice srcDevice);
+ }
+
+
+ /** Interface for callback invocation when stored group info list is available {@hide}*/
+ public interface PersistentGroupInfoListener {
+ /**
+ * The requested stored p2p group info list is available
+ * @param groups Wi-Fi p2p group info list
+ */
+ public void onPersistentGroupInfoAvailable(WifiP2pGroupList groups);
+ }
+
+ /**
+ * Interface for callback invocation when Handover Request or Select Message is available
+ * @hide
+ */
+ public interface HandoverMessageListener {
+ public void onHandoverMessageAvailable(String handoverMessage);
+ }
+
+ /** Interface for callback invocation when p2p state is available
+ * in response to {@link #requestP2pState}.
+ */
+ public interface P2pStateListener {
+ /**
+ * The requested p2p state is available.
+ * @param state Wi-Fi p2p state
+ * @see #WIFI_P2P_STATE_DISABLED
+ * @see #WIFI_P2P_STATE_ENABLED
+ */
+ void onP2pStateAvailable(@WifiP2pState int state);
+ }
+
+ /** Interface for callback invocation when p2p state is available
+ * in response to {@link #requestDiscoveryState}.
+ */
+ public interface DiscoveryStateListener {
+ /**
+ * The requested p2p discovery state is available.
+ * @param state Wi-Fi p2p discovery state
+ * @see #WIFI_P2P_DISCOVERY_STARTED
+ * @see #WIFI_P2P_DISCOVERY_STOPPED
+ */
+ void onDiscoveryStateAvailable(@WifiP2pDiscoveryState int state);
+ }
+
+ /** Interface for callback invocation when {@link android.net.NetworkInfo} is available
+ * in response to {@link #requestNetworkInfo}.
+ */
+ public interface NetworkInfoListener {
+ /**
+ * The requested {@link android.net.NetworkInfo} is available
+ * @param networkInfo Wi-Fi p2p {@link android.net.NetworkInfo}
+ */
+ void onNetworkInfoAvailable(@NonNull NetworkInfo networkInfo);
+ }
+
+ /**
+ * Interface for callback invocation when ongoing peer info is available
+ * @hide
+ */
+ public interface OngoingPeerInfoListener {
+ /**
+ * The requested ongoing WifiP2pConfig is available
+ * @param peerConfig WifiP2pConfig for current connecting session
+ */
+ void onOngoingPeerAvailable(WifiP2pConfig peerConfig);
+ }
+
+ /** Interface for callback invocation when {@link android.net.wifi.p2p.WifiP2pDevice}
+ * is available in response to {@link #requestDeviceInfo(Channel, DeviceInfoListener)}.
+ */
+ public interface DeviceInfoListener {
+ /**
+ * The requested {@link android.net.wifi.p2p.WifiP2pDevice} is available.
+ * @param wifiP2pDevice Wi-Fi p2p {@link android.net.wifi.p2p.WifiP2pDevice}
+ */
+ void onDeviceInfoAvailable(@Nullable WifiP2pDevice wifiP2pDevice);
+ }
+
+ /**
+ * A channel that connects the application to the Wifi p2p framework.
+ * Most p2p operations require a Channel as an argument. An instance of Channel is obtained
+ * by doing a call on {@link #initialize}
+ */
+ public static class Channel implements AutoCloseable {
+ /** @hide */
+ public Channel(Context context, Looper looper, ChannelListener l, Binder binder,
+ WifiP2pManager p2pManager) {
+ mAsyncChannel = new AsyncChannel();
+ mHandler = new P2pHandler(looper);
+ mChannelListener = l;
+ mContext = context;
+ mBinder = binder;
+ mP2pManager = p2pManager;
+
+ mCloseGuard.open("close");
+ }
+ private final static int INVALID_LISTENER_KEY = 0;
+ private final WifiP2pManager mP2pManager;
+ private ChannelListener mChannelListener;
+ private ServiceResponseListener mServRspListener;
+ private DnsSdServiceResponseListener mDnsSdServRspListener;
+ private DnsSdTxtRecordListener mDnsSdTxtListener;
+ private UpnpServiceResponseListener mUpnpServRspListener;
+ private HashMap<Integer, Object> mListenerMap = new HashMap<Integer, Object>();
+ private final Object mListenerMapLock = new Object();
+ private int mListenerKey = 0;
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ /**
+ * Close the current P2P connection and indicate to the P2P service that connections
+ * created by the app can be removed.
+ */
+ public void close() {
+ if (mP2pManager == null) {
+ Log.w(TAG, "Channel.close(): Null mP2pManager!?");
+ } else {
+ try {
+ mP2pManager.mService.close(mBinder);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ mAsyncChannel.disconnect();
+ mCloseGuard.close();
+ }
+
+ /** @hide */
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /* package */ final Binder mBinder;
+
+ @UnsupportedAppUsage
+ private AsyncChannel mAsyncChannel;
+ private P2pHandler mHandler;
+ Context mContext;
+ class P2pHandler extends Handler {
+ P2pHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ Object listener = getListener(message.arg2);
+ switch (message.what) {
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+ if (mChannelListener != null) {
+ mChannelListener.onChannelDisconnected();
+ mChannelListener = null;
+ }
+ break;
+ /* ActionListeners grouped together */
+ case DISCOVER_PEERS_FAILED:
+ case STOP_DISCOVERY_FAILED:
+ case DISCOVER_SERVICES_FAILED:
+ case CONNECT_FAILED:
+ case CANCEL_CONNECT_FAILED:
+ case CREATE_GROUP_FAILED:
+ case REMOVE_GROUP_FAILED:
+ case ADD_LOCAL_SERVICE_FAILED:
+ case REMOVE_LOCAL_SERVICE_FAILED:
+ case CLEAR_LOCAL_SERVICES_FAILED:
+ case ADD_SERVICE_REQUEST_FAILED:
+ case REMOVE_SERVICE_REQUEST_FAILED:
+ case CLEAR_SERVICE_REQUESTS_FAILED:
+ case SET_DEVICE_NAME_FAILED:
+ case DELETE_PERSISTENT_GROUP_FAILED:
+ case SET_WFD_INFO_FAILED:
+ case START_WPS_FAILED:
+ case START_LISTEN_FAILED:
+ case STOP_LISTEN_FAILED:
+ case SET_CHANNEL_FAILED:
+ case REPORT_NFC_HANDOVER_FAILED:
+ case FACTORY_RESET_FAILED:
+ case SET_ONGOING_PEER_CONFIG_FAILED:
+ if (listener != null) {
+ ((ActionListener) listener).onFailure(message.arg1);
+ }
+ break;
+ /* ActionListeners grouped together */
+ case DISCOVER_PEERS_SUCCEEDED:
+ case STOP_DISCOVERY_SUCCEEDED:
+ case DISCOVER_SERVICES_SUCCEEDED:
+ case CONNECT_SUCCEEDED:
+ case CANCEL_CONNECT_SUCCEEDED:
+ case CREATE_GROUP_SUCCEEDED:
+ case REMOVE_GROUP_SUCCEEDED:
+ case ADD_LOCAL_SERVICE_SUCCEEDED:
+ case REMOVE_LOCAL_SERVICE_SUCCEEDED:
+ case CLEAR_LOCAL_SERVICES_SUCCEEDED:
+ case ADD_SERVICE_REQUEST_SUCCEEDED:
+ case REMOVE_SERVICE_REQUEST_SUCCEEDED:
+ case CLEAR_SERVICE_REQUESTS_SUCCEEDED:
+ case SET_DEVICE_NAME_SUCCEEDED:
+ case DELETE_PERSISTENT_GROUP_SUCCEEDED:
+ case SET_WFD_INFO_SUCCEEDED:
+ case START_WPS_SUCCEEDED:
+ case START_LISTEN_SUCCEEDED:
+ case STOP_LISTEN_SUCCEEDED:
+ case SET_CHANNEL_SUCCEEDED:
+ case REPORT_NFC_HANDOVER_SUCCEEDED:
+ case FACTORY_RESET_SUCCEEDED:
+ case SET_ONGOING_PEER_CONFIG_SUCCEEDED:
+ if (listener != null) {
+ ((ActionListener) listener).onSuccess();
+ }
+ break;
+ case RESPONSE_PEERS:
+ WifiP2pDeviceList peers = (WifiP2pDeviceList) message.obj;
+ if (listener != null) {
+ ((PeerListListener) listener).onPeersAvailable(peers);
+ }
+ break;
+ case RESPONSE_CONNECTION_INFO:
+ WifiP2pInfo wifiP2pInfo = (WifiP2pInfo) message.obj;
+ if (listener != null) {
+ ((ConnectionInfoListener) listener).onConnectionInfoAvailable(wifiP2pInfo);
+ }
+ break;
+ case RESPONSE_GROUP_INFO:
+ WifiP2pGroup group = (WifiP2pGroup) message.obj;
+ if (listener != null) {
+ ((GroupInfoListener) listener).onGroupInfoAvailable(group);
+ }
+ break;
+ case RESPONSE_SERVICE:
+ WifiP2pServiceResponse resp = (WifiP2pServiceResponse) message.obj;
+ handleServiceResponse(resp);
+ break;
+ case RESPONSE_PERSISTENT_GROUP_INFO:
+ WifiP2pGroupList groups = (WifiP2pGroupList) message.obj;
+ if (listener != null) {
+ ((PersistentGroupInfoListener) listener).
+ onPersistentGroupInfoAvailable(groups);
+ }
+ break;
+ case RESPONSE_GET_HANDOVER_MESSAGE:
+ Bundle handoverBundle = (Bundle) message.obj;
+ if (listener != null) {
+ String handoverMessage = handoverBundle != null
+ ? handoverBundle.getString(EXTRA_HANDOVER_MESSAGE)
+ : null;
+ ((HandoverMessageListener) listener)
+ .onHandoverMessageAvailable(handoverMessage);
+ }
+ break;
+ case RESPONSE_ONGOING_PEER_CONFIG:
+ WifiP2pConfig peerConfig = (WifiP2pConfig) message.obj;
+ if (listener != null) {
+ ((OngoingPeerInfoListener) listener)
+ .onOngoingPeerAvailable(peerConfig);
+ }
+ break;
+ case RESPONSE_P2P_STATE:
+ if (listener != null) {
+ ((P2pStateListener) listener)
+ .onP2pStateAvailable(message.arg1);
+ }
+ break;
+ case RESPONSE_DISCOVERY_STATE:
+ if (listener != null) {
+ ((DiscoveryStateListener) listener)
+ .onDiscoveryStateAvailable(message.arg1);
+ }
+ break;
+ case RESPONSE_NETWORK_INFO:
+ if (listener != null) {
+ ((NetworkInfoListener) listener)
+ .onNetworkInfoAvailable((NetworkInfo) message.obj);
+ }
+ break;
+ case RESPONSE_DEVICE_INFO:
+ if (listener != null) {
+ ((DeviceInfoListener) listener)
+ .onDeviceInfoAvailable((WifiP2pDevice) message.obj);
+ }
+ break;
+ default:
+ Log.d(TAG, "Ignored " + message);
+ break;
+ }
+ }
+ }
+
+ private void handleServiceResponse(WifiP2pServiceResponse resp) {
+ if (resp instanceof WifiP2pDnsSdServiceResponse) {
+ handleDnsSdServiceResponse((WifiP2pDnsSdServiceResponse)resp);
+ } else if (resp instanceof WifiP2pUpnpServiceResponse) {
+ if (mUpnpServRspListener != null) {
+ handleUpnpServiceResponse((WifiP2pUpnpServiceResponse)resp);
+ }
+ } else {
+ if (mServRspListener != null) {
+ mServRspListener.onServiceAvailable(resp.getServiceType(),
+ resp.getRawData(), resp.getSrcDevice());
+ }
+ }
+ }
+
+ private void handleUpnpServiceResponse(WifiP2pUpnpServiceResponse resp) {
+ mUpnpServRspListener.onUpnpServiceAvailable(resp.getUniqueServiceNames(),
+ resp.getSrcDevice());
+ }
+
+ private void handleDnsSdServiceResponse(WifiP2pDnsSdServiceResponse resp) {
+ if (resp.getDnsType() == WifiP2pDnsSdServiceInfo.DNS_TYPE_PTR) {
+ if (mDnsSdServRspListener != null) {
+ mDnsSdServRspListener.onDnsSdServiceAvailable(
+ resp.getInstanceName(),
+ resp.getDnsQueryName(),
+ resp.getSrcDevice());
+ }
+ } else if (resp.getDnsType() == WifiP2pDnsSdServiceInfo.DNS_TYPE_TXT) {
+ if (mDnsSdTxtListener != null) {
+ mDnsSdTxtListener.onDnsSdTxtRecordAvailable(
+ resp.getDnsQueryName(),
+ resp.getTxtRecord(),
+ resp.getSrcDevice());
+ }
+ } else {
+ Log.e(TAG, "Unhandled resp " + resp);
+ }
+ }
+
+ @UnsupportedAppUsage
+ private int putListener(Object listener) {
+ if (listener == null) return INVALID_LISTENER_KEY;
+ int key;
+ synchronized (mListenerMapLock) {
+ do {
+ key = mListenerKey++;
+ } while (key == INVALID_LISTENER_KEY);
+ mListenerMap.put(key, listener);
+ }
+ return key;
+ }
+
+ private Object getListener(int key) {
+ if (key == INVALID_LISTENER_KEY) return null;
+ synchronized (mListenerMapLock) {
+ return mListenerMap.remove(key);
+ }
+ }
+ }
+
+ private static void checkChannel(Channel c) {
+ if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
+ }
+
+ private static void checkServiceInfo(WifiP2pServiceInfo info) {
+ if (info == null) throw new IllegalArgumentException("service info is null");
+ }
+
+ private static void checkServiceRequest(WifiP2pServiceRequest req) {
+ if (req == null) throw new IllegalArgumentException("service request is null");
+ }
+
+ private static void checkP2pConfig(WifiP2pConfig c) {
+ if (c == null) throw new IllegalArgumentException("config cannot be null");
+ if (TextUtils.isEmpty(c.deviceAddress)) {
+ throw new IllegalArgumentException("deviceAddress cannot be empty");
+ }
+ }
+
+ /**
+ * Registers the application with the Wi-Fi framework. This function
+ * must be the first to be called before any p2p operations are performed.
+ *
+ * @param srcContext is the context of the source
+ * @param srcLooper is the Looper on which the callbacks are receivied
+ * @param listener for callback at loss of framework communication. Can be null.
+ * @return Channel instance that is necessary for performing any further p2p operations
+ */
+ public Channel initialize(Context srcContext, Looper srcLooper, ChannelListener listener) {
+ Binder binder = new Binder();
+ Channel channel = initalizeChannel(srcContext, srcLooper, listener, getMessenger(binder),
+ binder);
+ return channel;
+ }
+
+ /**
+ * Registers the application with the Wi-Fi framework. Enables system-only functionality.
+ * @hide
+ */
+ public Channel initializeInternal(Context srcContext, Looper srcLooper,
+ ChannelListener listener) {
+ return initalizeChannel(srcContext, srcLooper, listener, getP2pStateMachineMessenger(),
+ null);
+ }
+
+ private Channel initalizeChannel(Context srcContext, Looper srcLooper, ChannelListener listener,
+ Messenger messenger, Binder binder) {
+ if (messenger == null) return null;
+
+ Channel c = new Channel(srcContext, srcLooper, listener, binder, this);
+ if (c.mAsyncChannel.connectSync(srcContext, c.mHandler, messenger)
+ == AsyncChannel.STATUS_SUCCESSFUL) {
+ Bundle bundle = new Bundle();
+ bundle.putString(CALLING_PACKAGE, c.mContext.getOpPackageName());
+ bundle.putBinder(CALLING_BINDER, binder);
+ c.mAsyncChannel.sendMessage(UPDATE_CHANNEL_INFO, 0,
+ c.putListener(null), bundle);
+ return c;
+ } else {
+ c.close();
+ return null;
+ }
+ }
+
+ /**
+ * Initiate peer discovery. A discovery process involves scanning for available Wi-Fi peers
+ * for the purpose of establishing a connection.
+ *
+ * <p> The function call immediately returns after sending a discovery request
+ * to the framework. The application is notified of a success or failure to initiate
+ * discovery through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * <p> The discovery remains active until a connection is initiated or
+ * a p2p group is formed. Register for {@link #WIFI_P2P_PEERS_CHANGED_ACTION} intent to
+ * determine when the framework notifies of a change as peers are discovered.
+ *
+ * <p> Upon receiving a {@link #WIFI_P2P_PEERS_CHANGED_ACTION} intent, an application
+ * can request for the list of peers using {@link #requestPeers}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callbacks on success or failure. Can be null.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
+ public void discoverPeers(Channel c, ActionListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(DISCOVER_PEERS, 0, c.putListener(listener));
+ }
+
+ /**
+ * Stop an ongoing peer discovery
+ *
+ * <p> The function call immediately returns after sending a stop request
+ * to the framework. The application is notified of a success or failure to initiate
+ * stop through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callbacks on success or failure. Can be null.
+ */
+ public void stopPeerDiscovery(Channel c, ActionListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(STOP_DISCOVERY, 0, c.putListener(listener));
+ }
+
+ /**
+ * Start a p2p connection to a device with the specified configuration.
+ *
+ * <p> The function call immediately returns after sending a connection request
+ * to the framework. The application is notified of a success or failure to initiate
+ * connect through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * <p> Register for {@link #WIFI_P2P_CONNECTION_CHANGED_ACTION} intent to
+ * determine when the framework notifies of a change in connectivity.
+ *
+ * <p> If the current device is not part of a p2p group, a connect request initiates
+ * a group negotiation with the peer.
+ *
+ * <p> If the current device is part of an existing p2p group or has created
+ * a p2p group with {@link #createGroup}, an invitation to join the group is sent to
+ * the peer device.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param config options as described in {@link WifiP2pConfig} class
+ * @param listener for callbacks on success or failure. Can be null.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
+ public void connect(Channel c, WifiP2pConfig config, ActionListener listener) {
+ checkChannel(c);
+ checkP2pConfig(config);
+ c.mAsyncChannel.sendMessage(CONNECT, 0, c.putListener(listener), config);
+ }
+
+ /**
+ * Cancel any ongoing p2p group negotiation
+ *
+ * <p> The function call immediately returns after sending a connection cancellation request
+ * to the framework. The application is notified of a success or failure to initiate
+ * cancellation through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callbacks on success or failure. Can be null.
+ */
+ public void cancelConnect(Channel c, ActionListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(CANCEL_CONNECT, 0, c.putListener(listener));
+ }
+
+ /**
+ * Create a p2p group with the current device as the group owner. This essentially creates
+ * an access point that can accept connections from legacy clients as well as other p2p
+ * devices.
+ *
+ * <p class="note"><strong>Note:</strong>
+ * This function would normally not be used unless the current device needs
+ * to form a p2p connection with a legacy client
+ *
+ * <p> The function call immediately returns after sending a group creation request
+ * to the framework. The application is notified of a success or failure to initiate
+ * group creation through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * <p> Application can request for the group details with {@link #requestGroupInfo}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callbacks on success or failure. Can be null.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
+ public void createGroup(Channel c, ActionListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(CREATE_GROUP, WifiP2pGroup.PERSISTENT_NET_ID,
+ c.putListener(listener));
+ }
+
+ /**
+ * Create a p2p group with the current device as the group owner. This essentially creates
+ * an access point that can accept connections from legacy clients as well as other p2p
+ * devices.
+ *
+ * <p> An app should use {@link WifiP2pConfig.Builder} to build the configuration
+ * for a group.
+ *
+ * <p class="note"><strong>Note:</strong>
+ * This function would normally not be used unless the current device needs
+ * to form a p2p group as a Group Owner and allow peers to join it as either
+ * Group Clients or legacy Wi-Fi STAs.
+ *
+ * <p> The function call immediately returns after sending a group creation request
+ * to the framework. The application is notified of a success or failure to initiate
+ * group creation through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * <p> Application can request for the group details with {@link #requestGroupInfo}.
+ *
+ * @param c is the channel created at {@link #initialize}.
+ * @param config the configuration of a p2p group.
+ * @param listener for callbacks on success or failure. Can be null.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
+ public void createGroup(@NonNull Channel c,
+ @Nullable WifiP2pConfig config,
+ @Nullable ActionListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(CREATE_GROUP, 0,
+ c.putListener(listener), config);
+ }
+
+ /**
+ * Remove the current p2p group.
+ *
+ * <p> The function call immediately returns after sending a group removal request
+ * to the framework. The application is notified of a success or failure to initiate
+ * group removal through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callbacks on success or failure. Can be null.
+ */
+ public void removeGroup(Channel c, ActionListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(REMOVE_GROUP, 0, c.putListener(listener));
+ }
+
+ /**
+ * Force p2p to enter or exit listen state
+ *
+ * @param c is the channel created at {@link #initialize(Context, Looper, ChannelListener)}
+ * @param enable enables or disables listening
+ * @param listener for callbacks on success or failure. Can be null.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public void listen(Channel c, boolean enable, ActionListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(enable ? START_LISTEN : STOP_LISTEN,
+ 0, c.putListener(listener));
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public void setWifiP2pChannels(Channel c, int lc, int oc, ActionListener listener) {
+ checkChannel(c);
+ Bundle p2pChannels = new Bundle();
+ p2pChannels.putInt("lc", lc);
+ p2pChannels.putInt("oc", oc);
+ c.mAsyncChannel.sendMessage(SET_CHANNEL, 0, c.putListener(listener), p2pChannels);
+ }
+
+ /**
+ * Start a Wi-Fi Protected Setup (WPS) session.
+ *
+ * <p> The function call immediately returns after sending a request to start a
+ * WPS session. Currently, this is only valid if the current device is running
+ * as a group owner to allow any new clients to join the group. The application
+ * is notified of a success or failure to initiate WPS through listener callbacks
+ * {@link ActionListener#onSuccess} or {@link ActionListener#onFailure}.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void startWps(Channel c, WpsInfo wps, ActionListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(START_WPS, 0, c.putListener(listener), wps);
+ }
+
+ /**
+ * Register a local service for service discovery. If a local service is registered,
+ * the framework automatically responds to a service discovery request from a peer.
+ *
+ * <p> The function call immediately returns after sending a request to add a local
+ * service to the framework. The application is notified of a success or failure to
+ * add service through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * <p>The service information is set through {@link WifiP2pServiceInfo}.<br>
+ * or its subclass calls {@link WifiP2pUpnpServiceInfo#newInstance} or
+ * {@link WifiP2pDnsSdServiceInfo#newInstance} for a Upnp or Bonjour service
+ * respectively
+ *
+ * <p>The service information can be cleared with calls to
+ * {@link #removeLocalService} or {@link #clearLocalServices}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param servInfo is a local service information.
+ * @param listener for callbacks on success or failure. Can be null.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
+ public void addLocalService(Channel c, WifiP2pServiceInfo servInfo, ActionListener listener) {
+ checkChannel(c);
+ checkServiceInfo(servInfo);
+ c.mAsyncChannel.sendMessage(ADD_LOCAL_SERVICE, 0, c.putListener(listener), servInfo);
+ }
+
+ /**
+ * Remove a registered local service added with {@link #addLocalService}
+ *
+ * <p> The function call immediately returns after sending a request to remove a
+ * local service to the framework. The application is notified of a success or failure to
+ * add service through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param servInfo is the local service information.
+ * @param listener for callbacks on success or failure. Can be null.
+ */
+ public void removeLocalService(Channel c, WifiP2pServiceInfo servInfo,
+ ActionListener listener) {
+ checkChannel(c);
+ checkServiceInfo(servInfo);
+ c.mAsyncChannel.sendMessage(REMOVE_LOCAL_SERVICE, 0, c.putListener(listener), servInfo);
+ }
+
+ /**
+ * Clear all registered local services of service discovery.
+ *
+ * <p> The function call immediately returns after sending a request to clear all
+ * local services to the framework. The application is notified of a success or failure to
+ * add service through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callbacks on success or failure. Can be null.
+ */
+ public void clearLocalServices(Channel c, ActionListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(CLEAR_LOCAL_SERVICES, 0, c.putListener(listener));
+ }
+
+ /**
+ * Register a callback to be invoked on receiving service discovery response.
+ * Used only for vendor specific protocol right now. For Bonjour or Upnp, use
+ * {@link #setDnsSdResponseListeners} or {@link #setUpnpServiceResponseListener}
+ * respectively.
+ *
+ * <p> see {@link #discoverServices} for the detail.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callbacks on receiving service discovery response.
+ */
+ public void setServiceResponseListener(Channel c,
+ ServiceResponseListener listener) {
+ checkChannel(c);
+ c.mServRspListener = listener;
+ }
+
+ /**
+ * Register a callback to be invoked on receiving Bonjour service discovery
+ * response.
+ *
+ * <p> see {@link #discoverServices} for the detail.
+ *
+ * @param c
+ * @param servListener is for listening to a Bonjour service response
+ * @param txtListener is for listening to a Bonjour TXT record response
+ */
+ public void setDnsSdResponseListeners(Channel c,
+ DnsSdServiceResponseListener servListener, DnsSdTxtRecordListener txtListener) {
+ checkChannel(c);
+ c.mDnsSdServRspListener = servListener;
+ c.mDnsSdTxtListener = txtListener;
+ }
+
+ /**
+ * Register a callback to be invoked on receiving upnp service discovery
+ * response.
+ *
+ * <p> see {@link #discoverServices} for the detail.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callbacks on receiving service discovery response.
+ */
+ public void setUpnpServiceResponseListener(Channel c,
+ UpnpServiceResponseListener listener) {
+ checkChannel(c);
+ c.mUpnpServRspListener = listener;
+ }
+
+ /**
+ * Initiate service discovery. A discovery process involves scanning for
+ * requested services for the purpose of establishing a connection to a peer
+ * that supports an available service.
+ *
+ * <p> The function call immediately returns after sending a request to start service
+ * discovery to the framework. The application is notified of a success or failure to initiate
+ * discovery through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * <p> The services to be discovered are specified with calls to {@link #addServiceRequest}.
+ *
+ * <p>The application is notified of the response against the service discovery request
+ * through listener callbacks registered by {@link #setServiceResponseListener} or
+ * {@link #setDnsSdResponseListeners}, or {@link #setUpnpServiceResponseListener}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callbacks on success or failure. Can be null.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
+ public void discoverServices(Channel c, ActionListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(DISCOVER_SERVICES, 0, c.putListener(listener));
+ }
+
+ /**
+ * Add a service discovery request.
+ *
+ * <p> The function call immediately returns after sending a request to add service
+ * discovery request to the framework. The application is notified of a success or failure to
+ * add service through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * <p>After service discovery request is added, you can initiate service discovery by
+ * {@link #discoverServices}.
+ *
+ * <p>The added service requests can be cleared with calls to
+ * {@link #removeServiceRequest(Channel, WifiP2pServiceRequest, ActionListener)} or
+ * {@link #clearServiceRequests(Channel, ActionListener)}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param req is the service discovery request.
+ * @param listener for callbacks on success or failure. Can be null.
+ */
+ public void addServiceRequest(Channel c,
+ WifiP2pServiceRequest req, ActionListener listener) {
+ checkChannel(c);
+ checkServiceRequest(req);
+ c.mAsyncChannel.sendMessage(ADD_SERVICE_REQUEST, 0,
+ c.putListener(listener), req);
+ }
+
+ /**
+ * Remove a specified service discovery request added with {@link #addServiceRequest}
+ *
+ * <p> The function call immediately returns after sending a request to remove service
+ * discovery request to the framework. The application is notified of a success or failure to
+ * add service through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param req is the service discovery request.
+ * @param listener for callbacks on success or failure. Can be null.
+ */
+ public void removeServiceRequest(Channel c, WifiP2pServiceRequest req,
+ ActionListener listener) {
+ checkChannel(c);
+ checkServiceRequest(req);
+ c.mAsyncChannel.sendMessage(REMOVE_SERVICE_REQUEST, 0,
+ c.putListener(listener), req);
+ }
+
+ /**
+ * Clear all registered service discovery requests.
+ *
+ * <p> The function call immediately returns after sending a request to clear all
+ * service discovery requests to the framework. The application is notified of a success
+ * or failure to add service through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callbacks on success or failure. Can be null.
+ */
+ public void clearServiceRequests(Channel c, ActionListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(CLEAR_SERVICE_REQUESTS,
+ 0, c.putListener(listener));
+ }
+
+ /**
+ * Request the current list of peers.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callback when peer list is available. Can be null.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
+ public void requestPeers(Channel c, PeerListListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(REQUEST_PEERS, 0, c.putListener(listener));
+ }
+
+ /**
+ * Request device connection info.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callback when connection info is available. Can be null.
+ */
+ public void requestConnectionInfo(Channel c, ConnectionInfoListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(REQUEST_CONNECTION_INFO, 0, c.putListener(listener));
+ }
+
+ /**
+ * Request p2p group info.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callback when group info is available. Can be null.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
+ public void requestGroupInfo(Channel c, GroupInfoListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(REQUEST_GROUP_INFO, 0, c.putListener(listener));
+ }
+
+ /**
+ * Set p2p device name.
+ * @hide
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callback when group info is available. Can be null.
+ */
+ @UnsupportedAppUsage
+ public void setDeviceName(Channel c, String devName, ActionListener listener) {
+ checkChannel(c);
+ WifiP2pDevice d = new WifiP2pDevice();
+ d.deviceName = devName;
+ c.mAsyncChannel.sendMessage(SET_DEVICE_NAME, 0, c.putListener(listener), d);
+ }
+
+ /** @hide */
+ @UnsupportedAppUsage
+ public void setWFDInfo(
+ Channel c, WifiP2pWfdInfo wfdInfo,
+ ActionListener listener) {
+ checkChannel(c);
+ try {
+ mService.checkConfigureWifiDisplayPermission();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ c.mAsyncChannel.sendMessage(SET_WFD_INFO, 0, c.putListener(listener), wfdInfo);
+ }
+
+
+ /**
+ * Delete a stored persistent group from the system settings.
+ *
+ * <p> The function call immediately returns after sending a persistent group removal request
+ * to the framework. The application is notified of a success or failure to initiate
+ * group removal through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * <p>The persistent p2p group list stored in the system can be obtained by
+ * {@link #requestPersistentGroupInfo(Channel, PersistentGroupInfoListener)} and
+ * a network id can be obtained by {@link WifiP2pGroup#getNetworkId()}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param netId he network id of the p2p group.
+ * @param listener for callbacks on success or failure. Can be null.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void deletePersistentGroup(Channel c, int netId, ActionListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(DELETE_PERSISTENT_GROUP, netId, c.putListener(listener));
+ }
+
+ /**
+ * Request a list of all the persistent p2p groups stored in system.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callback when persistent group info list is available. Can be null.
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public void requestPersistentGroupInfo(Channel c, PersistentGroupInfoListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(REQUEST_PERSISTENT_GROUP_INFO, 0, c.putListener(listener));
+ }
+
+ /** @hide */
+ public static final int MIRACAST_DISABLED = 0;
+ /** @hide */
+ public static final int MIRACAST_SOURCE = 1;
+ /** @hide */
+ public static final int MIRACAST_SINK = 2;
+ /** Internal use only @hide */
+ @UnsupportedAppUsage
+ public void setMiracastMode(int mode) {
+ try {
+ mService.setMiracastMode(mode);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get a reference to WifiP2pService handler. This is used to establish
+ * an AsyncChannel communication with WifiService
+ *
+ * @param binder A binder for the service to associate with this client.
+ *
+ * @return Messenger pointing to the WifiP2pService handler
+ * @hide
+ */
+ public Messenger getMessenger(Binder binder) {
+ try {
+ return mService.getMessenger(binder);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get a reference to P2pStateMachine handler. This is used to establish
+ * a priveleged AsyncChannel communication with WifiP2pService.
+ *
+ * @return Messenger pointing to the WifiP2pService handler
+ * @hide
+ */
+ public Messenger getP2pStateMachineMessenger() {
+ try {
+ return mService.getP2pStateMachineMessenger();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get a handover request message for use in WFA NFC Handover transfer.
+ * @hide
+ */
+ public void getNfcHandoverRequest(Channel c, HandoverMessageListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(GET_HANDOVER_REQUEST, 0, c.putListener(listener));
+ }
+
+
+ /**
+ * Get a handover select message for use in WFA NFC Handover transfer.
+ * @hide
+ */
+ public void getNfcHandoverSelect(Channel c, HandoverMessageListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(GET_HANDOVER_SELECT, 0, c.putListener(listener));
+ }
+
+ /**
+ * @hide
+ */
+ public void initiatorReportNfcHandover(Channel c, String handoverSelect,
+ ActionListener listener) {
+ checkChannel(c);
+ Bundle bundle = new Bundle();
+ bundle.putString(EXTRA_HANDOVER_MESSAGE, handoverSelect);
+ c.mAsyncChannel.sendMessage(INITIATOR_REPORT_NFC_HANDOVER, 0,
+ c.putListener(listener), bundle);
+ }
+
+
+ /**
+ * @hide
+ */
+ public void responderReportNfcHandover(Channel c, String handoverRequest,
+ ActionListener listener) {
+ checkChannel(c);
+ Bundle bundle = new Bundle();
+ bundle.putString(EXTRA_HANDOVER_MESSAGE, handoverRequest);
+ c.mAsyncChannel.sendMessage(RESPONDER_REPORT_NFC_HANDOVER, 0,
+ c.putListener(listener), bundle);
+ }
+
+ /**
+ * Removes all saved p2p groups.
+ *
+ * @param c is the channel created at {@link #initialize}.
+ * @param listener for callback on success or failure. Can be null.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public void factoryReset(@NonNull Channel c, @Nullable ActionListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(FACTORY_RESET, 0, c.putListener(listener));
+ }
+
+ /**
+ * Request saved WifiP2pConfig which used for an ongoing peer connection
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callback when ongoing peer config updated. Can't be null.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
+ public void requestOngoingPeerConfig(@NonNull Channel c,
+ @NonNull OngoingPeerInfoListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(REQUEST_ONGOING_PEER_CONFIG,
+ Binder.getCallingUid(), c.putListener(listener));
+ }
+
+ /**
+ * Set saved WifiP2pConfig which used for an ongoing peer connection
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param config used for change an ongoing peer connection
+ * @param listener for callback when ongoing peer config updated. Can be null.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
+ public void setOngoingPeerConfig(@NonNull Channel c, @NonNull WifiP2pConfig config,
+ @Nullable ActionListener listener) {
+ checkChannel(c);
+ checkP2pConfig(config);
+ c.mAsyncChannel.sendMessage(SET_ONGOING_PEER_CONFIG, 0,
+ c.putListener(listener), config);
+ }
+
+ /**
+ * Request p2p enabled state.
+ *
+ * <p> This state indicates whether Wi-Fi p2p is enabled or disabled.
+ * The valid value is one of {@link #WIFI_P2P_STATE_DISABLED} or
+ * {@link #WIFI_P2P_STATE_ENABLED}. The state is returned using the
+ * {@link P2pStateListener} listener.
+ *
+ * <p> This state is also included in the {@link #WIFI_P2P_STATE_CHANGED_ACTION}
+ * broadcast event with extra {@link #EXTRA_WIFI_STATE}.
+ *
+ * @param c is the channel created at {@link #initialize}.
+ * @param listener for callback when p2p state is available..
+ */
+ public void requestP2pState(@NonNull Channel c,
+ @NonNull P2pStateListener listener) {
+ checkChannel(c);
+ if (listener == null) throw new IllegalArgumentException("This listener cannot be null.");
+ c.mAsyncChannel.sendMessage(REQUEST_P2P_STATE, 0, c.putListener(listener));
+ }
+
+ /**
+ * Request p2p discovery state.
+ *
+ * <p> This state indicates whether p2p discovery has started or stopped.
+ * The valid value is one of {@link #WIFI_P2P_DISCOVERY_STARTED} or
+ * {@link #WIFI_P2P_DISCOVERY_STOPPED}. The state is returned using the
+ * {@link DiscoveryStateListener} listener.
+ *
+ * <p> This state is also included in the {@link #WIFI_P2P_DISCOVERY_CHANGED_ACTION}
+ * broadcast event with extra {@link #EXTRA_DISCOVERY_STATE}.
+ *
+ * @param c is the channel created at {@link #initialize}.
+ * @param listener for callback when discovery state is available..
+ */
+ public void requestDiscoveryState(@NonNull Channel c,
+ @NonNull DiscoveryStateListener listener) {
+ checkChannel(c);
+ if (listener == null) throw new IllegalArgumentException("This listener cannot be null.");
+ c.mAsyncChannel.sendMessage(REQUEST_DISCOVERY_STATE, 0, c.putListener(listener));
+ }
+
+ /**
+ * Request network info.
+ *
+ * <p> This method provides the network info in the form of a {@link android.net.NetworkInfo}.
+ * {@link android.net.NetworkInfo#isAvailable()} indicates the p2p availability and
+ * {@link android.net.NetworkInfo#getDetailedState()} reports the current fine-grained state
+ * of the network. This {@link android.net.NetworkInfo} is returned using the
+ * {@link NetworkInfoListener} listener.
+ *
+ * <p> This information is also included in the {@link #WIFI_P2P_CONNECTION_CHANGED_ACTION}
+ * broadcast event with extra {@link #EXTRA_NETWORK_INFO}.
+ *
+ * @param c is the channel created at {@link #initialize}.
+ * @param listener for callback when network info is available..
+ */
+ public void requestNetworkInfo(@NonNull Channel c,
+ @NonNull NetworkInfoListener listener) {
+ checkChannel(c);
+ if (listener == null) throw new IllegalArgumentException("This listener cannot be null.");
+ c.mAsyncChannel.sendMessage(REQUEST_NETWORK_INFO, 0, c.putListener(listener));
+ }
+
+ /**
+ * Request Device Info
+ *
+ * <p> This method provides the device info
+ * in the form of a {@link android.net.wifi.p2p.WifiP2pDevice}.
+ * Valid {@link android.net.wifi.p2p.WifiP2pDevice} is returned when p2p is enabled.
+ * To get information notifications on P2P getting enabled refers
+ * {@link #WIFI_P2P_STATE_ENABLED}.
+ *
+ * <p> This {@link android.net.wifi.p2p.WifiP2pDevice} is returned using the
+ * {@link DeviceInfoListener} listener.
+ *
+ * <p> {@link android.net.wifi.p2p.WifiP2pDevice#deviceAddress} is only available if the caller
+ * holds the {@code android.Manifest.permission#LOCAL_MAC_ADDRESS} permission, and holds the
+ * anonymized MAC address (02:00:00:00:00:00) otherwise.
+ *
+ * <p> This information is also included in the {@link #WIFI_P2P_THIS_DEVICE_CHANGED_ACTION}
+ * broadcast event with extra {@link #EXTRA_WIFI_P2P_DEVICE}.
+ *
+ * @param c is the channel created at {@link #initialize(Context, Looper, ChannelListener)}.
+ * @param listener for callback when network info is available.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
+ public void requestDeviceInfo(@NonNull Channel c, @NonNull DeviceInfoListener listener) {
+ checkChannel(c);
+ if (listener == null) throw new IllegalArgumentException("This listener cannot be null.");
+ c.mAsyncChannel.sendMessage(REQUEST_DEVICE_INFO, 0, c.putListener(listener));
+ }
+}
diff --git a/android/net/wifi/p2p/WifiP2pProvDiscEvent.java b/android/net/wifi/p2p/WifiP2pProvDiscEvent.java
new file mode 100644
index 0000000..153e03c
--- /dev/null
+++ b/android/net/wifi/p2p/WifiP2pProvDiscEvent.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2011 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.p2p;
+
+import android.annotation.UnsupportedAppUsage;
+
+/**
+ * A class representing a Wi-Fi p2p provisional discovery request/response
+ * See {@link #WifiP2pProvDiscEvent} for supported types
+ *
+ * @hide
+ */
+public class WifiP2pProvDiscEvent {
+
+ private static final String TAG = "WifiP2pProvDiscEvent";
+
+ public static final int PBC_REQ = 1;
+ public static final int PBC_RSP = 2;
+ public static final int ENTER_PIN = 3;
+ public static final int SHOW_PIN = 4;
+
+ /* One of PBC_REQ, PBC_RSP, ENTER_PIN or SHOW_PIN */
+ @UnsupportedAppUsage
+ public int event;
+
+ @UnsupportedAppUsage
+ public WifiP2pDevice device;
+
+ /* Valid when event = SHOW_PIN */
+ @UnsupportedAppUsage
+ public String pin;
+
+ @UnsupportedAppUsage
+ public WifiP2pProvDiscEvent() {
+ device = new WifiP2pDevice();
+ }
+
+ /**
+ * @param string formats supported include
+ *
+ * P2P-PROV-DISC-PBC-REQ 42:fc:89:e1:e2:27
+ * P2P-PROV-DISC-PBC-RESP 02:12:47:f2:5a:36
+ * P2P-PROV-DISC-ENTER-PIN 42:fc:89:e1:e2:27
+ * P2P-PROV-DISC-SHOW-PIN 42:fc:89:e1:e2:27 44490607
+ *
+ * Note: The events formats can be looked up in the wpa_supplicant code
+ * @hide
+ */
+ public WifiP2pProvDiscEvent(String string) throws IllegalArgumentException {
+ String[] tokens = string.split(" ");
+
+ if (tokens.length < 2) {
+ throw new IllegalArgumentException("Malformed event " + string);
+ }
+
+ if (tokens[0].endsWith("PBC-REQ")) event = PBC_REQ;
+ else if (tokens[0].endsWith("PBC-RESP")) event = PBC_RSP;
+ else if (tokens[0].endsWith("ENTER-PIN")) event = ENTER_PIN;
+ else if (tokens[0].endsWith("SHOW-PIN")) event = SHOW_PIN;
+ else throw new IllegalArgumentException("Malformed event " + string);
+
+
+ device = new WifiP2pDevice();
+ device.deviceAddress = tokens[1];
+
+ if (event == SHOW_PIN) {
+ pin = tokens[2];
+ }
+ }
+
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append(device);
+ sbuf.append("\n event: ").append(event);
+ sbuf.append("\n pin: ").append(pin);
+ return sbuf.toString();
+ }
+}
diff --git a/android/net/wifi/p2p/WifiP2pWfdInfo.java b/android/net/wifi/p2p/WifiP2pWfdInfo.java
new file mode 100644
index 0000000..3caa280
--- /dev/null
+++ b/android/net/wifi/p2p/WifiP2pWfdInfo.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2012 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.p2p;
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcelable;
+import android.os.Parcel;
+
+import java.util.Locale;
+
+/**
+ * A class representing Wifi Display information for a device
+ * @hide
+ */
+public class WifiP2pWfdInfo implements Parcelable {
+
+ private static final String TAG = "WifiP2pWfdInfo";
+
+ private boolean mWfdEnabled;
+
+ private int mDeviceInfo;
+
+ public static final int WFD_SOURCE = 0;
+ public static final int PRIMARY_SINK = 1;
+ public static final int SECONDARY_SINK = 2;
+ public static final int SOURCE_OR_PRIMARY_SINK = 3;
+
+ /* Device information bitmap */
+ /** One of {@link #WFD_SOURCE}, {@link #PRIMARY_SINK}, {@link #SECONDARY_SINK}
+ * or {@link #SOURCE_OR_PRIMARY_SINK}
+ */
+ private static final int DEVICE_TYPE = 0x3;
+ private static final int COUPLED_SINK_SUPPORT_AT_SOURCE = 0x4;
+ private static final int COUPLED_SINK_SUPPORT_AT_SINK = 0x8;
+ private static final int SESSION_AVAILABLE = 0x30;
+ private static final int SESSION_AVAILABLE_BIT1 = 0x10;
+ private static final int SESSION_AVAILABLE_BIT2 = 0x20;
+
+ private int mCtrlPort;
+
+ private int mMaxThroughput;
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public WifiP2pWfdInfo() {
+ }
+
+ @UnsupportedAppUsage
+ public WifiP2pWfdInfo(int devInfo, int ctrlPort, int maxTput) {
+ mWfdEnabled = true;
+ mDeviceInfo = devInfo;
+ mCtrlPort = ctrlPort;
+ mMaxThroughput = maxTput;
+ }
+
+ @UnsupportedAppUsage
+ public boolean isWfdEnabled() {
+ return mWfdEnabled;
+ }
+
+ @UnsupportedAppUsage
+ public void setWfdEnabled(boolean enabled) {
+ mWfdEnabled = enabled;
+ }
+
+ @UnsupportedAppUsage
+ public int getDeviceType() {
+ return (mDeviceInfo & DEVICE_TYPE);
+ }
+
+ @UnsupportedAppUsage
+ public boolean setDeviceType(int deviceType) {
+ if (deviceType >= WFD_SOURCE && deviceType <= SOURCE_OR_PRIMARY_SINK) {
+ mDeviceInfo &= ~DEVICE_TYPE;
+ mDeviceInfo |= deviceType;
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isCoupledSinkSupportedAtSource() {
+ return (mDeviceInfo & COUPLED_SINK_SUPPORT_AT_SINK) != 0;
+ }
+
+ public void setCoupledSinkSupportAtSource(boolean enabled) {
+ if (enabled ) {
+ mDeviceInfo |= COUPLED_SINK_SUPPORT_AT_SINK;
+ } else {
+ mDeviceInfo &= ~COUPLED_SINK_SUPPORT_AT_SINK;
+ }
+ }
+
+ public boolean isCoupledSinkSupportedAtSink() {
+ return (mDeviceInfo & COUPLED_SINK_SUPPORT_AT_SINK) != 0;
+ }
+
+ public void setCoupledSinkSupportAtSink(boolean enabled) {
+ if (enabled ) {
+ mDeviceInfo |= COUPLED_SINK_SUPPORT_AT_SINK;
+ } else {
+ mDeviceInfo &= ~COUPLED_SINK_SUPPORT_AT_SINK;
+ }
+ }
+
+ public boolean isSessionAvailable() {
+ return (mDeviceInfo & SESSION_AVAILABLE) != 0;
+ }
+
+ @UnsupportedAppUsage
+ public void setSessionAvailable(boolean enabled) {
+ if (enabled) {
+ mDeviceInfo |= SESSION_AVAILABLE_BIT1;
+ mDeviceInfo &= ~SESSION_AVAILABLE_BIT2;
+ } else {
+ mDeviceInfo &= ~SESSION_AVAILABLE;
+ }
+ }
+
+ public int getControlPort() {
+ return mCtrlPort;
+ }
+
+ @UnsupportedAppUsage
+ public void setControlPort(int port) {
+ mCtrlPort = port;
+ }
+
+ @UnsupportedAppUsage
+ public void setMaxThroughput(int maxThroughput) {
+ mMaxThroughput = maxThroughput;
+ }
+
+ public int getMaxThroughput() {
+ return mMaxThroughput;
+ }
+
+ public String getDeviceInfoHex() {
+ return String.format(
+ Locale.US, "%04x%04x%04x", mDeviceInfo, mCtrlPort, mMaxThroughput);
+ }
+
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append("WFD enabled: ").append(mWfdEnabled);
+ sbuf.append("WFD DeviceInfo: ").append(mDeviceInfo);
+ sbuf.append("\n WFD CtrlPort: ").append(mCtrlPort);
+ sbuf.append("\n WFD MaxThroughput: ").append(mMaxThroughput);
+ return sbuf.toString();
+ }
+
+ /** Implement the Parcelable interface */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** copy constructor */
+ @UnsupportedAppUsage
+ public WifiP2pWfdInfo(WifiP2pWfdInfo source) {
+ if (source != null) {
+ mWfdEnabled = source.mWfdEnabled;
+ mDeviceInfo = source.mDeviceInfo;
+ mCtrlPort = source.mCtrlPort;
+ mMaxThroughput = source.mMaxThroughput;
+ }
+ }
+
+ /** Implement the Parcelable interface */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mWfdEnabled ? 1 : 0);
+ dest.writeInt(mDeviceInfo);
+ dest.writeInt(mCtrlPort);
+ dest.writeInt(mMaxThroughput);
+ }
+
+ public void readFromParcel(Parcel in) {
+ mWfdEnabled = (in.readInt() == 1);
+ mDeviceInfo = in.readInt();
+ mCtrlPort = in.readInt();
+ mMaxThroughput = in.readInt();
+ }
+
+ /** Implement the Parcelable interface */
+ @UnsupportedAppUsage
+ public static final @android.annotation.NonNull Creator<WifiP2pWfdInfo> CREATOR =
+ new Creator<WifiP2pWfdInfo>() {
+ public WifiP2pWfdInfo createFromParcel(Parcel in) {
+ WifiP2pWfdInfo device = new WifiP2pWfdInfo();
+ device.readFromParcel(in);
+ return device;
+ }
+
+ public WifiP2pWfdInfo[] newArray(int size) {
+ return new WifiP2pWfdInfo[size];
+ }
+ };
+}
diff --git a/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java b/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java
new file mode 100644
index 0000000..e32c8e8
--- /dev/null
+++ b/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2012 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.p2p.nsd;
+
+import android.annotation.UnsupportedAppUsage;
+import android.net.nsd.DnsSdTxtRecord;
+import android.os.Build;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * A class for storing Bonjour service information that is advertised
+ * over a Wi-Fi peer-to-peer setup.
+ *
+ * {@see android.net.wifi.p2p.WifiP2pManager#addLocalService}
+ * {@see android.net.wifi.p2p.WifiP2pManager#removeLocalService}
+ * {@see WifiP2pServiceInfo}
+ * {@see WifiP2pUpnpServiceInfo}
+ */
+public class WifiP2pDnsSdServiceInfo extends WifiP2pServiceInfo {
+
+ /**
+ * Bonjour version 1.
+ * @hide
+ */
+ public static final int VERSION_1 = 0x01;
+
+ /**
+ * Pointer record.
+ * @hide
+ */
+ public static final int DNS_TYPE_PTR = 12;
+
+ /**
+ * Text record.
+ * @hide
+ */
+ public static final int DNS_TYPE_TXT = 16;
+
+ /**
+ * virtual memory packet.
+ * see E.3 of the Wi-Fi Direct technical specification for the detail.<br>
+ * Key: domain name Value: pointer address.<br>
+ */
+ private final static Map<String, String> sVmPacket;
+
+ static {
+ sVmPacket = new HashMap<String, String>();
+ sVmPacket.put("_tcp.local.", "c00c");
+ sVmPacket.put("local.", "c011");
+ sVmPacket.put("_udp.local.", "c01c");
+ }
+
+ /**
+ * This constructor is only used in newInstance().
+ *
+ * @param queryList
+ */
+ private WifiP2pDnsSdServiceInfo(List<String> queryList) {
+ super(queryList);
+ }
+
+ /**
+ * Create a Bonjour service information object.
+ *
+ * @param instanceName instance name.<br>
+ * e.g) "MyPrinter"
+ * @param serviceType service type.<br>
+ * e.g) "_ipp._tcp"
+ * @param txtMap TXT record with key/value pair in a map confirming to format defined at
+ * http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt
+ * @return Bonjour service information object
+ */
+ public static WifiP2pDnsSdServiceInfo newInstance(String instanceName,
+ String serviceType, Map<String, String> txtMap) {
+ if (TextUtils.isEmpty(instanceName) || TextUtils.isEmpty(serviceType)) {
+ throw new IllegalArgumentException(
+ "instance name or service type cannot be empty");
+ }
+
+ DnsSdTxtRecord txtRecord = new DnsSdTxtRecord();
+ if (txtMap != null) {
+ for (String key : txtMap.keySet()) {
+ txtRecord.set(key, txtMap.get(key));
+ }
+ }
+
+ ArrayList<String> queries = new ArrayList<String>();
+ queries.add(createPtrServiceQuery(instanceName, serviceType));
+ queries.add(createTxtServiceQuery(instanceName, serviceType, txtRecord));
+
+ return new WifiP2pDnsSdServiceInfo(queries);
+ }
+
+ /**
+ * Create wpa_supplicant service query for PTR record.
+ *
+ * @param instanceName instance name.<br>
+ * e.g) "MyPrinter"
+ * @param serviceType service type.<br>
+ * e.g) "_ipp._tcp"
+ * @return wpa_supplicant service query.
+ */
+ private static String createPtrServiceQuery(String instanceName,
+ String serviceType) {
+
+ StringBuffer sb = new StringBuffer();
+ sb.append("bonjour ");
+ sb.append(createRequest(serviceType + ".local.", DNS_TYPE_PTR, VERSION_1));
+ sb.append(" ");
+
+ byte[] data = instanceName.getBytes();
+ sb.append(String.format(Locale.US, "%02x", data.length));
+ sb.append(WifiP2pServiceInfo.bin2HexStr(data));
+ // This is the start point of this response.
+ // Therefore, it indicates the request domain name.
+ sb.append("c027");
+ return sb.toString();
+ }
+
+ /**
+ * Create wpa_supplicant service query for TXT record.
+ *
+ * @param instanceName instance name.<br>
+ * e.g) "MyPrinter"
+ * @param serviceType service type.<br>
+ * e.g) "_ipp._tcp"
+ * @param txtRecord TXT record.<br>
+ * @return wpa_supplicant service query.
+ */
+ private static String createTxtServiceQuery(String instanceName,
+ String serviceType,
+ DnsSdTxtRecord txtRecord) {
+
+
+ StringBuffer sb = new StringBuffer();
+ sb.append("bonjour ");
+
+ sb.append(createRequest((instanceName + "." + serviceType + ".local."),
+ DNS_TYPE_TXT, VERSION_1));
+ sb.append(" ");
+ byte[] rawData = txtRecord.getRawData();
+ if (rawData.length == 0) {
+ sb.append("00");
+ } else {
+ sb.append(bin2HexStr(rawData));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Create bonjour service discovery request.
+ *
+ * @param dnsName dns name
+ * @param dnsType dns type
+ * @param version version number
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ static String createRequest(String dnsName, int dnsType, int version) {
+ StringBuffer sb = new StringBuffer();
+
+ /*
+ * The request format is as follows.
+ * ________________________________________________
+ * | Encoded and Compressed dns name (variable) |
+ * ________________________________________________
+ * | Type (2) | Version (1) |
+ */
+ if (dnsType == WifiP2pDnsSdServiceInfo.DNS_TYPE_TXT) {
+ dnsName = dnsName.toLowerCase(Locale.ROOT); // TODO: is this right?
+ }
+ sb.append(compressDnsName(dnsName));
+ sb.append(String.format(Locale.US, "%04x", dnsType));
+ sb.append(String.format(Locale.US, "%02x", version));
+
+ return sb.toString();
+ }
+
+ /**
+ * Compress DNS data.
+ *
+ * see E.3 of the Wi-Fi Direct technical specification for the detail.
+ *
+ * @param dnsName dns name
+ * @return compressed dns name
+ */
+ private static String compressDnsName(String dnsName) {
+ StringBuffer sb = new StringBuffer();
+
+ // The domain name is replaced with a pointer to a prior
+ // occurrence of the same name in virtual memory packet.
+ while (true) {
+ String data = sVmPacket.get(dnsName);
+ if (data != null) {
+ sb.append(data);
+ break;
+ }
+ int i = dnsName.indexOf('.');
+ if (i == -1) {
+ if (dnsName.length() > 0) {
+ sb.append(String.format(Locale.US, "%02x", dnsName.length()));
+ sb.append(WifiP2pServiceInfo.bin2HexStr(dnsName.getBytes()));
+ }
+ // for a sequence of labels ending in a zero octet
+ sb.append("00");
+ break;
+ }
+
+ String name = dnsName.substring(0, i);
+ dnsName = dnsName.substring(i + 1);
+ sb.append(String.format(Locale.US, "%02x", name.length()));
+ sb.append(WifiP2pServiceInfo.bin2HexStr(name.getBytes()));
+ }
+ return sb.toString();
+ }
+}
diff --git a/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceRequest.java b/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceRequest.java
new file mode 100644
index 0000000..d5415e0
--- /dev/null
+++ b/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceRequest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2012 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.p2p.nsd;
+
+import android.net.wifi.p2p.WifiP2pManager;
+
+/**
+ * A class for creating a Bonjour service discovery request for use with
+ * {@link WifiP2pManager#addServiceRequest} and {@link WifiP2pManager#removeServiceRequest}
+ *
+ * {@see WifiP2pManager}
+ * {@see WifiP2pServiceRequest}
+ * {@see WifiP2pUpnpServiceRequest}
+ */
+public class WifiP2pDnsSdServiceRequest extends WifiP2pServiceRequest {
+
+ /**
+ * This constructor is only used in newInstance().
+ *
+ * @param query The part of service specific query.
+ * @hide
+ */
+ private WifiP2pDnsSdServiceRequest(String query) {
+ super(WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR, query);
+ }
+
+ /**
+ * This constructor is only used in newInstance().
+ * @hide
+ */
+ private WifiP2pDnsSdServiceRequest() {
+ super(WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR, null);
+ }
+
+ private WifiP2pDnsSdServiceRequest(String dnsQuery, int dnsType, int version) {
+ super(WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR, WifiP2pDnsSdServiceInfo.createRequest(
+ dnsQuery,
+ dnsType,
+ version));
+ }
+
+ /**
+ * Create a service discovery request to search all Bonjour services.
+ *
+ * @return service request for Bonjour.
+ */
+ public static WifiP2pDnsSdServiceRequest newInstance() {
+ return new WifiP2pDnsSdServiceRequest();
+ }
+
+ /**
+ * Create a service discovery to search for Bonjour services with the specified
+ * service type.
+ *
+ * @param serviceType service type. Cannot be null <br>
+ * "_afpovertcp._tcp."(Apple File Sharing over TCP)<br>
+ * "_ipp._tcp" (IP Printing over TCP)<br>
+ * "_http._tcp" (http service)
+ * @return service request for DnsSd.
+ */
+ public static WifiP2pDnsSdServiceRequest newInstance(String serviceType) {
+ if (serviceType == null) {
+ throw new IllegalArgumentException("service type cannot be null");
+ }
+ return new WifiP2pDnsSdServiceRequest(serviceType + ".local.",
+ WifiP2pDnsSdServiceInfo.DNS_TYPE_PTR,
+ WifiP2pDnsSdServiceInfo.VERSION_1);
+ }
+
+ /**
+ * Create a service discovery request to get the TXT data from the specified
+ * Bonjour service.
+ *
+ * @param instanceName instance name. Cannot be null. <br>
+ * "MyPrinter"
+ * @param serviceType service type. Cannot be null. <br>
+ * e.g) <br>
+ * "_afpovertcp._tcp"(Apple File Sharing over TCP)<br>
+ * "_ipp._tcp" (IP Printing over TCP)<br>
+ * @return service request for Bonjour.
+ */
+ public static WifiP2pDnsSdServiceRequest newInstance(String instanceName,
+ String serviceType) {
+ if (instanceName == null || serviceType == null) {
+ throw new IllegalArgumentException(
+ "instance name or service type cannot be null");
+ }
+ String fullDomainName = instanceName + "." + serviceType + ".local.";
+ return new WifiP2pDnsSdServiceRequest(fullDomainName,
+ WifiP2pDnsSdServiceInfo.DNS_TYPE_TXT,
+ WifiP2pDnsSdServiceInfo.VERSION_1);
+ }
+}
diff --git a/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceResponse.java b/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceResponse.java
new file mode 100644
index 0000000..ed84a1a
--- /dev/null
+++ b/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceResponse.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2012 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.p2p.nsd;
+
+import android.net.wifi.p2p.WifiP2pDevice;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A class for a response of bonjour service discovery.
+ *
+ * @hide
+ */
+public class WifiP2pDnsSdServiceResponse extends WifiP2pServiceResponse {
+
+ /**
+ * DNS query name.
+ * e.g)
+ * for PTR
+ * "_ipp._tcp.local."
+ * for TXT
+ * "MyPrinter._ipp._tcp.local."
+ */
+ private String mDnsQueryName;
+
+ /**
+ * Service instance name.
+ * e.g) "MyPrinter"
+ * This field is only used when the dns type equals to
+ * {@link WifiP2pDnsSdServiceInfo#DNS_TYPE_PTR}.
+ */
+ private String mInstanceName;
+
+ /**
+ * DNS Type.
+ * Should be {@link WifiP2pDnsSdServiceInfo#DNS_TYPE_PTR} or
+ * {@link WifiP2pDnsSdServiceInfo#DNS_TYPE_TXT}.
+ */
+ private int mDnsType;
+
+ /**
+ * DnsSd version number.
+ * Should be {@link WifiP2pDnsSdServiceInfo#VERSION_1}.
+ */
+ private int mVersion;
+
+ /**
+ * Txt record.
+ * This field is only used when the dns type equals to
+ * {@link WifiP2pDnsSdServiceInfo#DNS_TYPE_TXT}.
+ */
+ private final HashMap<String, String> mTxtRecord = new HashMap<String, String>();
+
+ /**
+ * Virtual memory packet.
+ * see E.3 of the Wi-Fi Direct technical specification for the detail.<br>
+ * The spec can be obtained from wi-fi.org
+ * Key: pointer Value: domain name.<br>
+ */
+ private final static Map<Integer, String> sVmpack;
+
+ static {
+ sVmpack = new HashMap<Integer, String>();
+ sVmpack.put(0x0c, "_tcp.local.");
+ sVmpack.put(0x11, "local.");
+ sVmpack.put(0x1c, "_udp.local.");
+ }
+
+ /**
+ * Returns query DNS name.
+ * @return DNS name.
+ */
+ public String getDnsQueryName() {
+ return mDnsQueryName;
+ }
+
+ /**
+ * Return query DNS type.
+ * @return DNS type.
+ */
+ public int getDnsType() {
+ return mDnsType;
+ }
+
+ /**
+ * Return bonjour version number.
+ * @return version number.
+ */
+ public int getVersion() {
+ return mVersion;
+ }
+
+ /**
+ * Return instance name.
+ * @return
+ */
+ public String getInstanceName() {
+ return mInstanceName;
+ }
+
+ /**
+ * Return TXT record data.
+ * @return TXT record data.
+ */
+ public Map<String, String> getTxtRecord() {
+ return mTxtRecord;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append("serviceType:DnsSd(").append(mServiceType).append(")");
+ sbuf.append(" status:").append(Status.toString(mStatus));
+ sbuf.append(" srcAddr:").append(mDevice.deviceAddress);
+ sbuf.append(" version:").append(String.format("%02x", mVersion));
+ sbuf.append(" dnsName:").append(mDnsQueryName);
+ sbuf.append(" TxtRecord:");
+ for (String key : mTxtRecord.keySet()) {
+ sbuf.append(" key:").append(key).append(" value:").append(mTxtRecord.get(key));
+ }
+ if (mInstanceName != null) {
+ sbuf.append(" InsName:").append(mInstanceName);
+ }
+ return sbuf.toString();
+ }
+
+ /**
+ * This is only used in framework.
+ * @param status status code.
+ * @param dev source device.
+ * @param data RDATA.
+ * @hide
+ */
+ protected WifiP2pDnsSdServiceResponse(int status,
+ int tranId, WifiP2pDevice dev, byte[] data) {
+ super(WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR,
+ status, tranId, dev, data);
+ if (!parse()) {
+ throw new IllegalArgumentException("Malformed bonjour service response");
+ }
+ }
+
+ /**
+ * Parse DnsSd service discovery response.
+ *
+ * @return {@code true} if the operation succeeded
+ */
+ private boolean parse() {
+ /*
+ * The data format from Wi-Fi Direct spec is as follows.
+ * ________________________________________________
+ * | encoded and compressed dns name (variable) |
+ * ________________________________________________
+ * | dnstype(2byte) | version(1byte) |
+ * ________________________________________________
+ * | RDATA (variable) |
+ */
+ if (mData == null) {
+ // the empty is OK.
+ return true;
+ }
+
+ DataInputStream dis = new DataInputStream(new ByteArrayInputStream(mData));
+
+ mDnsQueryName = readDnsName(dis);
+ if (mDnsQueryName == null) {
+ return false;
+ }
+
+ try {
+ mDnsType = dis.readUnsignedShort();
+ mVersion = dis.readUnsignedByte();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+
+ if (mDnsType == WifiP2pDnsSdServiceInfo.DNS_TYPE_PTR) {
+ String rData = readDnsName(dis);
+ if (rData == null) {
+ return false;
+ }
+ if (rData.length() <= mDnsQueryName.length()) {
+ return false;
+ }
+
+ mInstanceName = rData.substring(0,
+ rData.length() - mDnsQueryName.length() -1);
+ } else if (mDnsType == WifiP2pDnsSdServiceInfo.DNS_TYPE_TXT) {
+ return readTxtData(dis);
+ } else {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Read dns name.
+ *
+ * @param dis data input stream.
+ * @return dns name
+ */
+ private String readDnsName(DataInputStream dis) {
+ StringBuffer sb = new StringBuffer();
+
+ // copy virtual memory packet.
+ HashMap<Integer, String> vmpack = new HashMap<Integer, String>(sVmpack);
+ if (mDnsQueryName != null) {
+ vmpack.put(0x27, mDnsQueryName);
+ }
+ try {
+ while (true) {
+ int i = dis.readUnsignedByte();
+ if (i == 0x00) {
+ return sb.toString();
+ } else if (i == 0xc0) {
+ // refer to pointer.
+ String ref = vmpack.get(dis.readUnsignedByte());
+ if (ref == null) {
+ //invalid.
+ return null;
+ }
+ sb.append(ref);
+ return sb.toString();
+ } else {
+ byte[] data = new byte[i];
+ dis.readFully(data);
+ sb.append(new String(data));
+ sb.append(".");
+ }
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * Read TXT record data.
+ *
+ * @param dis
+ * @return true if TXT data is valid
+ */
+ private boolean readTxtData(DataInputStream dis) {
+ try {
+ while (dis.available() > 0) {
+ int len = dis.readUnsignedByte();
+ if (len == 0) {
+ break;
+ }
+ byte[] data = new byte[len];
+ dis.readFully(data);
+ String[] keyVal = new String(data).split("=");
+ if (keyVal.length != 2) {
+ return false;
+ }
+ mTxtRecord.put(keyVal[0], keyVal[1]);
+ }
+ return true;
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+ /**
+ * Creates DnsSd service response.
+ * This is only called from WifiP2pServiceResponse
+ *
+ * @param status status code.
+ * @param dev source device.
+ * @param data DnsSd response data.
+ * @return DnsSd service response data.
+ * @hide
+ */
+ static WifiP2pDnsSdServiceResponse newInstance(int status,
+ int transId, WifiP2pDevice dev, byte[] data) {
+ if (status != WifiP2pServiceResponse.Status.SUCCESS) {
+ return new WifiP2pDnsSdServiceResponse(status,
+ transId, dev, null);
+ }
+ try {
+ return new WifiP2pDnsSdServiceResponse(status,
+ transId, dev, data);
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+}
diff --git a/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.java b/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.java
new file mode 100644
index 0000000..db0bdb8
--- /dev/null
+++ b/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2012 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.p2p.nsd;
+
+import android.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class for storing service information that is advertised
+ * over a Wi-Fi peer-to-peer setup
+ *
+ * @see WifiP2pUpnpServiceInfo
+ * @see WifiP2pDnsSdServiceInfo
+ */
+public class WifiP2pServiceInfo implements Parcelable {
+
+ /**
+ * All service protocol types.
+ */
+ public static final int SERVICE_TYPE_ALL = 0;
+
+ /**
+ * DNS based service discovery protocol.
+ */
+ public static final int SERVICE_TYPE_BONJOUR = 1;
+
+ /**
+ * UPnP protocol.
+ */
+ public static final int SERVICE_TYPE_UPNP = 2;
+
+ /**
+ * WS-Discovery protocol
+ * @hide
+ */
+ public static final int SERVICE_TYPE_WS_DISCOVERY = 3;
+
+ /**
+ * Vendor Specific protocol
+ */
+ public static final int SERVICE_TYPE_VENDOR_SPECIFIC = 255;
+
+ /**
+ * the list of query string for wpa_supplicant
+ *
+ * e.g)
+ * # IP Printing over TCP (PTR) (RDATA=MyPrinter._ipp._tcp.local.)
+ * {"bonjour", "045f697070c00c000c01", "094d795072696e746572c027"
+ *
+ * # IP Printing over TCP (TXT) (RDATA=txtvers=1,pdl=application/postscript)
+ * {"bonjour", "096d797072696e746572045f697070c00c001001",
+ * "09747874766572733d311a70646c3d6170706c69636174696f6e2f706f7374736372797074"}
+ *
+ * [UPnP]
+ * # UPnP uuid
+ * {"upnp", "10", "uuid:6859dede-8574-59ab-9332-123456789012"}
+ *
+ * # UPnP rootdevice
+ * {"upnp", "10", "uuid:6859dede-8574-59ab-9332-123456789012::upnp:rootdevice"}
+ *
+ * # UPnP device
+ * {"upnp", "10", "uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp
+ * -org:device:InternetGatewayDevice:1"}
+ *
+ * # UPnP service
+ * {"upnp", "10", "uuid:6859dede-8574-59ab-9322-123456789012::urn:schemas-upnp
+ * -org:service:ContentDirectory:2"}
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ private List<String> mQueryList;
+
+ /**
+ * This is only used in subclass.
+ *
+ * @param queryList query string for wpa_supplicant
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ protected WifiP2pServiceInfo(List<String> queryList) {
+ if (queryList == null) {
+ throw new IllegalArgumentException("query list cannot be null");
+ }
+ mQueryList = queryList;
+ }
+
+ /**
+ * Return the list of the query string for wpa_supplicant.
+ *
+ * @return the list of the query string for wpa_supplicant.
+ * @hide
+ */
+ public List<String> getSupplicantQueryList() {
+ return mQueryList;
+ }
+
+ /**
+ * Converts byte array to hex string.
+ *
+ * @param data
+ * @return hex string.
+ * @hide
+ */
+ static String bin2HexStr(byte[] data) {
+ StringBuffer sb = new StringBuffer();
+
+ for (byte b: data) {
+ String s = null;
+ try {
+ s = Integer.toHexString(b & 0xff);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ //add 0 padding
+ if (s.length() == 1) {
+ sb.append('0');
+ }
+ sb.append(s);
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof WifiP2pServiceInfo)) {
+ return false;
+ }
+
+ WifiP2pServiceInfo servInfo = (WifiP2pServiceInfo)o;
+ return mQueryList.equals(servInfo.mQueryList);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mQueryList == null ? 0 : mQueryList.hashCode());
+ return result;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStringList(mQueryList);
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ @UnsupportedAppUsage
+ public static final @android.annotation.NonNull Creator<WifiP2pServiceInfo> CREATOR =
+ new Creator<WifiP2pServiceInfo>() {
+ public WifiP2pServiceInfo createFromParcel(Parcel in) {
+
+ List<String> data = new ArrayList<String>();
+ in.readStringList(data);
+ return new WifiP2pServiceInfo(data);
+ }
+
+ public WifiP2pServiceInfo[] newArray(int size) {
+ return new WifiP2pServiceInfo[size];
+ }
+ };
+}
diff --git a/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.java b/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.java
new file mode 100644
index 0000000..87528c4
--- /dev/null
+++ b/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2012 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.p2p.nsd;
+
+import android.annotation.UnsupportedAppUsage;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Locale;
+
+/**
+ * A class for creating a service discovery request for use with
+ * {@link WifiP2pManager#addServiceRequest} and {@link WifiP2pManager#removeServiceRequest}
+ *
+ * <p>This class is used to create service discovery request for custom
+ * vendor specific service discovery protocol {@link WifiP2pServiceInfo#SERVICE_TYPE_VENDOR_SPECIFIC}
+ * or to search all service protocols {@link WifiP2pServiceInfo#SERVICE_TYPE_ALL}.
+ *
+ * <p>For the purpose of creating a UPnP or Bonjour service request, use
+ * {@link WifiP2pUpnpServiceRequest} or {@link WifiP2pDnsSdServiceRequest} respectively.
+ *
+ * {@see WifiP2pManager}
+ * {@see WifiP2pUpnpServiceRequest}
+ * {@see WifiP2pDnsSdServiceRequest}
+ */
+public class WifiP2pServiceRequest implements Parcelable {
+
+ /**
+ * Service discovery protocol. It's defined in table63 in Wi-Fi Direct specification.
+ */
+ private int mProtocolType;
+
+ /**
+ * The length of the service request TLV.
+ * The value is equal to 2 plus the number of octets in the
+ * query data field.
+ */
+ private int mLength;
+
+ /**
+ * Service transaction ID.
+ * This is a nonzero value used to match the service request/response TLVs.
+ */
+ private int mTransId;
+
+ /**
+ * The hex dump string of query data for the requested service information.
+ *
+ * e.g) DnsSd apple file sharing over tcp (dns name=_afpovertcp._tcp.local.)
+ * 0b5f6166706f766572746370c00c000c01
+ */
+ private String mQuery;
+
+ /**
+ * This constructor is only used in newInstance().
+ *
+ * @param protocolType service discovery protocol.
+ * @param query The part of service specific query.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ protected WifiP2pServiceRequest(int protocolType, String query) {
+ validateQuery(query);
+
+ mProtocolType = protocolType;
+ mQuery = query;
+ if (query != null) {
+ mLength = query.length()/2 + 2;
+ } else {
+ mLength = 2;
+ }
+ }
+
+ /**
+ * This constructor is only used in Parcelable.
+ *
+ * @param serviceType service discovery type.
+ * @param length the length of service discovery packet.
+ * @param transId the transaction id
+ * @param query The part of service specific query.
+ */
+ private WifiP2pServiceRequest(int serviceType, int length,
+ int transId, String query) {
+ mProtocolType = serviceType;
+ mLength = length;
+ mTransId = transId;
+ mQuery = query;
+ }
+
+ /**
+ * Return transaction id.
+ *
+ * @return transaction id
+ * @hide
+ */
+ public int getTransactionId() {
+ return mTransId;
+ }
+
+ /**
+ * Set transaction id.
+ *
+ * @param id
+ * @hide
+ */
+ public void setTransactionId(int id) {
+ mTransId = id;
+ }
+
+ /**
+ * Return wpa_supplicant request string.
+ *
+ * The format is the hex dump of the following frame.
+ * <pre>
+ * _______________________________________________________________
+ * | Length (2) | Type (1) | Transaction ID (1) |
+ * | Query Data (variable) |
+ * </pre>
+ *
+ * @return wpa_supplicant request string.
+ * @hide
+ */
+ public String getSupplicantQuery() {
+ StringBuffer sb = new StringBuffer();
+ // length is retained as little endian format.
+ sb.append(String.format(Locale.US, "%02x", (mLength) & 0xff));
+ sb.append(String.format(Locale.US, "%02x", (mLength >> 8) & 0xff));
+ sb.append(String.format(Locale.US, "%02x", mProtocolType));
+ sb.append(String.format(Locale.US, "%02x", mTransId));
+ if (mQuery != null) {
+ sb.append(mQuery);
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Validate query.
+ *
+ * <p>If invalid, throw IllegalArgumentException.
+ * @param query The part of service specific query.
+ */
+ private void validateQuery(String query) {
+ if (query == null) {
+ return;
+ }
+
+ int UNSIGNED_SHORT_MAX = 0xffff;
+ if (query.length()%2 == 1) {
+ throw new IllegalArgumentException(
+ "query size is invalid. query=" + query);
+ }
+ if (query.length()/2 > UNSIGNED_SHORT_MAX) {
+ throw new IllegalArgumentException(
+ "query size is too large. len=" + query.length());
+ }
+
+ // check whether query is hex string.
+ query = query.toLowerCase(Locale.ROOT);
+ char[] chars = query.toCharArray();
+ for (char c: chars) {
+ if (!((c >= '0' && c <= '9') ||
+ (c >= 'a' && c <= 'f'))){
+ throw new IllegalArgumentException(
+ "query should be hex string. query=" + query);
+ }
+ }
+ }
+
+ /**
+ * Create a service discovery request.
+ *
+ * @param protocolType can be {@link WifiP2pServiceInfo#SERVICE_TYPE_ALL}
+ * or {@link WifiP2pServiceInfo#SERVICE_TYPE_VENDOR_SPECIFIC}.
+ * In order to create a UPnP or Bonjour service request, use
+ * {@link WifiP2pUpnpServiceRequest} or {@link WifiP2pDnsSdServiceRequest}
+ * respectively
+ *
+ * @param queryData hex string that is vendor specific. Can be null.
+ * @return service discovery request.
+ */
+ public static WifiP2pServiceRequest newInstance(int protocolType, String queryData) {
+ return new WifiP2pServiceRequest(protocolType, queryData);
+ }
+
+ /**
+ * Create a service discovery request.
+ *
+ * @param protocolType can be {@link WifiP2pServiceInfo#SERVICE_TYPE_ALL}
+ * or {@link WifiP2pServiceInfo#SERVICE_TYPE_VENDOR_SPECIFIC}.
+ * In order to create a UPnP or Bonjour service request, use
+ * {@link WifiP2pUpnpServiceRequest} or {@link WifiP2pDnsSdServiceRequest}
+ * respectively
+ *
+ * @return service discovery request.
+ */
+ public static WifiP2pServiceRequest newInstance(int protocolType ) {
+ return new WifiP2pServiceRequest(protocolType, null);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof WifiP2pServiceRequest)) {
+ return false;
+ }
+
+ WifiP2pServiceRequest req = (WifiP2pServiceRequest)o;
+
+ /*
+ * Not compare transaction id.
+ * Transaction id may be changed on each service discovery operation.
+ */
+ if ((req.mProtocolType != mProtocolType) ||
+ (req.mLength != mLength)) {
+ return false;
+ }
+
+ if (req.mQuery == null && mQuery == null) {
+ return true;
+ } else if (req.mQuery != null) {
+ return req.mQuery.equals(mQuery);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mProtocolType;
+ result = 31 * result + mLength;
+ result = 31 * result + (mQuery == null ? 0 : mQuery.hashCode());
+ return result;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mProtocolType);
+ dest.writeInt(mLength);
+ dest.writeInt(mTransId);
+ dest.writeString(mQuery);
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ @UnsupportedAppUsage
+ public static final @android.annotation.NonNull Creator<WifiP2pServiceRequest> CREATOR =
+ new Creator<WifiP2pServiceRequest>() {
+ public WifiP2pServiceRequest createFromParcel(Parcel in) {
+ int servType = in.readInt();
+ int length = in.readInt();
+ int transId = in.readInt();
+ String query = in.readString();
+ return new WifiP2pServiceRequest(servType, length, transId, query);
+ }
+
+ public WifiP2pServiceRequest[] newArray(int size) {
+ return new WifiP2pServiceRequest[size];
+ }
+ };
+}
diff --git a/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.java b/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.java
new file mode 100644
index 0000000..1b9c080
--- /dev/null
+++ b/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2012 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.p2p.nsd;
+
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The class for a response of service discovery.
+ *
+ * @hide
+ */
+public class WifiP2pServiceResponse implements Parcelable {
+
+ private static int MAX_BUF_SIZE = 1024;
+
+ /**
+ * Service type. It's defined in table63 in Wi-Fi Direct specification.
+ */
+ protected int mServiceType;
+
+ /**
+ * Status code of service discovery response.
+ * It's defined in table65 in Wi-Fi Direct specification.
+ * @see Status
+ */
+ protected int mStatus;
+
+ /**
+ * Service transaction ID.
+ * This is a nonzero value used to match the service request/response TLVs.
+ */
+ protected int mTransId;
+
+ /**
+ * Source device.
+ */
+ protected WifiP2pDevice mDevice;
+
+ /**
+ * Service discovery response data based on the requested on
+ * the service protocol type. The protocol format depends on the service type.
+ */
+ protected byte[] mData;
+
+
+ /**
+ * The status code of service discovery response.
+ * Currently 4 status codes are defined and the status codes from 4 to 255
+ * are reserved.
+ *
+ * See Wi-Fi Direct specification for the detail.
+ */
+ public static class Status {
+ /** success */
+ public static final int SUCCESS = 0;
+
+ /** the service protocol type is not available */
+ public static final int SERVICE_PROTOCOL_NOT_AVAILABLE = 1;
+
+ /** the requested information is not available */
+ public static final int REQUESTED_INFORMATION_NOT_AVAILABLE = 2;
+
+ /** bad request */
+ public static final int BAD_REQUEST = 3;
+
+ /** @hide */
+ public static String toString(int status) {
+ switch(status) {
+ case SUCCESS:
+ return "SUCCESS";
+ case SERVICE_PROTOCOL_NOT_AVAILABLE:
+ return "SERVICE_PROTOCOL_NOT_AVAILABLE";
+ case REQUESTED_INFORMATION_NOT_AVAILABLE:
+ return "REQUESTED_INFORMATION_NOT_AVAILABLE";
+ case BAD_REQUEST:
+ return "BAD_REQUEST";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ /** not used */
+ private Status() {}
+ }
+
+ /**
+ * Hidden constructor. This is only used in framework.
+ *
+ * @param serviceType service discovery type.
+ * @param status status code.
+ * @param transId transaction id.
+ * @param device source device.
+ * @param data query data.
+ */
+ protected WifiP2pServiceResponse(int serviceType, int status, int transId,
+ WifiP2pDevice device, byte[] data) {
+ mServiceType = serviceType;
+ mStatus = status;
+ mTransId = transId;
+ mDevice = device;
+ mData = data;
+ }
+
+ /**
+ * Return the service type of service discovery response.
+ *
+ * @return service discovery type.<br>
+ * e.g) {@link WifiP2pServiceInfo#SERVICE_TYPE_BONJOUR}
+ */
+ public int getServiceType() {
+ return mServiceType;
+ }
+
+ /**
+ * Return the status code of service discovery response.
+ *
+ * @return status code.
+ * @see Status
+ */
+ public int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * Return the transaction id of service discovery response.
+ *
+ * @return transaction id.
+ * @hide
+ */
+ public int getTransactionId() {
+ return mTransId;
+ }
+
+ /**
+ * Return response data.
+ *
+ * <pre>Data format depends on service type
+ *
+ * @return a query or response data.
+ */
+ public byte[] getRawData() {
+ return mData;
+ }
+
+ /**
+ * Returns the source device of service discovery response.
+ *
+ * <pre>This is valid only when service discovery response.
+ *
+ * @return the source device of service discovery response.
+ */
+ public WifiP2pDevice getSrcDevice() {
+ return mDevice;
+ }
+
+ /** @hide */
+ public void setSrcDevice(WifiP2pDevice dev) {
+ if (dev == null) return;
+ this.mDevice = dev;
+ }
+
+
+ /**
+ * Create the list of WifiP2pServiceResponse instance from supplicant event.
+ *
+ * @param srcAddr source address of the service response
+ * @param tlvsBin byte array containing the binary tlvs data
+ * @return if parse failed, return null
+ * @hide
+ */
+ public static List<WifiP2pServiceResponse> newInstance(String srcAddr, byte[] tlvsBin) {
+ //updateIndicator not used, and not passed up from supplicant
+
+ List<WifiP2pServiceResponse> respList = new ArrayList<WifiP2pServiceResponse>();
+ WifiP2pDevice dev = new WifiP2pDevice();
+ dev.deviceAddress = srcAddr;
+ if (tlvsBin == null) {
+ return null;
+ }
+
+
+ DataInputStream dis = new DataInputStream(new ByteArrayInputStream(tlvsBin));
+ try {
+ while (dis.available() > 0) {
+ /*
+ * Service discovery header is as follows.
+ * ______________________________________________________________
+ * | Length(2byte) | Type(1byte) | TransId(1byte)}|
+ * ______________________________________________________________
+ * | status(1byte) | vendor specific(variable) |
+ */
+ // The length equals to 3 plus the number of octets in the vendor
+ // specific content field. And this is little endian.
+ int length = (dis.readUnsignedByte() +
+ (dis.readUnsignedByte() << 8)) - 3;
+ int type = dis.readUnsignedByte();
+ int transId = dis.readUnsignedByte();
+ int status = dis.readUnsignedByte();
+ if (length < 0) {
+ return null;
+ }
+ if (length == 0) {
+ if (status == Status.SUCCESS) {
+ respList.add(new WifiP2pServiceResponse(type, status,
+ transId, dev, null));
+ }
+ continue;
+ }
+ if (length > MAX_BUF_SIZE) {
+ dis.skip(length);
+ continue;
+ }
+ byte[] data = new byte[length];
+ dis.readFully(data);
+
+ WifiP2pServiceResponse resp;
+ if (type == WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR) {
+ resp = WifiP2pDnsSdServiceResponse.newInstance(status,
+ transId, dev, data);
+ } else if (type == WifiP2pServiceInfo.SERVICE_TYPE_UPNP) {
+ resp = WifiP2pUpnpServiceResponse.newInstance(status,
+ transId, dev, data);
+ } else {
+ resp = new WifiP2pServiceResponse(type, status, transId, dev, data);
+ }
+ if (resp != null && resp.getStatus() == Status.SUCCESS) {
+ respList.add(resp);
+ }
+ }
+ return respList;
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ if (respList.size() > 0) {
+ return respList;
+ }
+ return null;
+ }
+
+ /**
+ * Converts hex string to byte array.
+ *
+ * @param hex hex string. if invalid, return null.
+ * @return binary data.
+ */
+ private static byte[] hexStr2Bin(String hex) {
+ int sz = hex.length()/2;
+ byte[] b = new byte[hex.length()/2];
+
+ for (int i=0;i<sz;i++) {
+ try {
+ b[i] = (byte)Integer.parseInt(hex.substring(i*2, i*2+2), 16);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+ return b;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append("serviceType:").append(mServiceType);
+ sbuf.append(" status:").append(Status.toString(mStatus));
+ sbuf.append(" srcAddr:").append(mDevice.deviceAddress);
+ sbuf.append(" data:").append(Arrays.toString(mData));
+ return sbuf.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof WifiP2pServiceResponse)) {
+ return false;
+ }
+
+ WifiP2pServiceResponse req = (WifiP2pServiceResponse)o;
+
+ return (req.mServiceType == mServiceType) &&
+ (req.mStatus == mStatus) &&
+ equals(req.mDevice.deviceAddress, mDevice.deviceAddress) &&
+ Arrays.equals(req.mData, mData);
+ }
+
+ private boolean equals(Object a, Object b) {
+ if (a == null && b == null) {
+ return true;
+ } else if (a != null) {
+ return a.equals(b);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mServiceType;
+ result = 31 * result + mStatus;
+ result = 31 * result + mTransId;
+ result = 31 * result + (mDevice.deviceAddress == null ?
+ 0 : mDevice.deviceAddress.hashCode());
+ result = 31 * result + (mData == null ? 0 : Arrays.hashCode(mData));
+ return result;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mServiceType);
+ dest.writeInt(mStatus);
+ dest.writeInt(mTransId);
+ dest.writeParcelable(mDevice, flags);
+ if (mData == null || mData.length == 0) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(mData.length);
+ dest.writeByteArray(mData);
+ }
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final @android.annotation.NonNull Creator<WifiP2pServiceResponse> CREATOR =
+ new Creator<WifiP2pServiceResponse>() {
+ public WifiP2pServiceResponse createFromParcel(Parcel in) {
+
+ int type = in.readInt();
+ int status = in.readInt();
+ int transId = in.readInt();
+ WifiP2pDevice dev = (WifiP2pDevice)in.readParcelable(null);
+ int len = in.readInt();
+ byte[] data = null;
+ if (len > 0) {
+ data = new byte[len];
+ in.readByteArray(data);
+ }
+ if (type == WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR) {
+ return WifiP2pDnsSdServiceResponse.newInstance(status,
+ transId, dev, data);
+ } else if (type == WifiP2pServiceInfo.SERVICE_TYPE_UPNP) {
+ return WifiP2pUpnpServiceResponse.newInstance(status,
+ transId, dev, data);
+ }
+ return new WifiP2pServiceResponse(type, status, transId, dev, data);
+ }
+
+ public WifiP2pServiceResponse[] newArray(int size) {
+ return new WifiP2pServiceResponse[size];
+ }
+ };
+}
diff --git a/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceInfo.java b/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceInfo.java
new file mode 100644
index 0000000..a4cdfd9
--- /dev/null
+++ b/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceInfo.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2012 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.p2p.nsd;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.UUID;
+
+/**
+ * A class for storing Upnp service information that is advertised
+ * over a Wi-Fi peer-to-peer setup.
+ *
+ * {@see android.net.wifi.p2p.WifiP2pManager#addLocalService}
+ * {@see android.net.wifi.p2p.WifiP2pManager#removeLocalService}
+ * {@see WifiP2pServiceInfo}
+ * {@see WifiP2pDnsSdServiceInfo}
+ */
+public class WifiP2pUpnpServiceInfo extends WifiP2pServiceInfo {
+
+ /**
+ * UPnP version 1.0.
+ *
+ * <pre>Query Version should always be set to 0x10 if the query values are
+ * compatible with UPnP Device Architecture 1.0.
+ * @hide
+ */
+ public static final int VERSION_1_0 = 0x10;
+
+ /**
+ * This constructor is only used in newInstance().
+ *
+ * @param queryList
+ */
+ private WifiP2pUpnpServiceInfo(List<String> queryList) {
+ super(queryList);
+ }
+
+ /**
+ * Create UPnP service information object.
+ *
+ * @param uuid a string representation of this UUID in the following format,
+ * as per <a href="http://www.ietf.org/rfc/rfc4122.txt">RFC 4122</a>.<br>
+ * e.g) 6859dede-8574-59ab-9332-123456789012
+ * @param device a string representation of this device in the following format,
+ * as per
+ * <a href="http://www.upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf">
+ * UPnP Device Architecture1.1</a><br>
+ * e.g) urn:schemas-upnp-org:device:MediaServer:1
+ * @param services a string representation of this service in the following format,
+ * as per
+ * <a href="http://www.upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf">
+ * UPnP Device Architecture1.1</a><br>
+ * e.g) urn:schemas-upnp-org:service:ContentDirectory:1
+ * @return UPnP service information object.
+ */
+ public static WifiP2pUpnpServiceInfo newInstance(String uuid,
+ String device, List<String> services) {
+ if (uuid == null || device == null) {
+ throw new IllegalArgumentException("uuid or device cannnot be null");
+ }
+ UUID.fromString(uuid);
+
+ ArrayList<String> info = new ArrayList<String>();
+
+ info.add(createSupplicantQuery(uuid, null));
+ info.add(createSupplicantQuery(uuid, "upnp:rootdevice"));
+ info.add(createSupplicantQuery(uuid, device));
+ if (services != null) {
+ for (String service:services) {
+ info.add(createSupplicantQuery(uuid, service));
+ }
+ }
+
+ return new WifiP2pUpnpServiceInfo(info);
+ }
+
+ /**
+ * Create wpa_supplicant service query for upnp.
+ *
+ * @param uuid
+ * @param data
+ * @return wpa_supplicant service query for upnp
+ */
+ private static String createSupplicantQuery(String uuid, String data) {
+ StringBuffer sb = new StringBuffer();
+ sb.append("upnp ");
+ sb.append(String.format(Locale.US, "%02x ", VERSION_1_0));
+ sb.append("uuid:");
+ sb.append(uuid);
+ if (data != null) {
+ sb.append("::");
+ sb.append(data);
+ }
+ return sb.toString();
+ }
+}
diff --git a/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceRequest.java b/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceRequest.java
new file mode 100644
index 0000000..98e447e
--- /dev/null
+++ b/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceRequest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2012 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.p2p.nsd;
+
+import android.net.wifi.p2p.WifiP2pManager;
+
+import java.util.Locale;
+
+/**
+ * A class for creating a Upnp service discovery request for use with
+ * {@link WifiP2pManager#addServiceRequest} and {@link WifiP2pManager#removeServiceRequest}
+ *
+ * {@see WifiP2pManager}
+ * {@see WifiP2pServiceRequest}
+ * {@see WifiP2pDnsSdServiceRequest}
+ */
+public class WifiP2pUpnpServiceRequest extends WifiP2pServiceRequest {
+
+ /**
+ * This constructor is only used in newInstance().
+ *
+ * @param query The part of service specific query.
+ * @hide
+ */
+ protected WifiP2pUpnpServiceRequest(String query) {
+ super(WifiP2pServiceInfo.SERVICE_TYPE_UPNP, query);
+ }
+
+ /**
+ * This constructor is only used in newInstance().
+ * @hide
+ */
+ protected WifiP2pUpnpServiceRequest() {
+ super(WifiP2pServiceInfo.SERVICE_TYPE_UPNP, null);
+ }
+
+ /**
+ * Create a service discovery request to search all UPnP services.
+ *
+ * @return service request for UPnP.
+ */
+ public static WifiP2pUpnpServiceRequest newInstance() {
+ return new WifiP2pUpnpServiceRequest();
+ }
+ /**
+ * Create a service discovery request to search specified UPnP services.
+ *
+ * @param st ssdp search target. Cannot be null.<br>
+ * e.g ) <br>
+ * <ul>
+ * <li>"ssdp:all"
+ * <li>"upnp:rootdevice"
+ * <li>"urn:schemas-upnp-org:device:MediaServer:2"
+ * <li>"urn:schemas-upnp-org:service:ContentDirectory:2"
+ * <li>"uuid:6859dede-8574-59ab-9332-123456789012"
+ * </ul>
+ * @return service request for UPnP.
+ */
+ public static WifiP2pUpnpServiceRequest newInstance(String st) {
+ if (st == null) {
+ throw new IllegalArgumentException("search target cannot be null");
+ }
+ StringBuffer sb = new StringBuffer();
+ sb.append(String.format(Locale.US, "%02x", WifiP2pUpnpServiceInfo.VERSION_1_0));
+ sb.append(WifiP2pServiceInfo.bin2HexStr(st.getBytes()));
+ return new WifiP2pUpnpServiceRequest(sb.toString());
+ }
+}
diff --git a/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceResponse.java b/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceResponse.java
new file mode 100644
index 0000000..ab95af6
--- /dev/null
+++ b/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceResponse.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2012 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.p2p.nsd;
+
+import android.net.wifi.p2p.WifiP2pDevice;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class for a response of upnp service discovery.
+ *
+ * @hide
+ */
+public class WifiP2pUpnpServiceResponse extends WifiP2pServiceResponse {
+
+ /**
+ * UPnP version. should be {@link WifiP2pUpnpServiceInfo#VERSION_1_0}
+ */
+ private int mVersion;
+
+ /**
+ * The list of Unique Service Name.
+ * e.g)
+ *{"uuid:6859dede-8574-59ab-9332-123456789012",
+ *"uuid:6859dede-8574-59ab-9332-123456789012::upnp:rootdevice"}
+ */
+ private List<String> mUniqueServiceNames;
+
+ /**
+ * Return UPnP version number.
+ *
+ * @return version number.
+ * @see WifiP2pUpnpServiceInfo#VERSION_1_0
+ */
+ public int getVersion() {
+ return mVersion;
+ }
+
+ /**
+ * Return Unique Service Name strings.
+ *
+ * @return Unique Service Name.<br>
+ * e.g ) <br>
+ * <ul>
+ * <li>"uuid:6859dede-8574-59ab-9332-123456789012"
+ * <li>"uuid:6859dede-8574-59ab-9332-123456789012::upnp:rootdevice"
+ * <li>"uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp-org:device:
+ * MediaServer:2"
+ * <li>"uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp-org:service:
+ * ContentDirectory:2"
+ * </ul>
+ */
+ public List<String> getUniqueServiceNames() {
+ return mUniqueServiceNames;
+ }
+
+ /**
+ * hidden constructor.
+ *
+ * @param status status code
+ * @param transId transaction id
+ * @param dev source device
+ * @param data UPnP response data.
+ */
+ protected WifiP2pUpnpServiceResponse(int status,
+ int transId, WifiP2pDevice dev, byte[] data) {
+ super(WifiP2pServiceInfo.SERVICE_TYPE_UPNP,
+ status, transId, dev, data);
+ if (!parse()) {
+ throw new IllegalArgumentException("Malformed upnp service response");
+ }
+ }
+
+ /**
+ * Parse UPnP service discovery response
+ *
+ * @return {@code true} if the operation succeeded
+ */
+ private boolean parse() {
+ /*
+ * The data format is as follows.
+ *
+ * ______________________________________________________
+ * | Version (1) | USN (Variable) |
+ */
+ if (mData == null) {
+ // the empty is OK.
+ return true;
+ }
+
+ if (mData.length < 1) {
+ return false;
+ }
+
+ mVersion = mData[0] & 0xff;
+ String[] names = new String(mData, 1, mData.length-1).split(",");
+ mUniqueServiceNames = new ArrayList<String>();
+ for (String name : names) {
+ mUniqueServiceNames.add(name);
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append("serviceType:UPnP(").append(mServiceType).append(")");
+ sbuf.append(" status:").append(Status.toString(mStatus));
+ sbuf.append(" srcAddr:").append(mDevice.deviceAddress);
+ sbuf.append(" version:").append(String.format("%02x", mVersion));
+ if (mUniqueServiceNames != null) {
+ for (String name : mUniqueServiceNames) {
+ sbuf.append(" usn:").append(name);
+ }
+ }
+ return sbuf.toString();
+ }
+
+ /**
+ * Create upnp service response.
+ *
+ * <pre>This is only used in{@link WifiP2pServiceResponse}
+ *
+ * @param status status code.
+ * @param transId transaction id.
+ * @param device source device.
+ * @param data UPnP response data.
+ * @return UPnP service response data.
+ * @hide
+ */
+ static WifiP2pUpnpServiceResponse newInstance(int status,
+ int transId, WifiP2pDevice device, byte[] data) {
+ if (status != WifiP2pServiceResponse.Status.SUCCESS) {
+ return new WifiP2pUpnpServiceResponse(status, transId, device, null);
+ }
+
+ try {
+ return new WifiP2pUpnpServiceResponse(status, transId, device, data);
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+}
diff --git a/android/net/wifi/rtt/CivicLocation.java b/android/net/wifi/rtt/CivicLocation.java
new file mode 100644
index 0000000..1d41177
--- /dev/null
+++ b/android/net/wifi/rtt/CivicLocation.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.rtt;
+
+import android.annotation.Nullable;
+import android.location.Address;
+import android.net.wifi.rtt.CivicLocationKeys.CivicLocationKeysType;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Parcelable.Creator;
+import android.util.SparseArray;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * Decodes the Type Length Value (TLV) elements found in a Location Civic Record as defined by IEEE
+ * P802.11-REVmc/D8.0 section 9.4.2.22.13 using the format described in IETF RFC 4776.
+ *
+ * <p>The TLVs each define a key, value pair for a civic address type such as apt, street, city,
+ * county, and country. The class provides a general getter method to extract a value for an element
+ * key, returning null if not set.
+ *
+ * @hide
+ */
+public final class CivicLocation implements Parcelable {
+ // Address (class) line indexes
+ private static final int ADDRESS_LINE_0_ROOM_DESK_FLOOR = 0;
+ private static final int ADDRESS_LINE_1_NUMBER_ROAD_SUFFIX_APT = 1;
+ private static final int ADDRESS_LINE_2_CITY = 2;
+ private static final int ADDRESS_LINE_3_STATE_POSTAL_CODE = 3;
+ private static final int ADDRESS_LINE_4_COUNTRY = 4;
+
+ // Buffer management
+ private static final int MIN_CIVIC_BUFFER_SIZE = 3;
+ private static final int MAX_CIVIC_BUFFER_SIZE = 256;
+ private static final int COUNTRY_CODE_LENGTH = 2;
+ private static final int BYTE_MASK = 0xFF;
+ private static final int TLV_TYPE_INDEX = 0;
+ private static final int TLV_LENGTH_INDEX = 1;
+ private static final int TLV_VALUE_INDEX = 2;
+
+ private final boolean mIsValid;
+ private final String mCountryCode; // Two character country code (ISO 3166 standard).
+ private SparseArray<String> mCivicAddressElements =
+ new SparseArray<>(MIN_CIVIC_BUFFER_SIZE);
+
+
+ /**
+ * Constructor
+ *
+ * @param civicTLVs a byte buffer containing parameters in the form type, length, value
+ * @param countryCode the two letter code defined by the ISO 3166 standard
+ *
+ * @hide
+ */
+ public CivicLocation(@Nullable byte[] civicTLVs, @Nullable String countryCode) {
+ this.mCountryCode = countryCode;
+ if (countryCode == null || countryCode.length() != COUNTRY_CODE_LENGTH) {
+ this.mIsValid = false;
+ return;
+ }
+ boolean isValid = false;
+ if (civicTLVs != null
+ && civicTLVs.length >= MIN_CIVIC_BUFFER_SIZE
+ && civicTLVs.length < MAX_CIVIC_BUFFER_SIZE) {
+ isValid = parseCivicTLVs(civicTLVs);
+ }
+
+ mIsValid = isValid;
+ }
+
+ private CivicLocation(Parcel in) {
+ mIsValid = in.readByte() != 0;
+ mCountryCode = in.readString();
+ mCivicAddressElements = in.readSparseArray(this.getClass().getClassLoader());
+ }
+
+ public static final @android.annotation.NonNull Creator<CivicLocation> CREATOR = new Creator<CivicLocation>() {
+ @Override
+ public CivicLocation createFromParcel(Parcel in) {
+ return new CivicLocation(in);
+ }
+
+ @Override
+ public CivicLocation[] newArray(int size) {
+ return new CivicLocation[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeByte((byte) (mIsValid ? 1 : 0));
+ parcel.writeString(mCountryCode);
+ parcel.writeSparseArray((android.util.SparseArray) mCivicAddressElements);
+ }
+
+ /**
+ * Check TLV format and store TLV key/value pairs in this object so they can be queried by key.
+ *
+ * @param civicTLVs the buffer of TLV elements
+ * @return a boolean indicating success of the parsing process
+ */
+ private boolean parseCivicTLVs(byte[] civicTLVs) {
+ int bufferPtr = 0;
+ int bufferLength = civicTLVs.length;
+
+ // Iterate through the sub-elements contained in the LCI IE checking the accumulated
+ // element lengths do not overflow the total buffer length
+ while (bufferPtr < bufferLength) {
+ int civicAddressType = civicTLVs[bufferPtr + TLV_TYPE_INDEX] & BYTE_MASK;
+ int civicAddressTypeLength = civicTLVs[bufferPtr + TLV_LENGTH_INDEX];
+ if (civicAddressTypeLength != 0) {
+ if (bufferPtr + TLV_VALUE_INDEX + civicAddressTypeLength > bufferLength) {
+ return false;
+ }
+ mCivicAddressElements.put(civicAddressType,
+ new String(civicTLVs, bufferPtr + TLV_VALUE_INDEX,
+ civicAddressTypeLength, StandardCharsets.UTF_8));
+ }
+ bufferPtr += civicAddressTypeLength + TLV_VALUE_INDEX;
+ }
+ return true;
+ }
+
+ /**
+ * Getter for the value of a civic Address element type.
+ *
+ * @param key an integer code for the element type key
+ * @return the string value associated with that element type
+ */
+ @Nullable
+ public String getCivicElementValue(@CivicLocationKeysType int key) {
+ return mCivicAddressElements.get(key);
+ }
+
+ /**
+ * Converts a CivicLocation object to a SparseArray.
+ *
+ * @return the SparseArray<string> representation of the CivicLocation
+ */
+ @Nullable
+ public SparseArray<String> toSparseArray() {
+ return mCivicAddressElements;
+ }
+
+ /**
+ * Generates a comma separated string of all the defined elements.
+ *
+ * @return a compiled string representing all elements
+ */
+ @Override
+ public String toString() {
+ return mCivicAddressElements.toString();
+ }
+
+ /**
+ * Converts Civic Location to the best effort Address Object.
+ *
+ * @return the {@link Address} object based on the Civic Location data
+ */
+ @Nullable
+ public Address toAddress() {
+ if (!mIsValid) {
+ return null;
+ }
+ Address address = new Address(Locale.US);
+ String room = formatAddressElement("Room: ", getCivicElementValue(CivicLocationKeys.ROOM));
+ String desk =
+ formatAddressElement(" Desk: ", getCivicElementValue(CivicLocationKeys.DESK));
+ String floor =
+ formatAddressElement(", Flr: ", getCivicElementValue(CivicLocationKeys.FLOOR));
+ String houseNumber = formatAddressElement("", getCivicElementValue(CivicLocationKeys.HNO));
+ String houseNumberSuffix =
+ formatAddressElement("", getCivicElementValue(CivicLocationKeys.HNS));
+ String road =
+ formatAddressElement(" ", getCivicElementValue(
+ CivicLocationKeys.PRIMARY_ROAD_NAME));
+ String roadSuffix = formatAddressElement(" ", getCivicElementValue(CivicLocationKeys.STS));
+ String apt = formatAddressElement(", Apt: ", getCivicElementValue(CivicLocationKeys.APT));
+ String city = formatAddressElement("", getCivicElementValue(CivicLocationKeys.CITY));
+ String state = formatAddressElement("", getCivicElementValue(CivicLocationKeys.STATE));
+ String postalCode =
+ formatAddressElement(" ", getCivicElementValue(CivicLocationKeys.POSTAL_CODE));
+
+ // Aggregation into common address format
+ String addressLine0 =
+ new StringBuilder().append(room).append(desk).append(floor).toString();
+ String addressLine1 =
+ new StringBuilder().append(houseNumber).append(houseNumberSuffix).append(road)
+ .append(roadSuffix).append(apt).toString();
+ String addressLine2 = city;
+ String addressLine3 = new StringBuilder().append(state).append(postalCode).toString();
+ String addressLine4 = mCountryCode;
+
+ // Setting Address object line fields by common convention.
+ address.setAddressLine(ADDRESS_LINE_0_ROOM_DESK_FLOOR, addressLine0);
+ address.setAddressLine(ADDRESS_LINE_1_NUMBER_ROAD_SUFFIX_APT, addressLine1);
+ address.setAddressLine(ADDRESS_LINE_2_CITY, addressLine2);
+ address.setAddressLine(ADDRESS_LINE_3_STATE_POSTAL_CODE, addressLine3);
+ address.setAddressLine(ADDRESS_LINE_4_COUNTRY, addressLine4);
+
+ // Other compatible fields between the CIVIC_ADDRESS and the Address Class.
+ address.setFeatureName(getCivicElementValue(CivicLocationKeys.NAM)); // Structure name
+ address.setSubThoroughfare(getCivicElementValue(CivicLocationKeys.HNO));
+ address.setThoroughfare(getCivicElementValue(CivicLocationKeys.PRIMARY_ROAD_NAME));
+ address.setSubLocality(getCivicElementValue(CivicLocationKeys.NEIGHBORHOOD));
+ address.setSubAdminArea(getCivicElementValue(CivicLocationKeys.COUNTY));
+ address.setAdminArea(getCivicElementValue(CivicLocationKeys.STATE));
+ address.setPostalCode(getCivicElementValue(CivicLocationKeys.POSTAL_CODE));
+ address.setCountryCode(mCountryCode); // Country
+ return address;
+ }
+
+ /**
+ * Prepares an address element so that it can be integrated into an address line convention.
+ *
+ * <p>If an address element is null, the return string will be empty e.g. "".
+ *
+ * @param label a string defining the type of address element
+ * @param value a string defining the elements value
+ * @return the formatted version of the value, with null values converted to empty strings
+ */
+ private String formatAddressElement(String label, String value) {
+ if (value != null) {
+ return label + value;
+ } else {
+ return "";
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof CivicLocation)) {
+ return false;
+ }
+ CivicLocation other = (CivicLocation) obj;
+ return mIsValid == other.mIsValid
+ && Objects.equals(mCountryCode, other.mCountryCode)
+ && isSparseArrayStringEqual(mCivicAddressElements, other.mCivicAddressElements);
+ }
+
+ @Override
+ public int hashCode() {
+ int[] civicAddressKeys = getSparseArrayKeys(mCivicAddressElements);
+ String[] civicAddressValues = getSparseArrayValues(mCivicAddressElements);
+ return Objects.hash(mIsValid, mCountryCode, civicAddressKeys, civicAddressValues);
+ }
+
+ /**
+ * Tests if the Civic Location object is valid
+ *
+ * @return a boolean defining mIsValid
+ */
+ public boolean isValid() {
+ return mIsValid;
+ }
+
+ /**
+ * Tests if two sparse arrays are equal on a key for key basis
+ *
+ * @param sa1 the first sparse array
+ * @param sa2 the second sparse array
+ * @return the boolean result after comparing values key by key
+ */
+ private boolean isSparseArrayStringEqual(SparseArray<String> sa1, SparseArray<String> sa2) {
+ int size = sa1.size();
+ if (size != sa2.size()) {
+ return false;
+ }
+ for (int i = 0; i < size; i++) {
+ String sa1Value = sa1.valueAt(i);
+ String sa2Value = sa2.valueAt(i);
+ if (!sa1Value.equals(sa2Value)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Extract an array of all the keys in a SparseArray<String>
+ *
+ * @param sa the sparse array of Strings
+ * @return an integer array of all keys in the SparseArray<String>
+ */
+ private int[] getSparseArrayKeys(SparseArray<String> sa) {
+ int size = sa.size();
+ int[] keys = new int[size];
+ for (int i = 0; i < size; i++) {
+ keys[i] = sa.keyAt(i);
+ }
+ return keys;
+ }
+
+ /**
+ * Extract an array of all the String values in a SparseArray<String>
+ *
+ * @param sa the sparse array of Strings
+ * @return a String array of all values in the SparseArray<String>
+ */
+ private String[] getSparseArrayValues(SparseArray<String> sa) {
+ int size = sa.size();
+ String[] values = new String[size];
+ for (int i = 0; i < size; i++) {
+ values[i] = sa.valueAt(i);
+ }
+ return values;
+ }
+}
diff --git a/android/net/wifi/rtt/CivicLocationKeys.java b/android/net/wifi/rtt/CivicLocationKeys.java
new file mode 100644
index 0000000..b03f4a9
--- /dev/null
+++ b/android/net/wifi/rtt/CivicLocationKeys.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.rtt;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Civic Address key types used to define address elements.
+ *
+ * <p>These keys can be used with {@code ResponderLocation.toCivicLocationSparseArray()}
+ * to look-up the corresponding string values.</p>
+ */
+public class CivicLocationKeys {
+
+ /**
+ * An enumeration of all civic location keys.
+ *
+ * @hide
+ */
+ @Retention(SOURCE)
+ @IntDef({LANGUAGE, STATE, COUNTY, CITY, BOROUGH, NEIGHBORHOOD, GROUP_OF_STREETS, PRD, POD, STS,
+ HNO, HNS, LMK, LOC, NAM, POSTAL_CODE, BUILDING, APT, FLOOR, ROOM, TYPE_OF_PLACE, PCN,
+ PO_BOX, ADDITIONAL_CODE, DESK, PRIMARY_ROAD_NAME, ROAD_SECTION, BRANCH_ROAD_NAME,
+ SUBBRANCH_ROAD_NAME, STREET_NAME_PRE_MODIFIER, STREET_NAME_POST_MODIFIER, SCRIPT})
+ public @interface CivicLocationKeysType {
+ }
+
+ /** Language key e.g. i-default. */
+ public static final int LANGUAGE = 0;
+ /** Category label A1 key e.g. California. */
+ public static final int STATE = 1;
+ /** Category label A2 key e.g. Marin. */
+ public static final int COUNTY = 2;
+ /** Category label A3 key e.g. San Francisco. */
+ public static final int CITY = 3;
+ /** Category label A4 key e.g. Westminster. */
+ public static final int BOROUGH = 4;
+ /** Category label A5 key e.g. Pacific Heights. */
+ public static final int NEIGHBORHOOD = 5;
+ /** Category label A6 key e.g. University District. */
+ public static final int GROUP_OF_STREETS = 6;
+ // 7 - 15 not defined
+ /** Leading Street direction key e.g. N. */
+ public static final int PRD = 16;
+ /** Trailing street suffix key e.g. SW. */
+ public static final int POD = 17;
+ /** Street suffix or Type key e.g Ave, Platz. */
+ public static final int STS = 18;
+ /** House Number key e.g. 123. */
+ public static final int HNO = 19;
+ /** House number suffix key e.g. A, 1/2. */
+ public static final int HNS = 20;
+ /** Landmark or vanity address key e.g. Golden Gate Bridge. */
+ public static final int LMK = 21;
+ /** Additional Location info key e.g. South Wing. */
+ public static final int LOC = 22;
+ /** Name of residence key e.g. Joe's Barbershop. */
+ public static final int NAM = 23;
+ /** Postal or ZIP code key e.g. 10027-1234. */
+ public static final int POSTAL_CODE = 24;
+ /** Building key e.g. Lincoln Library. */
+ public static final int BUILDING = 25;
+ /** Apartment or suite key e.g. Apt 42. */
+ public static final int APT = 26;
+ /** Floor key e.g. 4. */
+ public static final int FLOOR = 27;
+ /** Room key e.g. 450F. */
+ public static final int ROOM = 28;
+ /** Type of place key e.g. office. */
+ public static final int TYPE_OF_PLACE = 29;
+ /** Postal community name key e.g. Leonia. */
+ public static final int PCN = 30;
+ /** Post Office Box key e.g. 12345. */
+ public static final int PO_BOX = 31;
+ /** Additional Code key e.g. 13203000003. */
+ public static final int ADDITIONAL_CODE = 32;
+ /** Seat, desk, pole, or cubical key e.g. WS 181. */
+ public static final int DESK = 33;
+ /** Primary road name key e.g. Shoreline. */
+ public static final int PRIMARY_ROAD_NAME = 34;
+ /** Road Section key e.g. 14. */
+ public static final int ROAD_SECTION = 35;
+ /** Branch Rd Name key e.g. Lane 7. */
+ public static final int BRANCH_ROAD_NAME = 36;
+ /** Subbranch Rd Name key e.g. Alley 8. */
+ public static final int SUBBRANCH_ROAD_NAME = 37;
+ /** Premodifier key e.g. Old. */
+ public static final int STREET_NAME_PRE_MODIFIER = 38;
+ /** Postmodifier key e.g. Service. */
+ public static final int STREET_NAME_POST_MODIFIER = 39;
+ /** Script key e.g. Latn. */
+ public static final int SCRIPT = 128;
+
+ /** private constructor */
+ private CivicLocationKeys() {}
+}
+
diff --git a/android/net/wifi/rtt/RangingRequest.java b/android/net/wifi/rtt/RangingRequest.java
new file mode 100644
index 0000000..70af03e
--- /dev/null
+++ b/android/net/wifi/rtt/RangingRequest.java
@@ -0,0 +1,266 @@
+/*
+ * 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 android.net.wifi.rtt;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.net.MacAddress;
+import android.net.wifi.ScanResult;
+import android.net.wifi.aware.AttachCallback;
+import android.net.wifi.aware.DiscoverySessionCallback;
+import android.net.wifi.aware.IdentityChangedListener;
+import android.net.wifi.aware.PeerHandle;
+import android.net.wifi.aware.WifiAwareManager;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringJoiner;
+
+/**
+ * Defines the ranging request to other devices. The ranging request is built using
+ * {@link RangingRequest.Builder}.
+ * A ranging request is executed using
+ * {@link WifiRttManager#startRanging(RangingRequest, java.util.concurrent.Executor, RangingResultCallback)}.
+ * <p>
+ * The ranging request is a batch request - specifying a set of devices (specified using
+ * {@link RangingRequest.Builder#addAccessPoint(ScanResult)} and
+ * {@link RangingRequest.Builder#addAccessPoints(List)}).
+ */
+public final class RangingRequest implements Parcelable {
+ private static final int MAX_PEERS = 10;
+
+ /**
+ * Returns the maximum number of peers to range which can be specified in a single {@code
+ * RangingRequest}. The limit applies no matter how the peers are added to the request, e.g.
+ * through {@link RangingRequest.Builder#addAccessPoint(ScanResult)} or
+ * {@link RangingRequest.Builder#addAccessPoints(List)}.
+ *
+ * @return Maximum number of peers.
+ */
+ public static int getMaxPeers() {
+ return MAX_PEERS;
+ }
+
+ /** @hide */
+ public final List<ResponderConfig> mRttPeers;
+
+ /** @hide */
+ private RangingRequest(List<ResponderConfig> rttPeers) {
+ mRttPeers = rttPeers;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeList(mRttPeers);
+ }
+
+ public static final @android.annotation.NonNull Creator<RangingRequest> CREATOR = new Creator<RangingRequest>() {
+ @Override
+ public RangingRequest[] newArray(int size) {
+ return new RangingRequest[size];
+ }
+
+ @Override
+ public RangingRequest createFromParcel(Parcel in) {
+ return new RangingRequest(in.readArrayList(null));
+ }
+ };
+
+ /** @hide */
+ @Override
+ public String toString() {
+ StringJoiner sj = new StringJoiner(", ", "RangingRequest: mRttPeers=[", "]");
+ for (ResponderConfig rc : mRttPeers) {
+ sj.add(rc.toString());
+ }
+ return sj.toString();
+ }
+
+ /** @hide */
+ public void enforceValidity(boolean awareSupported) {
+ if (mRttPeers.size() > MAX_PEERS) {
+ throw new IllegalArgumentException(
+ "Ranging to too many peers requested. Use getMaxPeers() API to get limit.");
+ }
+
+ for (ResponderConfig peer: mRttPeers) {
+ if (!peer.isValid(awareSupported)) {
+ throw new IllegalArgumentException("Invalid Responder specification");
+ }
+ }
+ }
+
+ /**
+ * Builder class used to construct {@link RangingRequest} objects.
+ */
+ public static final class Builder {
+ private List<ResponderConfig> mRttPeers = new ArrayList<>();
+
+ /**
+ * Add the device specified by the {@link ScanResult} to the list of devices with
+ * which to measure range. The total number of peers added to a request cannot exceed the
+ * limit specified by {@link #getMaxPeers()}.
+ * <p>
+ * Ranging may not be supported if the Access Point does not support IEEE 802.11mc. Use
+ * {@link ScanResult#is80211mcResponder()} to verify the Access Point's capabilities. If
+ * not supported the result status will be
+ * {@link RangingResult#STATUS_RESPONDER_DOES_NOT_SUPPORT_IEEE80211MC}.
+ *
+ * @param apInfo Information of an Access Point (AP) obtained in a Scan Result.
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder addAccessPoint(@NonNull ScanResult apInfo) {
+ if (apInfo == null) {
+ throw new IllegalArgumentException("Null ScanResult!");
+ }
+ return addResponder(ResponderConfig.fromScanResult(apInfo));
+ }
+
+ /**
+ * Add the devices specified by the {@link ScanResult}s to the list of devices with
+ * which to measure range. The total number of peers added to a request cannot exceed the
+ * limit specified by {@link #getMaxPeers()}.
+ * <p>
+ * Ranging may not be supported if the Access Point does not support IEEE 802.11mc. Use
+ * {@link ScanResult#is80211mcResponder()} to verify the Access Point's capabilities. If
+ * not supported the result status will be
+ * {@link RangingResult#STATUS_RESPONDER_DOES_NOT_SUPPORT_IEEE80211MC}.
+ *
+ * @param apInfos Information of an Access Points (APs) obtained in a Scan Result.
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder addAccessPoints(@NonNull List<ScanResult> apInfos) {
+ if (apInfos == null) {
+ throw new IllegalArgumentException("Null list of ScanResults!");
+ }
+ for (ScanResult scanResult : apInfos) {
+ addAccessPoint(scanResult);
+ }
+ return this;
+ }
+
+ /**
+ * Add the device specified by the {@code peerMacAddress} to the list of devices with
+ * which to measure range.
+ * <p>
+ * The MAC address may be obtained out-of-band from a peer Wi-Fi Aware device. A Wi-Fi
+ * Aware device may obtain its MAC address using the {@link IdentityChangedListener}
+ * provided to
+ * {@link WifiAwareManager#attach(AttachCallback, IdentityChangedListener, Handler)}.
+ * <p>
+ * Note: in order to use this API the device must support Wi-Fi Aware
+ * {@link android.net.wifi.aware}. The peer device which is being ranged to must be
+ * configured to publish a service (with any name) with:
+ * <li>Type {@link android.net.wifi.aware.PublishConfig#PUBLISH_TYPE_UNSOLICITED}.
+ * <li>Ranging enabled
+ * {@link android.net.wifi.aware.PublishConfig.Builder#setRangingEnabled(boolean)}.
+ *
+ * @param peerMacAddress The MAC address of the Wi-Fi Aware peer.
+ * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder addWifiAwarePeer(@NonNull MacAddress peerMacAddress) {
+ if (peerMacAddress == null) {
+ throw new IllegalArgumentException("Null peer MAC address");
+ }
+ return addResponder(
+ ResponderConfig.fromWifiAwarePeerMacAddressWithDefaults(peerMacAddress));
+ }
+
+ /**
+ * Add a device specified by a {@link PeerHandle} to the list of devices with which to
+ * measure range.
+ * <p>
+ * The {@link PeerHandle} may be obtained as part of the Wi-Fi Aware discovery process. E.g.
+ * using {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], List)}.
+ * <p>
+ * Note: in order to use this API the device must support Wi-Fi Aware
+ * {@link android.net.wifi.aware}. The peer device which is being ranged to must be
+ * configured to publish a service (with any name) with:
+ * <li>Type {@link android.net.wifi.aware.PublishConfig#PUBLISH_TYPE_UNSOLICITED}.
+ * <li>Ranging enabled
+ * {@link android.net.wifi.aware.PublishConfig.Builder#setRangingEnabled(boolean)}.
+ *
+ * @param peerHandle The peer handler of the peer Wi-Fi Aware device.
+ * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder addWifiAwarePeer(@NonNull PeerHandle peerHandle) {
+ if (peerHandle == null) {
+ throw new IllegalArgumentException("Null peer handler (identifier)");
+ }
+
+ return addResponder(ResponderConfig.fromWifiAwarePeerHandleWithDefaults(peerHandle));
+ }
+
+ /**
+ * Add the Responder device specified by the {@link ResponderConfig} to the list of devices
+ * with which to measure range. The total number of peers added to the request cannot exceed
+ * the limit specified by {@link #getMaxPeers()}.
+ *
+ * @param responder Information on the RTT Responder.
+ * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public Builder addResponder(@NonNull ResponderConfig responder) {
+ if (responder == null) {
+ throw new IllegalArgumentException("Null Responder!");
+ }
+
+ mRttPeers.add(responder);
+ return this;
+ }
+
+ /**
+ * Build {@link RangingRequest} given the current configurations made on the
+ * builder.
+ */
+ public RangingRequest build() {
+ return new RangingRequest(mRttPeers);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof RangingRequest)) {
+ return false;
+ }
+
+ RangingRequest lhs = (RangingRequest) o;
+
+ return mRttPeers.size() == lhs.mRttPeers.size() && mRttPeers.containsAll(lhs.mRttPeers);
+ }
+
+ @Override
+ public int hashCode() {
+ return mRttPeers.hashCode();
+ }
+}
diff --git a/android/net/wifi/rtt/RangingResult.java b/android/net/wifi/rtt/RangingResult.java
new file mode 100644
index 0000000..a065bbc
--- /dev/null
+++ b/android/net/wifi/rtt/RangingResult.java
@@ -0,0 +1,438 @@
+/*
+ * 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 android.net.wifi.rtt;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.net.MacAddress;
+import android.net.wifi.aware.PeerHandle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Ranging result for a request started by
+ * {@link WifiRttManager#startRanging(RangingRequest, java.util.concurrent.Executor, RangingResultCallback)}.
+ * Results are returned in {@link RangingResultCallback#onRangingResults(List)}.
+ * <p>
+ * A ranging result is the distance measurement result for a single device specified in the
+ * {@link RangingRequest}.
+ */
+public final class RangingResult implements Parcelable {
+ private static final String TAG = "RangingResult";
+ private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+
+ /** @hide */
+ @IntDef({STATUS_SUCCESS, STATUS_FAIL, STATUS_RESPONDER_DOES_NOT_SUPPORT_IEEE80211MC})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RangeResultStatus {
+ }
+
+ /**
+ * Individual range request status, {@link #getStatus()}. Indicates ranging operation was
+ * successful and distance value is valid.
+ */
+ public static final int STATUS_SUCCESS = 0;
+
+ /**
+ * Individual range request status, {@link #getStatus()}. Indicates ranging operation failed
+ * and the distance value is invalid.
+ */
+ public static final int STATUS_FAIL = 1;
+
+ /**
+ * Individual range request status, {@link #getStatus()}. Indicates that the ranging operation
+ * failed because the specified peer does not support IEEE 802.11mc RTT operations. Support by
+ * an Access Point can be confirmed using
+ * {@link android.net.wifi.ScanResult#is80211mcResponder()}.
+ * <p>
+ * On such a failure, the individual result fields of {@link RangingResult} such as
+ * {@link RangingResult#getDistanceMm()} are invalid.
+ */
+ public static final int STATUS_RESPONDER_DOES_NOT_SUPPORT_IEEE80211MC = 2;
+
+ private final int mStatus;
+ private final MacAddress mMac;
+ private final PeerHandle mPeerHandle;
+ private final int mDistanceMm;
+ private final int mDistanceStdDevMm;
+ private final int mRssi;
+ private final int mNumAttemptedMeasurements;
+ private final int mNumSuccessfulMeasurements;
+ private final byte[] mLci;
+ private final byte[] mLcr;
+ private final ResponderLocation mResponderLocation;
+ private final long mTimestamp;
+
+ /** @hide */
+ public RangingResult(@RangeResultStatus int status, @NonNull MacAddress mac, int distanceMm,
+ int distanceStdDevMm, int rssi, int numAttemptedMeasurements,
+ int numSuccessfulMeasurements, byte[] lci, byte[] lcr,
+ ResponderLocation responderLocation, long timestamp) {
+ mStatus = status;
+ mMac = mac;
+ mPeerHandle = null;
+ mDistanceMm = distanceMm;
+ mDistanceStdDevMm = distanceStdDevMm;
+ mRssi = rssi;
+ mNumAttemptedMeasurements = numAttemptedMeasurements;
+ mNumSuccessfulMeasurements = numSuccessfulMeasurements;
+ mLci = lci == null ? EMPTY_BYTE_ARRAY : lci;
+ mLcr = lcr == null ? EMPTY_BYTE_ARRAY : lcr;
+ mResponderLocation = responderLocation;
+ mTimestamp = timestamp;
+ }
+
+ /** @hide */
+ public RangingResult(@RangeResultStatus int status, PeerHandle peerHandle, int distanceMm,
+ int distanceStdDevMm, int rssi, int numAttemptedMeasurements,
+ int numSuccessfulMeasurements, byte[] lci, byte[] lcr,
+ ResponderLocation responderLocation, long timestamp) {
+ mStatus = status;
+ mMac = null;
+ mPeerHandle = peerHandle;
+ mDistanceMm = distanceMm;
+ mDistanceStdDevMm = distanceStdDevMm;
+ mRssi = rssi;
+ mNumAttemptedMeasurements = numAttemptedMeasurements;
+ mNumSuccessfulMeasurements = numSuccessfulMeasurements;
+ mLci = lci == null ? EMPTY_BYTE_ARRAY : lci;
+ mLcr = lcr == null ? EMPTY_BYTE_ARRAY : lcr;
+ mResponderLocation = responderLocation;
+ mTimestamp = timestamp;
+ }
+
+ /**
+ * @return The status of ranging measurement: {@link #STATUS_SUCCESS} in case of success, and
+ * {@link #STATUS_FAIL} in case of failure.
+ */
+ @RangeResultStatus
+ public int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * @return The MAC address of the device whose range measurement was requested. Will correspond
+ * to the MAC address of the device in the {@link RangingRequest}.
+ * <p>
+ * Will return a {@code null} for results corresponding to requests issued using a {@code
+ * PeerHandle}, i.e. using the {@link RangingRequest.Builder#addWifiAwarePeer(PeerHandle)} API.
+ */
+ @Nullable
+ public MacAddress getMacAddress() {
+ return mMac;
+ }
+
+ /**
+ * @return The PeerHandle of the device whose reange measurement was requested. Will correspond
+ * to the PeerHandle of the devices requested using
+ * {@link RangingRequest.Builder#addWifiAwarePeer(PeerHandle)}.
+ * <p>
+ * Will return a {@code null} for results corresponding to requests issued using a MAC address.
+ */
+ @Nullable public PeerHandle getPeerHandle() {
+ return mPeerHandle;
+ }
+
+ /**
+ * @return The distance (in mm) to the device specified by {@link #getMacAddress()} or
+ * {@link #getPeerHandle()}.
+ * <p>
+ * Note: the measured distance may be negative for very close devices.
+ * <p>
+ * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+ * exception.
+ */
+ public int getDistanceMm() {
+ if (mStatus != STATUS_SUCCESS) {
+ throw new IllegalStateException(
+ "getDistanceMm(): invoked on an invalid result: getStatus()=" + mStatus);
+ }
+ return mDistanceMm;
+ }
+
+ /**
+ * @return The standard deviation of the measured distance (in mm) to the device specified by
+ * {@link #getMacAddress()} or {@link #getPeerHandle()}. The standard deviation is calculated
+ * over the measurements executed in a single RTT burst. The number of measurements is returned
+ * by {@link #getNumSuccessfulMeasurements()} - 0 successful measurements indicate that the
+ * standard deviation is not valid (a valid standard deviation requires at least 2 data points).
+ * <p>
+ * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+ * exception.
+ */
+ public int getDistanceStdDevMm() {
+ if (mStatus != STATUS_SUCCESS) {
+ throw new IllegalStateException(
+ "getDistanceStdDevMm(): invoked on an invalid result: getStatus()=" + mStatus);
+ }
+ return mDistanceStdDevMm;
+ }
+
+ /**
+ * @return The average RSSI, in units of dBm, observed during the RTT measurement.
+ * <p>
+ * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+ * exception.
+ */
+ public int getRssi() {
+ if (mStatus != STATUS_SUCCESS) {
+ throw new IllegalStateException(
+ "getRssi(): invoked on an invalid result: getStatus()=" + mStatus);
+ }
+ return mRssi;
+ }
+
+ /**
+ * @return The number of attempted measurements used in the RTT exchange resulting in this set
+ * of results. The number of successful measurements is returned by
+ * {@link #getNumSuccessfulMeasurements()} which at most, if there are no errors, will be 1 less
+ * that the number of attempted measurements.
+ * <p>
+ * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+ * exception.
+ */
+ public int getNumAttemptedMeasurements() {
+ if (mStatus != STATUS_SUCCESS) {
+ throw new IllegalStateException(
+ "getNumAttemptedMeasurements(): invoked on an invalid result: getStatus()="
+ + mStatus);
+ }
+ return mNumAttemptedMeasurements;
+ }
+
+ /**
+ * @return The number of successful measurements used to calculate the distance and standard
+ * deviation. If the number of successful measurements if 1 then then standard deviation,
+ * returned by {@link #getDistanceStdDevMm()}, is not valid (a 0 is returned for the standard
+ * deviation).
+ * <p>
+ * The total number of measurement attempts is returned by
+ * {@link #getNumAttemptedMeasurements()}. The number of successful measurements will be at
+ * most 1 less then the number of attempted measurements.
+ * <p>
+ * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+ * exception.
+ */
+ public int getNumSuccessfulMeasurements() {
+ if (mStatus != STATUS_SUCCESS) {
+ throw new IllegalStateException(
+ "getNumSuccessfulMeasurements(): invoked on an invalid result: getStatus()="
+ + mStatus);
+ }
+ return mNumSuccessfulMeasurements;
+ }
+
+ /**
+ * @return The unverified responder location represented as {@link ResponderLocation} which
+ * captures location information the responder is programmed to broadcast. The responder
+ * location is referred to as unverified, because we are relying on the device/site
+ * administrator to correctly configure its location data.
+ * <p>
+ * Will return a {@code null} when the location information cannot be parsed.
+ * <p>
+ * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+ * exception.
+ */
+ @Nullable
+ public ResponderLocation getUnverifiedResponderLocation() {
+ if (mStatus != STATUS_SUCCESS) {
+ throw new IllegalStateException(
+ "getUnverifiedResponderLocation(): invoked on an invalid result: getStatus()="
+ + mStatus);
+ }
+ return mResponderLocation;
+ }
+
+ /**
+ * @return The Location Configuration Information (LCI) as self-reported by the peer. The format
+ * is specified in the IEEE 802.11-2016 specifications, section 9.4.2.22.10.
+ * <p>
+ * Note: the information is NOT validated - use with caution. Consider validating it with
+ * other sources of information before using it.
+ *
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public byte[] getLci() {
+ if (mStatus != STATUS_SUCCESS) {
+ throw new IllegalStateException(
+ "getLci(): invoked on an invalid result: getStatus()=" + mStatus);
+ }
+ return mLci;
+ }
+
+ /**
+ * @return The Location Civic report (LCR) as self-reported by the peer. The format
+ * is specified in the IEEE 802.11-2016 specifications, section 9.4.2.22.13.
+ * <p>
+ * Note: the information is NOT validated - use with caution. Consider validating it with
+ * other sources of information before using it.
+ *
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public byte[] getLcr() {
+ if (mStatus != STATUS_SUCCESS) {
+ throw new IllegalStateException(
+ "getReportedLocationCivic(): invoked on an invalid result: getStatus()="
+ + mStatus);
+ }
+ return mLcr;
+ }
+
+ /**
+ * @return The timestamp at which the ranging operation was performed. The timestamp is in
+ * milliseconds since boot, including time spent in sleep, corresponding to values provided by
+ * {@link android.os.SystemClock#elapsedRealtime()}.
+ * <p>
+ * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+ * exception.
+ */
+ public long getRangingTimestampMillis() {
+ if (mStatus != STATUS_SUCCESS) {
+ throw new IllegalStateException(
+ "getRangingTimestampMillis(): invoked on an invalid result: getStatus()="
+ + mStatus);
+ }
+ return mTimestamp;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mStatus);
+ if (mMac == null) {
+ dest.writeBoolean(false);
+ } else {
+ dest.writeBoolean(true);
+ mMac.writeToParcel(dest, flags);
+ }
+ if (mPeerHandle == null) {
+ dest.writeBoolean(false);
+ } else {
+ dest.writeBoolean(true);
+ dest.writeInt(mPeerHandle.peerId);
+ }
+ dest.writeInt(mDistanceMm);
+ dest.writeInt(mDistanceStdDevMm);
+ dest.writeInt(mRssi);
+ dest.writeInt(mNumAttemptedMeasurements);
+ dest.writeInt(mNumSuccessfulMeasurements);
+ dest.writeByteArray(mLci);
+ dest.writeByteArray(mLcr);
+ dest.writeParcelable(mResponderLocation, flags);
+ dest.writeLong(mTimestamp);
+ }
+
+ public static final @android.annotation.NonNull Creator<RangingResult> CREATOR = new Creator<RangingResult>() {
+ @Override
+ public RangingResult[] newArray(int size) {
+ return new RangingResult[size];
+ }
+
+ @Override
+ public RangingResult createFromParcel(Parcel in) {
+ int status = in.readInt();
+ boolean macAddressPresent = in.readBoolean();
+ MacAddress mac = null;
+ if (macAddressPresent) {
+ mac = MacAddress.CREATOR.createFromParcel(in);
+ }
+ boolean peerHandlePresent = in.readBoolean();
+ PeerHandle peerHandle = null;
+ if (peerHandlePresent) {
+ peerHandle = new PeerHandle(in.readInt());
+ }
+ int distanceMm = in.readInt();
+ int distanceStdDevMm = in.readInt();
+ int rssi = in.readInt();
+ int numAttemptedMeasurements = in.readInt();
+ int numSuccessfulMeasurements = in.readInt();
+ byte[] lci = in.createByteArray();
+ byte[] lcr = in.createByteArray();
+ ResponderLocation responderLocation =
+ in.readParcelable(this.getClass().getClassLoader());
+ long timestamp = in.readLong();
+ if (peerHandlePresent) {
+ return new RangingResult(status, peerHandle, distanceMm, distanceStdDevMm, rssi,
+ numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr,
+ responderLocation, timestamp);
+ } else {
+ return new RangingResult(status, mac, distanceMm, distanceStdDevMm, rssi,
+ numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr,
+ responderLocation, timestamp);
+ }
+ }
+ };
+
+ /** @hide */
+ @Override
+ public String toString() {
+ return new StringBuilder("RangingResult: [status=").append(mStatus).append(", mac=").append(
+ mMac).append(", peerHandle=").append(
+ mPeerHandle == null ? "<null>" : mPeerHandle.peerId).append(", distanceMm=").append(
+ mDistanceMm).append(", distanceStdDevMm=").append(mDistanceStdDevMm).append(
+ ", rssi=").append(mRssi).append(", numAttemptedMeasurements=").append(
+ mNumAttemptedMeasurements).append(", numSuccessfulMeasurements=").append(
+ mNumSuccessfulMeasurements).append(", lci=").append(mLci).append(", lcr=").append(
+ mLcr).append(", responderLocation=").append(mResponderLocation)
+ .append(", timestamp=").append(mTimestamp).append("]").toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof RangingResult)) {
+ return false;
+ }
+
+ RangingResult lhs = (RangingResult) o;
+
+ return mStatus == lhs.mStatus && Objects.equals(mMac, lhs.mMac) && Objects.equals(
+ mPeerHandle, lhs.mPeerHandle) && mDistanceMm == lhs.mDistanceMm
+ && mDistanceStdDevMm == lhs.mDistanceStdDevMm && mRssi == lhs.mRssi
+ && mNumAttemptedMeasurements == lhs.mNumAttemptedMeasurements
+ && mNumSuccessfulMeasurements == lhs.mNumSuccessfulMeasurements
+ && Arrays.equals(mLci, lhs.mLci) && Arrays.equals(mLcr, lhs.mLcr)
+ && mTimestamp == lhs.mTimestamp
+ && Objects.equals(mResponderLocation, lhs.mResponderLocation);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mStatus, mMac, mPeerHandle, mDistanceMm, mDistanceStdDevMm, mRssi,
+ mNumAttemptedMeasurements, mNumSuccessfulMeasurements, mLci, mLcr,
+ mResponderLocation, mTimestamp);
+ }
+}
diff --git a/android/net/wifi/rtt/RangingResultCallback.java b/android/net/wifi/rtt/RangingResultCallback.java
new file mode 100644
index 0000000..fa7d79e
--- /dev/null
+++ b/android/net/wifi/rtt/RangingResultCallback.java
@@ -0,0 +1,70 @@
+/*
+ * 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 android.net.wifi.rtt;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * Base class for ranging result callbacks. Should be extended by applications and set when calling
+ * {@link WifiRttManager#startRanging(RangingRequest, java.util.concurrent.Executor, RangingResultCallback)}.
+ * If the ranging operation fails in whole (not attempted) then {@link #onRangingFailure(int)}
+ * will be called with a failure code. If the ranging operation is performed for each of the
+ * requested peers then the {@link #onRangingResults(List)} will be called with the set of
+ * results (@link {@link RangingResult}, each of which has its own success/failure code
+ * {@link RangingResult#getStatus()}.
+ */
+public abstract class RangingResultCallback {
+ /** @hide */
+ @IntDef({STATUS_CODE_FAIL, STATUS_CODE_FAIL_RTT_NOT_AVAILABLE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RangingOperationStatus {
+ }
+
+ /**
+ * A failure code for the whole ranging request operation. Indicates a failure.
+ */
+ public static final int STATUS_CODE_FAIL = 1;
+
+ /**
+ * A failure code for the whole ranging request operation. Indicates that the request failed due
+ * to RTT not being available - e.g. Wi-Fi was disabled. Use the
+ * {@link WifiRttManager#isAvailable()} and {@link WifiRttManager#ACTION_WIFI_RTT_STATE_CHANGED}
+ * to track RTT availability.
+ */
+ public static final int STATUS_CODE_FAIL_RTT_NOT_AVAILABLE = 2;
+
+ /**
+ * Called when a ranging operation failed in whole - i.e. no ranging operation to any of the
+ * devices specified in the request was attempted.
+ *
+ * @param code A status code indicating the type of failure.
+ */
+ public abstract void onRangingFailure(@RangingOperationStatus int code);
+
+ /**
+ * Called when a ranging operation was executed. The list of results corresponds to devices
+ * specified in the ranging request.
+ *
+ * @param results List of range measurements, one per requested device.
+ */
+ public abstract void onRangingResults(@NonNull List<RangingResult> results);
+}
diff --git a/android/net/wifi/rtt/ResponderConfig.java b/android/net/wifi/rtt/ResponderConfig.java
new file mode 100644
index 0000000..e6ae483
--- /dev/null
+++ b/android/net/wifi/rtt/ResponderConfig.java
@@ -0,0 +1,500 @@
+/*
+ * 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 android.net.wifi.rtt;
+
+import static android.net.wifi.ScanResult.InformationElement.EID_HT_CAPABILITIES;
+import static android.net.wifi.ScanResult.InformationElement.EID_VHT_CAPABILITIES;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.net.MacAddress;
+import android.net.wifi.ScanResult;
+import android.net.wifi.aware.PeerHandle;
+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.Objects;
+
+/**
+ * Defines the configuration of an IEEE 802.11mc Responder. The Responder may be an Access Point
+ * (AP), a Wi-Fi Aware device, or a manually configured Responder.
+ * <p>
+ * A Responder configuration may be constructed from a {@link ScanResult} or manually (with the
+ * data obtained out-of-band from a peer).
+ *
+ * @hide
+ */
+@SystemApi
+public final class ResponderConfig implements Parcelable {
+ private static final String TAG = "ResponderConfig";
+ private static final int AWARE_BAND_2_DISCOVERY_CHANNEL = 2437;
+
+ /** @hide */
+ @IntDef({RESPONDER_AP, RESPONDER_STA, RESPONDER_P2P_GO, RESPONDER_P2P_CLIENT, RESPONDER_AWARE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResponderType {
+ }
+
+ /**
+ * Responder is an AP.
+ */
+ public static final int RESPONDER_AP = 0;
+ /**
+ * Responder is a STA.
+ */
+ public static final int RESPONDER_STA = 1;
+ /**
+ * Responder is a Wi-Fi Direct Group Owner (GO).
+ */
+ public static final int RESPONDER_P2P_GO = 2;
+ /**
+ * Responder is a Wi-Fi Direct Group Client.
+ */
+ public static final int RESPONDER_P2P_CLIENT = 3;
+ /**
+ * Responder is a Wi-Fi Aware device.
+ */
+ public static final int RESPONDER_AWARE = 4;
+
+
+ /** @hide */
+ @IntDef({
+ CHANNEL_WIDTH_20MHZ, CHANNEL_WIDTH_40MHZ, CHANNEL_WIDTH_80MHZ, CHANNEL_WIDTH_160MHZ,
+ CHANNEL_WIDTH_80MHZ_PLUS_MHZ})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ChannelWidth {
+ }
+
+ /**
+ * Channel bandwidth is 20 MHZ
+ */
+ public static final int CHANNEL_WIDTH_20MHZ = 0;
+ /**
+ * Channel bandwidth is 40 MHZ
+ */
+ public static final int CHANNEL_WIDTH_40MHZ = 1;
+ /**
+ * Channel bandwidth is 80 MHZ
+ */
+ public static final int CHANNEL_WIDTH_80MHZ = 2;
+ /**
+ * Channel bandwidth is 160 MHZ
+ */
+ public static final int CHANNEL_WIDTH_160MHZ = 3;
+ /**
+ * Channel bandwidth is 160 MHZ, but 80MHZ + 80MHZ
+ */
+ public static final int CHANNEL_WIDTH_80MHZ_PLUS_MHZ = 4;
+
+ /** @hide */
+ @IntDef({PREAMBLE_LEGACY, PREAMBLE_HT, PREAMBLE_VHT})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PreambleType {
+ }
+
+ /**
+ * Preamble type: Legacy.
+ */
+ public static final int PREAMBLE_LEGACY = 0;
+
+ /**
+ * Preamble type: HT.
+ */
+ public static final int PREAMBLE_HT = 1;
+
+ /**
+ * Preamble type: VHT.
+ */
+ public static final int PREAMBLE_VHT = 2;
+
+
+ /**
+ * The MAC address of the Responder. Will be null if a Wi-Fi Aware peer identifier (the
+ * peerHandle field) ise used to identify the Responder.
+ */
+ public final MacAddress macAddress;
+
+ /**
+ * The peer identifier of a Wi-Fi Aware Responder. Will be null if a MAC Address (the macAddress
+ * field) is used to identify the Responder.
+ */
+ public final PeerHandle peerHandle;
+
+ /**
+ * The device type of the Responder.
+ */
+ public final int responderType;
+
+ /**
+ * Indicates whether the Responder device supports IEEE 802.11mc.
+ */
+ public final boolean supports80211mc;
+
+ /**
+ * Responder channel bandwidth, specified using {@link ChannelWidth}.
+ */
+ public final int channelWidth;
+
+ /**
+ * The primary 20 MHz frequency (in MHz) of the channel of the Responder.
+ */
+ public final int frequency;
+
+ /**
+ * Not used if the {@link #channelWidth} is 20 MHz. If the Responder uses 40, 80 or 160 MHz,
+ * this is the center frequency (in MHz), if the Responder uses 80 + 80 MHz, this is the
+ * center frequency of the first segment (in MHz).
+ */
+ public final int centerFreq0;
+
+ /**
+ * Only used if the {@link #channelWidth} is 80 + 80 MHz. If the Responder uses 80 + 80 MHz,
+ * this is the center frequency of the second segment (in MHz).
+ */
+ public final int centerFreq1;
+
+ /**
+ * The preamble used by the Responder, specified using {@link PreambleType}.
+ */
+ public final int preamble;
+
+ /**
+ * Constructs Responder configuration, using a MAC address to identify the Responder.
+ *
+ * @param macAddress The MAC address of the Responder.
+ * @param responderType The type of the responder device, specified using
+ * {@link ResponderType}.
+ * @param supports80211mc Indicates whether the responder supports IEEE 802.11mc.
+ * @param channelWidth Responder channel bandwidth, specified using {@link ChannelWidth}.
+ * @param frequency The primary 20 MHz frequency (in MHz) of the channel of the Responder.
+ * @param centerFreq0 Not used if the {@code channelWidth} is 20 MHz. If the Responder uses
+ * 40, 80 or 160 MHz, this is the center frequency (in MHz), if the
+ * Responder uses 80 + 80 MHz, this is the center frequency of the first
+ * segment (in MHz).
+ * @param centerFreq1 Only used if the {@code channelWidth} is 80 + 80 MHz. If the
+ * Responder
+ * uses 80 + 80 MHz, this is the center frequency of the second segment
+ * (in
+ * MHz).
+ * @param preamble The preamble used by the Responder, specified using
+ * {@link PreambleType}.
+ */
+ public ResponderConfig(@NonNull MacAddress macAddress, @ResponderType int responderType,
+ boolean supports80211mc, @ChannelWidth int channelWidth, int frequency, int centerFreq0,
+ int centerFreq1, @PreambleType int preamble) {
+ if (macAddress == null) {
+ throw new IllegalArgumentException(
+ "Invalid ResponderConfig - must specify a MAC address");
+ }
+ this.macAddress = macAddress;
+ this.peerHandle = null;
+ this.responderType = responderType;
+ this.supports80211mc = supports80211mc;
+ this.channelWidth = channelWidth;
+ this.frequency = frequency;
+ this.centerFreq0 = centerFreq0;
+ this.centerFreq1 = centerFreq1;
+ this.preamble = preamble;
+ }
+
+ /**
+ * Constructs Responder configuration, using a Wi-Fi Aware PeerHandle to identify the Responder.
+ *
+ * @param peerHandle The Wi-Fi Aware peer identifier of the Responder.
+ * @param responderType The type of the responder device, specified using
+ * {@link ResponderType}.
+ * @param supports80211mc Indicates whether the responder supports IEEE 802.11mc.
+ * @param channelWidth Responder channel bandwidth, specified using {@link ChannelWidth}.
+ * @param frequency The primary 20 MHz frequency (in MHz) of the channel of the Responder.
+ * @param centerFreq0 Not used if the {@code channelWidth} is 20 MHz. If the Responder uses
+ * 40, 80 or 160 MHz, this is the center frequency (in MHz), if the
+ * Responder uses 80 + 80 MHz, this is the center frequency of the first
+ * segment (in MHz).
+ * @param centerFreq1 Only used if the {@code channelWidth} is 80 + 80 MHz. If the
+ * Responder
+ * uses 80 + 80 MHz, this is the center frequency of the second segment
+ * (in
+ * MHz).
+ * @param preamble The preamble used by the Responder, specified using
+ * {@link PreambleType}.
+ */
+ public ResponderConfig(@NonNull PeerHandle peerHandle, @ResponderType int responderType,
+ boolean supports80211mc, @ChannelWidth int channelWidth, int frequency, int centerFreq0,
+ int centerFreq1, @PreambleType int preamble) {
+ this.macAddress = null;
+ this.peerHandle = peerHandle;
+ this.responderType = responderType;
+ this.supports80211mc = supports80211mc;
+ this.channelWidth = channelWidth;
+ this.frequency = frequency;
+ this.centerFreq0 = centerFreq0;
+ this.centerFreq1 = centerFreq1;
+ this.preamble = preamble;
+ }
+
+ /**
+ * Constructs Responder configuration. This is an internal-only constructor which specifies both
+ * a MAC address and a Wi-Fi PeerHandle to identify the Responder.
+ *
+ * @param macAddress The MAC address of the Responder.
+ * @param peerHandle The Wi-Fi Aware peer identifier of the Responder.
+ * @param responderType The type of the responder device, specified using
+ * {@link ResponderType}.
+ * @param supports80211mc Indicates whether the responder supports IEEE 802.11mc.
+ * @param channelWidth Responder channel bandwidth, specified using {@link ChannelWidth}.
+ * @param frequency The primary 20 MHz frequency (in MHz) of the channel of the Responder.
+ * @param centerFreq0 Not used if the {@code channelWidth} is 20 MHz. If the Responder uses
+ * 40, 80 or 160 MHz, this is the center frequency (in MHz), if the
+ * Responder uses 80 + 80 MHz, this is the center frequency of the first
+ * segment (in MHz).
+ * @param centerFreq1 Only used if the {@code channelWidth} is 80 + 80 MHz. If the
+ * Responder
+ * uses 80 + 80 MHz, this is the center frequency of the second segment
+ * (in
+ * MHz).
+ * @param preamble The preamble used by the Responder, specified using
+ * {@link PreambleType}.
+ * @hide
+ */
+ public ResponderConfig(@NonNull MacAddress macAddress, @NonNull PeerHandle peerHandle,
+ @ResponderType int responderType, boolean supports80211mc,
+ @ChannelWidth int channelWidth, int frequency, int centerFreq0, int centerFreq1,
+ @PreambleType int preamble) {
+ this.macAddress = macAddress;
+ this.peerHandle = peerHandle;
+ this.responderType = responderType;
+ this.supports80211mc = supports80211mc;
+ this.channelWidth = channelWidth;
+ this.frequency = frequency;
+ this.centerFreq0 = centerFreq0;
+ this.centerFreq1 = centerFreq1;
+ this.preamble = preamble;
+ }
+
+ /**
+ * Creates a Responder configuration from a {@link ScanResult} corresponding to an Access
+ * Point (AP), which can be obtained from {@link android.net.wifi.WifiManager#getScanResults()}.
+ */
+ public static ResponderConfig fromScanResult(ScanResult scanResult) {
+ MacAddress macAddress = MacAddress.fromString(scanResult.BSSID);
+ int responderType = RESPONDER_AP;
+ boolean supports80211mc = scanResult.is80211mcResponder();
+ int channelWidth = translateScanResultChannelWidth(scanResult.channelWidth);
+ int frequency = scanResult.frequency;
+ int centerFreq0 = scanResult.centerFreq0;
+ int centerFreq1 = scanResult.centerFreq1;
+
+ int preamble;
+ if (scanResult.informationElements != null && scanResult.informationElements.length != 0) {
+ boolean htCapabilitiesPresent = false;
+ boolean vhtCapabilitiesPresent = false;
+ for (ScanResult.InformationElement ie : scanResult.informationElements) {
+ if (ie.id == EID_HT_CAPABILITIES) {
+ htCapabilitiesPresent = true;
+ } else if (ie.id == EID_VHT_CAPABILITIES) {
+ vhtCapabilitiesPresent = true;
+ }
+ }
+ if (vhtCapabilitiesPresent) {
+ preamble = PREAMBLE_VHT;
+ } else if (htCapabilitiesPresent) {
+ preamble = PREAMBLE_HT;
+ } else {
+ preamble = PREAMBLE_LEGACY;
+ }
+ } else {
+ Log.e(TAG, "Scan Results do not contain IEs - using backup method to select preamble");
+ if (channelWidth == CHANNEL_WIDTH_80MHZ || channelWidth == CHANNEL_WIDTH_160MHZ) {
+ preamble = PREAMBLE_VHT;
+ } else {
+ preamble = PREAMBLE_HT;
+ }
+ }
+
+ return new ResponderConfig(macAddress, responderType, supports80211mc, channelWidth,
+ frequency, centerFreq0, centerFreq1, preamble);
+ }
+
+ /**
+ * Creates a Responder configuration from a MAC address corresponding to a Wi-Fi Aware
+ * Responder. The Responder parameters are set to defaults.
+ */
+ public static ResponderConfig fromWifiAwarePeerMacAddressWithDefaults(MacAddress macAddress) {
+ /* Note: the parameters are those of the Aware discovery channel (channel 6). A Responder
+ * is expected to be brought up and available to negotiate a maximum accuracy channel
+ * (i.e. Band 5 @ 80MHz). A Responder is brought up on the peer by starting an Aware
+ * Unsolicited Publisher with Ranging enabled.
+ */
+ return new ResponderConfig(macAddress, RESPONDER_AWARE, true, CHANNEL_WIDTH_20MHZ,
+ AWARE_BAND_2_DISCOVERY_CHANNEL, 0, 0, PREAMBLE_HT);
+ }
+
+ /**
+ * Creates a Responder configuration from a {@link PeerHandle} corresponding to a Wi-Fi Aware
+ * Responder. The Responder parameters are set to defaults.
+ */
+ public static ResponderConfig fromWifiAwarePeerHandleWithDefaults(PeerHandle peerHandle) {
+ /* Note: the parameters are those of the Aware discovery channel (channel 6). A Responder
+ * is expected to be brought up and available to negotiate a maximum accuracy channel
+ * (i.e. Band 5 @ 80MHz). A Responder is brought up on the peer by starting an Aware
+ * Unsolicited Publisher with Ranging enabled.
+ */
+ return new ResponderConfig(peerHandle, RESPONDER_AWARE, true, CHANNEL_WIDTH_20MHZ,
+ AWARE_BAND_2_DISCOVERY_CHANNEL, 0, 0, PREAMBLE_HT);
+ }
+
+ /**
+ * Check whether the Responder configuration is valid.
+ *
+ * @return true if valid, false otherwise.
+ * @hide
+ */
+ public boolean isValid(boolean awareSupported) {
+ if (macAddress == null && peerHandle == null || macAddress != null && peerHandle != null) {
+ return false;
+ }
+ if (!awareSupported && responderType == RESPONDER_AWARE) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (macAddress == null) {
+ dest.writeBoolean(false);
+ } else {
+ dest.writeBoolean(true);
+ macAddress.writeToParcel(dest, flags);
+ }
+ if (peerHandle == null) {
+ dest.writeBoolean(false);
+ } else {
+ dest.writeBoolean(true);
+ dest.writeInt(peerHandle.peerId);
+ }
+ dest.writeInt(responderType);
+ dest.writeInt(supports80211mc ? 1 : 0);
+ dest.writeInt(channelWidth);
+ dest.writeInt(frequency);
+ dest.writeInt(centerFreq0);
+ dest.writeInt(centerFreq1);
+ dest.writeInt(preamble);
+ }
+
+ public static final @android.annotation.NonNull Creator<ResponderConfig> CREATOR = new Creator<ResponderConfig>() {
+ @Override
+ public ResponderConfig[] newArray(int size) {
+ return new ResponderConfig[size];
+ }
+
+ @Override
+ public ResponderConfig createFromParcel(Parcel in) {
+ boolean macAddressPresent = in.readBoolean();
+ MacAddress macAddress = null;
+ if (macAddressPresent) {
+ macAddress = MacAddress.CREATOR.createFromParcel(in);
+ }
+ boolean peerHandlePresent = in.readBoolean();
+ PeerHandle peerHandle = null;
+ if (peerHandlePresent) {
+ peerHandle = new PeerHandle(in.readInt());
+ }
+ int responderType = in.readInt();
+ boolean supports80211mc = in.readInt() == 1;
+ int channelWidth = in.readInt();
+ int frequency = in.readInt();
+ int centerFreq0 = in.readInt();
+ int centerFreq1 = in.readInt();
+ int preamble = in.readInt();
+
+ if (peerHandle == null) {
+ return new ResponderConfig(macAddress, responderType, supports80211mc, channelWidth,
+ frequency, centerFreq0, centerFreq1, preamble);
+ } else {
+ return new ResponderConfig(peerHandle, responderType, supports80211mc, channelWidth,
+ frequency, centerFreq0, centerFreq1, preamble);
+ }
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof ResponderConfig)) {
+ return false;
+ }
+
+ ResponderConfig lhs = (ResponderConfig) o;
+
+ return Objects.equals(macAddress, lhs.macAddress) && Objects.equals(peerHandle,
+ lhs.peerHandle) && responderType == lhs.responderType
+ && supports80211mc == lhs.supports80211mc && channelWidth == lhs.channelWidth
+ && frequency == lhs.frequency && centerFreq0 == lhs.centerFreq0
+ && centerFreq1 == lhs.centerFreq1 && preamble == lhs.preamble;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(macAddress, peerHandle, responderType, supports80211mc, channelWidth,
+ frequency, centerFreq0, centerFreq1, preamble);
+ }
+
+ /** @hide */
+ @Override
+ public String toString() {
+ return new StringBuffer("ResponderConfig: macAddress=").append(macAddress).append(
+ ", peerHandle=").append(peerHandle == null ? "<null>" : peerHandle.peerId).append(
+ ", responderType=").append(responderType).append(", supports80211mc=").append(
+ supports80211mc).append(", channelWidth=").append(channelWidth).append(
+ ", frequency=").append(frequency).append(", centerFreq0=").append(
+ centerFreq0).append(", centerFreq1=").append(centerFreq1).append(
+ ", preamble=").append(preamble).toString();
+ }
+
+ /** @hide */
+ static int translateScanResultChannelWidth(int scanResultChannelWidth) {
+ switch (scanResultChannelWidth) {
+ case ScanResult.CHANNEL_WIDTH_20MHZ:
+ return CHANNEL_WIDTH_20MHZ;
+ case ScanResult.CHANNEL_WIDTH_40MHZ:
+ return CHANNEL_WIDTH_40MHZ;
+ case ScanResult.CHANNEL_WIDTH_80MHZ:
+ return CHANNEL_WIDTH_80MHZ;
+ case ScanResult.CHANNEL_WIDTH_160MHZ:
+ return CHANNEL_WIDTH_160MHZ;
+ case ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ:
+ return CHANNEL_WIDTH_80MHZ_PLUS_MHZ;
+ default:
+ throw new IllegalArgumentException(
+ "translateScanResultChannelWidth: bad " + scanResultChannelWidth);
+ }
+ }
+}
diff --git a/android/net/wifi/rtt/ResponderLocation.java b/android/net/wifi/rtt/ResponderLocation.java
new file mode 100644
index 0000000..e1d82f8
--- /dev/null
+++ b/android/net/wifi/rtt/ResponderLocation.java
@@ -0,0 +1,1424 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.rtt;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.location.Address;
+import android.location.Location;
+import android.net.MacAddress;
+import android.net.Uri;
+import android.net.wifi.rtt.CivicLocationKeys.CivicLocationKeysType;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.SparseArray;
+import android.webkit.MimeTypeMap;
+
+import java.lang.annotation.Retention;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * ResponderLocation is both a Location Configuration Information (LCI) decoder and a Location Civic
+ * Report (LCR) decoder for information received from a Wi-Fi Access Point (AP) during Wi-Fi RTT
+ * ranging process.
+ *
+ * <p>This is based on the IEEE P802.11-REVmc/D8.0 spec section 9.4.2.22, under Measurement Report
+ * Element. Subelement location data-fields parsed from separate input LCI and LCR Information
+ * Elements are unified in this class.</p>
+ *
+ * <p>Note: The information provided by this class is broadcast by a responder (usually an Access
+ * Point), and passed on as-is. There is no guarantee this information is accurate or correct, and
+ * as a result developers should carefully consider how this information should be used and provide
+ * corresponding advice to users.</p>
+ */
+public final class ResponderLocation implements Parcelable {
+ private static final int BYTE_MASK = 0xFF;
+ private static final int LSB_IN_BYTE = 0x01;
+ private static final int MSB_IN_BYTE = 0x80;
+ private static final int MIN_BUFFER_SIZE = 3; // length of LEAD_LCI_ELEMENT_BYTES
+ private static final int MAX_BUFFER_SIZE = 256;
+
+ // Information Element (IE) fields
+ private static final byte MEASUREMENT_TOKEN_AUTONOMOUS = 0x01;
+ private static final byte MEASUREMENT_REPORT_MODE = 0x00;
+ private static final byte MEASUREMENT_TYPE_LCI = 0x08;
+ private static final byte MEASUREMENT_TYPE_LCR = 0x0b;
+
+ // LCI Subelement IDs
+ private static final byte SUBELEMENT_LCI = 0x00;
+ private static final byte SUBELEMENT_Z = 0x04;
+ private static final byte SUBELEMENT_USAGE = 0x06;
+ private static final byte SUBELEMENT_BSSID_LIST = 0x07;
+
+ // LCI Subelement Lengths
+ private static final int SUBELEMENT_LCI_LENGTH = 16;
+ private static final int SUBELEMENT_Z_LENGTH = 6;
+ private static final int SUBELEMENT_USAGE_LENGTH1 = 1;
+ private static final int SUBELEMENT_USAGE_LENGTH3 = 3;
+ private static final int SUBELEMENT_BSSID_LIST_MIN_BUFFER_LENGTH = 1;
+
+ private static final byte[] LEAD_LCI_ELEMENT_BYTES = {
+ MEASUREMENT_TOKEN_AUTONOMOUS, MEASUREMENT_REPORT_MODE, MEASUREMENT_TYPE_LCI
+ };
+
+ // Subelement LCI constants
+
+ /* The LCI subelement bit-field lengths are described in Figure 9-214 of the REVmc spec and
+ represented here as a an array of integers */
+ private static final int[] SUBELEMENT_LCI_BIT_FIELD_LENGTHS = {
+ 6, 34, 6, 34, 4, 6, 30, 3, 1, 1, 1, 2
+ };
+ private static final int LATLNG_FRACTION_BITS = 25;
+ private static final int LATLNG_UNCERTAINTY_BASE = 8;
+ private static final int ALTITUDE_FRACTION_BITS = 8;
+ private static final int ALTITUDE_UNCERTAINTY_BASE = 21;
+ private static final double LAT_ABS_LIMIT = 90.0;
+ private static final double LNG_ABS_LIMIT = 180.0;
+ private static final int UNCERTAINTY_UNDEFINED = 0;
+
+ // Subelement LCI fields indices
+ private static final int SUBELEMENT_LCI_LAT_UNCERTAINTY_INDEX = 0;
+ private static final int SUBELEMENT_LCI_LAT_INDEX = 1;
+ private static final int SUBELEMENT_LCI_LNG_UNCERTAINTY_INDEX = 2;
+ private static final int SUBELEMENT_LCI_LNG_INDEX = 3;
+ private static final int SUBELEMENT_LCI_ALT_TYPE_INDEX = 4;
+ private static final int SUBELEMENT_LCI_ALT_UNCERTAINTY_INDEX = 5;
+ private static final int SUBELEMENT_LCI_ALT_INDEX = 6;
+ private static final int SUBELEMENT_LCI_DATUM_INDEX = 7;
+ private static final int SUBELEMENT_LCI_REGLOC_AGREEMENT_INDEX = 8;
+ private static final int SUBELEMENT_LCI_REGLOC_DSE_INDEX = 9;
+ private static final int SUBELEMENT_LCI_DEPENDENT_STA_INDEX = 10;
+ private static final int SUBELEMENT_LCI_VERSION_INDEX = 11;
+
+ /**
+ * The Altitude value is interpreted based on the Altitude Type, and the selected mDatum.
+ *
+ * @hide
+ */
+ @Retention(SOURCE)
+ @IntDef({ALTITUDE_UNDEFINED, ALTITUDE_METERS, ALTITUDE_FLOORS})
+ public @interface AltitudeType {
+ }
+
+ /**
+ * Altitude is not defined for the Responder.
+ * The altitude and altitude uncertainty should not be used: see section 2.4 of IETF RFC 6225.
+ */
+ public static final int ALTITUDE_UNDEFINED = 0;
+ /** Responder Altitude is measured in meters. */
+ public static final int ALTITUDE_METERS = 1;
+ /** Responder Altitude is measured in floors. */
+ public static final int ALTITUDE_FLOORS = 2;
+
+ /**
+ * The Datum value determines how coordinates are organized in relation to the real world.
+ *
+ * @hide
+ */
+ @Retention(SOURCE)
+ @IntDef({DATUM_UNDEFINED, DATUM_WGS84, DATUM_NAD83_NAV88, DATUM_NAD83_MLLW})
+ public @interface DatumType {
+ }
+
+ /** Datum is not defined. */
+ public static final int DATUM_UNDEFINED = 0;
+ /** Datum used is WGS84. */
+ public static final int DATUM_WGS84 = 1;
+ /** Datum used is NAD83 NAV88. */
+ public static final int DATUM_NAD83_NAV88 = 2;
+ /** Datum used is NAD83 MLLW. */
+ public static final int DATUM_NAD83_MLLW = 3;
+
+
+ /** Version of the LCI protocol is 1.0, the only defined protocol at this time. */
+ public static final int LCI_VERSION_1 = 1;
+
+ /** Provider/Source of the location */
+ private static final String LOCATION_PROVIDER = "WiFi Access Point";
+
+ // LCI Subelement Z constants
+ private static final int[] SUBELEMENT_Z_BIT_FIELD_LENGTHS = {2, 14, 24, 8};
+ private static final int Z_FLOOR_NUMBER_FRACTION_BITS = 4;
+ private static final int Z_FLOOR_HEIGHT_FRACTION_BITS = 12;
+ private static final int Z_MAX_HEIGHT_UNCERTAINTY_FACTOR = 25;
+
+ // LCI Subelement Z fields indices
+ private static final int SUBELEMENT_Z_LAT_EXPECTED_TO_MOVE_INDEX = 0;
+ private static final int SUBELEMENT_Z_FLOOR_NUMBER_INDEX = 1;
+ private static final int SUBELEMENT_Z_HEIGHT_ABOVE_FLOOR_INDEX = 2;
+ private static final int SUBELEMENT_Z_HEIGHT_ABOVE_FLOOR_UNCERTAINTY_INDEX = 3;
+
+ // LCI Subelement Usage Rules constants
+ private static final int SUBELEMENT_USAGE_MASK_RETRANSMIT = 0x01;
+ private static final int SUBELEMENT_USAGE_MASK_RETENTION_EXPIRES = 0x02;
+ private static final int SUBELEMENT_USAGE_MASK_STA_LOCATION_POLICY = 0x04;
+
+ // LCI Subelement Usage Rules field indices
+ private static final int SUBELEMENT_USAGE_PARAMS_INDEX = 0; // 8 bits
+
+ // LCI Subelement BSSID List
+ private static final int SUBELEMENT_BSSID_MAX_INDICATOR_INDEX = 0;
+ private static final int SUBELEMENT_BSSID_LIST_INDEX = 1;
+ private static final int BYTES_IN_A_BSSID = 6;
+
+ /**
+ * The Expected-To-Move value determines how mobile we expect the STA to be.
+ *
+ * @hide
+ */
+ @Retention(SOURCE)
+ @IntDef({LOCATION_FIXED, LOCATION_VARIABLE, LOCATION_MOVEMENT_UNKNOWN, LOCATION_RESERVED})
+ public @interface ExpectedToMoveType {
+ }
+
+ /** Location of responder is fixed (does not move) */
+ public static final int LOCATION_FIXED = 0;
+ /** Location of the responder is variable, and may move */
+ public static final int LOCATION_VARIABLE = 1;
+ /** Location of the responder is not known to be either fixed or variable. */
+ public static final int LOCATION_MOVEMENT_UNKNOWN = 2;
+ /** Location of the responder status is a reserved value */
+ public static final int LOCATION_RESERVED = 3;
+
+ // LCR Subelement IDs
+ private static final byte SUBELEMENT_LOCATION_CIVIC = 0x00;
+ private static final byte SUBELEMENT_MAP_IMAGE = 0x05;
+
+ // LCR Subelement Lengths
+ private static final int SUBELEMENT_LOCATION_CIVIC_MIN_LENGTH = 2;
+ private static final int SUBELEMENT_LOCATION_CIVIC_MAX_LENGTH = 256;
+ private static final int SUBELEMENT_MAP_IMAGE_URL_MAX_LENGTH = 256;
+
+ private static final byte[] LEAD_LCR_ELEMENT_BYTES = {
+ MEASUREMENT_TOKEN_AUTONOMOUS, MEASUREMENT_REPORT_MODE, MEASUREMENT_TYPE_LCR
+ };
+
+ // LCR Location Civic Subelement
+ private static final int CIVIC_COUNTRY_CODE_INDEX = 0;
+ private static final int CIVIC_TLV_LIST_INDEX = 2;
+
+ // LCR Map Image Subelement field indexes.
+ private static final int SUBELEMENT_IMAGE_MAP_TYPE_INDEX = 0;
+ private static final int MAP_TYPE_URL_DEFINED = 0;
+ private static final String[] SUPPORTED_IMAGE_FILE_EXTENSIONS = {
+ "",
+ "png",
+ "gif",
+ "jpg",
+ "svg",
+ "dxf",
+ "dwg",
+ "dwf",
+ "cad",
+ "tif",
+ "gml",
+ "kml",
+ "bmp",
+ "pgm",
+ "ppm",
+ "xbm",
+ "xpm",
+ "ico"
+ };
+
+ // General LCI and LCR state
+ private final boolean mIsValid;
+
+ /*
+ These members are not final because we are not sure if the corresponding subelement will be
+ present until after the parsing process. However, the default value should be set as listed.
+ */
+ private boolean mIsLciValid = false;
+ private boolean mIsZValid = false;
+ private boolean mIsUsageValid = true; // By default this is assumed true
+ private boolean mIsBssidListValid = false;
+ private boolean mIsLocationCivicValid = false;
+ private boolean mIsMapImageValid = false;
+
+ // LCI Subelement LCI state
+ private double mLatitudeUncertainty;
+ private double mLatitude;
+ private double mLongitudeUncertainty;
+ private double mLongitude;
+ private int mAltitudeType;
+ private double mAltitudeUncertainty;
+ private double mAltitude;
+ private int mDatum;
+ private boolean mLciRegisteredLocationAgreement;
+ private boolean mLciRegisteredLocationDse;
+ private boolean mLciDependentStation;
+ private int mLciVersion;
+
+ // LCI Subelement Z state
+ private int mExpectedToMove;
+ private double mFloorNumber;
+ private double mHeightAboveFloorMeters;
+ private double mHeightAboveFloorUncertaintyMeters;
+
+ // LCI Subelement Usage Rights state
+ private boolean mUsageRetransmit;
+ private boolean mUsageRetentionExpires;
+ private boolean mUsageExtraInfoOnAssociation;
+
+ // LCI Subelement BSSID List state
+ private ArrayList<MacAddress> mBssidList;
+
+ // LCR Subelement Location Civic state
+ private String mCivicLocationCountryCode;
+ private String mCivicLocationString;
+ private CivicLocation mCivicLocation;
+
+ // LCR Subelement Map Image state
+ private int mMapImageType;
+ private Uri mMapImageUri;
+
+ /**
+ * Constructor
+ *
+ * @param lciBuffer the bytes received in the LCI Measurement Report Information Element
+ * @param lcrBuffer the bytes received in the LCR Measurement Report Information Element
+ *
+ * @hide
+ */
+ public ResponderLocation(byte[] lciBuffer, byte[] lcrBuffer) {
+ boolean isLciIeValid = false;
+ boolean isLcrIeValid = false;
+ setLciSubelementDefaults();
+ setZaxisSubelementDefaults();
+ setUsageSubelementDefaults();
+ setBssidListSubelementDefaults();
+ setCivicLocationSubelementDefaults();
+ setMapImageSubelementDefaults();
+ if (lciBuffer != null && lciBuffer.length > LEAD_LCI_ELEMENT_BYTES.length) {
+ isLciIeValid = parseInformationElementBuffer(
+ MEASUREMENT_TYPE_LCI, lciBuffer, LEAD_LCI_ELEMENT_BYTES);
+ }
+ if (lcrBuffer != null && lcrBuffer.length > LEAD_LCR_ELEMENT_BYTES.length) {
+ isLcrIeValid = parseInformationElementBuffer(
+ MEASUREMENT_TYPE_LCR, lcrBuffer, LEAD_LCR_ELEMENT_BYTES);
+ }
+
+ boolean isLciValid = isLciIeValid && mIsUsageValid
+ && (mIsLciValid || mIsZValid || mIsBssidListValid);
+
+ boolean isLcrValid = isLcrIeValid && mIsUsageValid
+ && (mIsLocationCivicValid || mIsMapImageValid);
+
+ mIsValid = isLciValid || isLcrValid;
+
+ if (!mIsValid) {
+ setLciSubelementDefaults();
+ setZaxisSubelementDefaults();
+ setCivicLocationSubelementDefaults();
+ setMapImageSubelementDefaults();
+ }
+ }
+
+ private ResponderLocation(Parcel in) {
+ // Object Validation
+ mIsValid = in.readByte() != 0;
+ mIsLciValid = in.readByte() != 0;
+ mIsZValid = in.readByte() != 0;
+ mIsUsageValid = in.readByte() != 0;
+ mIsBssidListValid = in.readByte() != 0;
+ mIsLocationCivicValid = in.readByte() != 0;
+ mIsMapImageValid = in.readByte() != 0;
+
+ // LCI Subelement LCI state
+ mLatitudeUncertainty = in.readDouble();
+ mLatitude = in.readDouble();
+ mLongitudeUncertainty = in.readDouble();
+ mLongitude = in.readDouble();
+ mAltitudeType = in.readInt();
+ mAltitudeUncertainty = in.readDouble();
+ mAltitude = in.readDouble();
+ mDatum = in.readInt();
+ mLciRegisteredLocationAgreement = in.readByte() != 0;
+ mLciRegisteredLocationDse = in.readByte() != 0;
+ mLciDependentStation = in.readByte() != 0;
+ mLciVersion = in.readInt();
+
+ // LCI Subelement Z state
+ mExpectedToMove = in.readInt();
+ mFloorNumber = in.readDouble();
+ mHeightAboveFloorMeters = in.readDouble();
+ mHeightAboveFloorUncertaintyMeters = in.readDouble();
+
+ // LCI Usage Rights
+ mUsageRetransmit = in.readByte() != 0;
+ mUsageRetentionExpires = in.readByte() != 0;
+ mUsageExtraInfoOnAssociation = in.readByte() != 0;
+
+ // LCI Subelement BSSID List
+ mBssidList = in.readArrayList(MacAddress.class.getClassLoader());
+
+ // LCR Subelement Location Civic
+ mCivicLocationCountryCode = in.readString();
+ mCivicLocationString = in.readString();
+ mCivicLocation = in.readParcelable(this.getClass().getClassLoader());
+
+ // LCR Subelement Map Image
+ mMapImageType = in.readInt();
+ String urlString = in.readString();
+ if (TextUtils.isEmpty(urlString)) {
+ mMapImageUri = null;
+ } else {
+ mMapImageUri = Uri.parse(urlString);
+ }
+ }
+
+ public static final @android.annotation.NonNull Creator<ResponderLocation> CREATOR = new Creator<ResponderLocation>() {
+ @Override
+ public ResponderLocation createFromParcel(Parcel in) {
+ return new ResponderLocation(in);
+ }
+
+ @Override
+ public ResponderLocation[] newArray(int size) {
+ return new ResponderLocation[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ // Object
+ parcel.writeByte((byte) (mIsValid ? 1 : 0));
+ parcel.writeByte((byte) (mIsLciValid ? 1 : 0));
+ parcel.writeByte((byte) (mIsZValid ? 1 : 0));
+ parcel.writeByte((byte) (mIsUsageValid ? 1 : 0));
+ parcel.writeByte((byte) (mIsBssidListValid ? 1 : 0));
+ parcel.writeByte((byte) (mIsLocationCivicValid ? 1 : 0));
+ parcel.writeByte((byte) (mIsMapImageValid ? 1 : 0));
+
+ // LCI Subelement LCI state
+ parcel.writeDouble(mLatitudeUncertainty);
+ parcel.writeDouble(mLatitude);
+ parcel.writeDouble(mLongitudeUncertainty);
+ parcel.writeDouble(mLongitude);
+ parcel.writeInt(mAltitudeType);
+ parcel.writeDouble(mAltitudeUncertainty);
+ parcel.writeDouble(mAltitude);
+ parcel.writeInt(mDatum);
+ parcel.writeByte((byte) (mLciRegisteredLocationAgreement ? 1 : 0));
+ parcel.writeByte((byte) (mLciRegisteredLocationDse ? 1 : 0));
+ parcel.writeByte((byte) (mLciDependentStation ? 1 : 0));
+ parcel.writeInt(mLciVersion);
+
+ // LCI Subelement Z state
+ parcel.writeInt(mExpectedToMove);
+ parcel.writeDouble(mFloorNumber);
+ parcel.writeDouble(mHeightAboveFloorMeters);
+ parcel.writeDouble(mHeightAboveFloorUncertaintyMeters);
+
+ // LCI Usage Rights
+ parcel.writeByte((byte) (mUsageRetransmit ? 1 : 0));
+ parcel.writeByte((byte) (mUsageRetentionExpires ? 1 : 0));
+ parcel.writeByte((byte) (mUsageExtraInfoOnAssociation ? 1 : 0));
+
+ // LCI Subelement BSSID List
+ parcel.writeList(mBssidList);
+
+ // LCR Subelement Location Civic
+ parcel.writeString(mCivicLocationCountryCode);
+ parcel.writeString(mCivicLocationString);
+ parcel.writeParcelable(mCivicLocation, flags);
+
+ // LCR Subelement Map Image
+ parcel.writeInt(mMapImageType);
+ if (mMapImageUri != null) {
+ parcel.writeString(mMapImageUri.toString());
+ } else {
+ parcel.writeString("");
+ }
+ }
+
+ /**
+ * Test if the Information Element (IE) is in the correct format, and then parse its Subelements
+ * based on their type, and setting state in this object when present.
+ *
+ * @return a boolean indicating the success of the parsing function
+ */
+ private boolean parseInformationElementBuffer(
+ int ieType, byte[] buffer, byte[] expectedLeadBytes) {
+ int bufferPtr = 0;
+ int bufferLength = buffer.length;
+
+ // Ensure the buffer size is within expected limits
+ if (bufferLength < MIN_BUFFER_SIZE || bufferLength > MAX_BUFFER_SIZE) {
+ return false;
+ }
+
+ // Ensure the IE contains the correct leading bytes
+ byte[] leadBufferBytes = Arrays.copyOfRange(buffer, bufferPtr, expectedLeadBytes.length);
+ if (!Arrays.equals(leadBufferBytes, expectedLeadBytes)) {
+ return false;
+ }
+
+ // Iterate through the sub-elements contained in the Information Element (IE)
+ bufferPtr += expectedLeadBytes.length;
+ // Loop over the buffer ensuring there are 2-bytes available for each new subelement tested.
+ while (bufferPtr + 1 < bufferLength) {
+ byte subelement = buffer[bufferPtr++];
+ int subelementLength = buffer[bufferPtr++];
+ // Check there is enough data for the next subelement
+ if ((bufferPtr + subelementLength) > bufferLength || subelementLength <= 0) {
+ return false;
+ }
+
+ byte[] subelementData =
+ Arrays.copyOfRange(buffer, bufferPtr, bufferPtr + subelementLength);
+
+ if (ieType == MEASUREMENT_TYPE_LCI) {
+ switch (subelement) {
+ case SUBELEMENT_LCI:
+ mIsLciValid = parseSubelementLci(subelementData);
+ if (!mIsLciValid || mLciVersion != LCI_VERSION_1) {
+ setLciSubelementDefaults();
+ }
+ break;
+ case SUBELEMENT_Z:
+ mIsZValid = parseSubelementZ(subelementData);
+ if (!mIsZValid) {
+ setZaxisSubelementDefaults();
+ }
+ break;
+ case SUBELEMENT_USAGE:
+ mIsUsageValid = parseSubelementUsage(subelementData);
+ // Note: if the Usage Subelement is not valid, don't reset the state, as
+ // it is now indicating the whole ResponderLocation is invalid.
+ break;
+ case SUBELEMENT_BSSID_LIST:
+ mIsBssidListValid = parseSubelementBssidList(subelementData);
+ if (!mIsBssidListValid) {
+ setBssidListSubelementDefaults();
+ }
+ break;
+ default:
+ break; // skip over unused or vendor specific subelements
+ }
+ } else if (ieType == MEASUREMENT_TYPE_LCR) {
+ switch (subelement) {
+ case SUBELEMENT_LOCATION_CIVIC:
+ mIsLocationCivicValid = parseSubelementLocationCivic(subelementData);
+ if (!mIsLocationCivicValid) {
+ setCivicLocationSubelementDefaults();
+ }
+ break;
+ case SUBELEMENT_MAP_IMAGE:
+ mIsMapImageValid = parseSubelementMapImage(subelementData);
+ if (!mIsMapImageValid) {
+ setMapImageSubelementDefaults();
+ }
+ break;
+ default:
+ break; // skip over unused or other vendor specific subelements
+ }
+ }
+
+ bufferPtr += subelementLength;
+ }
+ return true;
+ }
+
+ /**
+ * Parse the LCI Sub-Element in the LCI Information Element (IE).
+ *
+ * @param buffer a buffer containing the subelement
+ * @return boolean true indicates success
+ */
+ private boolean parseSubelementLci(byte[] buffer) {
+ if (buffer.length > SUBELEMENT_LCI_LENGTH) {
+ return false;
+ }
+ swapEndianByteByByte(buffer);
+ long[] subelementLciFields = getFieldData(buffer, SUBELEMENT_LCI_BIT_FIELD_LENGTHS);
+ if (subelementLciFields == null) {
+ return false;
+ }
+ // Set member state based on parsed buffer data
+ mLatitudeUncertainty = decodeLciLatLngUncertainty(
+ subelementLciFields[SUBELEMENT_LCI_LAT_UNCERTAINTY_INDEX]);
+ mLatitude = decodeLciLatLng(subelementLciFields, SUBELEMENT_LCI_BIT_FIELD_LENGTHS,
+ SUBELEMENT_LCI_LAT_INDEX, LAT_ABS_LIMIT);
+ mLongitudeUncertainty = decodeLciLatLngUncertainty(
+ subelementLciFields[SUBELEMENT_LCI_LNG_UNCERTAINTY_INDEX]);
+ mLongitude =
+ decodeLciLatLng(subelementLciFields, SUBELEMENT_LCI_BIT_FIELD_LENGTHS,
+ SUBELEMENT_LCI_LNG_INDEX, LNG_ABS_LIMIT);
+ mAltitudeType = (int) subelementLciFields[SUBELEMENT_LCI_ALT_TYPE_INDEX] & BYTE_MASK;
+ mAltitudeUncertainty =
+ decodeLciAltUncertainty(subelementLciFields[SUBELEMENT_LCI_ALT_UNCERTAINTY_INDEX]);
+ mAltitude =
+ Math.scalb(subelementLciFields[SUBELEMENT_LCI_ALT_INDEX], -ALTITUDE_FRACTION_BITS);
+ mDatum = (int) subelementLciFields[SUBELEMENT_LCI_DATUM_INDEX] & BYTE_MASK;
+ mLciRegisteredLocationAgreement =
+ (subelementLciFields[SUBELEMENT_LCI_REGLOC_AGREEMENT_INDEX] == 1);
+ mLciRegisteredLocationDse =
+ (subelementLciFields[SUBELEMENT_LCI_REGLOC_DSE_INDEX] == 1);
+ mLciDependentStation =
+ (subelementLciFields[SUBELEMENT_LCI_DEPENDENT_STA_INDEX] == 1);
+ mLciVersion = (int) subelementLciFields[SUBELEMENT_LCI_VERSION_INDEX];
+ return true;
+ }
+
+ /**
+ * Decode the floating point value of an encoded lat or lng in the LCI subelement field.
+ *
+ * @param fields the array of field data represented as longs
+ * @param bitFieldSizes the lengths of each field
+ * @param offset the offset of the field being decoded
+ * @param limit the maximum absolute value (note: different for lat vs lng)
+ * @return the floating point value of the lat or lng
+ */
+ private double decodeLciLatLng(long[] fields, int[] bitFieldSizes, int offset, double limit) {
+ double angle;
+ if ((fields[offset] & (long) Math.pow(2, bitFieldSizes[offset] - 1)) != 0) {
+ // Negative 2's complement value
+ // Note: The Math.pow(...) method cannot return a NaN value because the bitFieldSize
+ // for Lat or Lng is limited to exactly 34 bits.
+ angle = Math.scalb(fields[offset] - Math.pow(2, bitFieldSizes[offset]),
+ -LATLNG_FRACTION_BITS);
+ } else {
+ // Positive 2's complement value
+ angle = Math.scalb(fields[offset], -LATLNG_FRACTION_BITS);
+ }
+ if (angle > limit) {
+ angle = limit;
+ } else if (angle < -limit) {
+ angle = -limit;
+ }
+ return angle;
+ }
+
+ /**
+ * Coverts an encoded Lat/Lng uncertainty into a number of degrees.
+ *
+ * @param encodedValue the encoded uncertainty
+ * @return the value in degrees
+ */
+ private double decodeLciLatLngUncertainty(long encodedValue) {
+ return Math.pow(2, LATLNG_UNCERTAINTY_BASE - encodedValue);
+ }
+
+ /**
+ * Converts an encoded Alt uncertainty into a number of degrees.
+ *
+ * @param encodedValue the encoded uncertainty
+ * @return the value in degrees
+ */
+ private double decodeLciAltUncertainty(long encodedValue) {
+ return Math.pow(2, ALTITUDE_UNCERTAINTY_BASE - encodedValue);
+ }
+
+ /**
+ * Parse the Z subelement of the LCI IE.
+ *
+ * @param buffer a buffer containing the subelement
+ * @return boolean true indicates success
+ */
+ private boolean parseSubelementZ(byte[] buffer) {
+ if (buffer.length != SUBELEMENT_Z_LENGTH) {
+ return false;
+ }
+ swapEndianByteByByte(buffer);
+ long[] subelementZFields = getFieldData(buffer, SUBELEMENT_Z_BIT_FIELD_LENGTHS);
+ if (subelementZFields == null) {
+ return false;
+ }
+
+ mExpectedToMove =
+ (int) subelementZFields[SUBELEMENT_Z_LAT_EXPECTED_TO_MOVE_INDEX] & BYTE_MASK;
+ mFloorNumber = decodeZUnsignedToSignedValue(subelementZFields,
+ SUBELEMENT_Z_BIT_FIELD_LENGTHS, SUBELEMENT_Z_FLOOR_NUMBER_INDEX,
+ Z_FLOOR_NUMBER_FRACTION_BITS);
+
+ mHeightAboveFloorMeters = decodeZUnsignedToSignedValue(subelementZFields,
+ SUBELEMENT_Z_BIT_FIELD_LENGTHS, SUBELEMENT_Z_HEIGHT_ABOVE_FLOOR_INDEX,
+ Z_FLOOR_HEIGHT_FRACTION_BITS);
+
+ long zHeightUncertainty =
+ subelementZFields[SUBELEMENT_Z_HEIGHT_ABOVE_FLOOR_UNCERTAINTY_INDEX];
+ if (zHeightUncertainty > 0 && zHeightUncertainty < Z_MAX_HEIGHT_UNCERTAINTY_FACTOR) {
+ mHeightAboveFloorUncertaintyMeters =
+ Math.pow(2, Z_FLOOR_HEIGHT_FRACTION_BITS - zHeightUncertainty - 1);
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Decode a two's complement encoded value, to a signed double based on the field length.
+ *
+ * @param fieldValues the array of field values reprented as longs
+ * @param fieldLengths the array of field lengths
+ * @param index the index of the field being decoded
+ * @param fraction the number of fractional bits in the value
+ * @return the signed value represented as a double
+ */
+ private double decodeZUnsignedToSignedValue(long[] fieldValues, int[] fieldLengths, int index,
+ int fraction) {
+ int value = (int) fieldValues[index];
+ int maxPositiveValue = (int) Math.pow(2, fieldLengths[index] - 1) - 1;
+ if (value > maxPositiveValue) {
+ value -= Math.pow(2, fieldLengths[index]);
+ }
+ return Math.scalb(value, -fraction);
+ }
+
+ /**
+ * Parse Subelement Usage Rights
+ */
+ private boolean parseSubelementUsage(byte[] buffer) {
+ if (buffer.length != SUBELEMENT_USAGE_LENGTH1
+ && buffer.length != SUBELEMENT_USAGE_LENGTH3) {
+ return false;
+ }
+ mUsageRetransmit =
+ (buffer[SUBELEMENT_USAGE_PARAMS_INDEX] & SUBELEMENT_USAGE_MASK_RETRANSMIT) != 0;
+ mUsageRetentionExpires =
+ (buffer[SUBELEMENT_USAGE_PARAMS_INDEX] & SUBELEMENT_USAGE_MASK_RETENTION_EXPIRES)
+ != 0;
+ mUsageExtraInfoOnAssociation =
+ (buffer[SUBELEMENT_USAGE_PARAMS_INDEX] & SUBELEMENT_USAGE_MASK_STA_LOCATION_POLICY)
+ != 0;
+ // Note: the Retransmit flag must be true, and RetentionExpires, false for the
+ // ResponderLocation object to be usable by public applications.
+ return (mUsageRetransmit && !mUsageRetentionExpires);
+ }
+
+ /**
+ * Parse the BSSID List Subelement of the LCI IE.
+ *
+ * @param buffer a buffer containing the subelement
+ * @return boolean true indicates success
+ */
+ private boolean parseSubelementBssidList(byte[] buffer) {
+ if (buffer.length < SUBELEMENT_BSSID_LIST_MIN_BUFFER_LENGTH) {
+ return false;
+ }
+ if ((buffer.length - 1) % BYTES_IN_A_BSSID != 0) {
+ return false;
+ }
+
+ int maxBssidIndicator = (int) buffer[SUBELEMENT_BSSID_MAX_INDICATOR_INDEX] & BYTE_MASK;
+ int bssidListLength = (buffer.length - 1) / BYTES_IN_A_BSSID;
+ // Check the max number of BSSIDs agrees with the list length.
+ if (maxBssidIndicator != bssidListLength) {
+ return false;
+ }
+
+ int bssidOffset = SUBELEMENT_BSSID_LIST_INDEX;
+ for (int i = 0; i < bssidListLength; i++) {
+ byte[] bssid = Arrays.copyOfRange(buffer, bssidOffset, bssidOffset + BYTES_IN_A_BSSID);
+ MacAddress macAddress = MacAddress.fromBytes(bssid);
+ mBssidList.add(macAddress);
+ bssidOffset += BYTES_IN_A_BSSID;
+ }
+ return true;
+ }
+
+ /**
+ * Parse the Location Civic subelement in the LCR IE.
+ *
+ * @param buffer a buffer containing the subelement
+ * @return boolean true indicates success
+ */
+ private boolean parseSubelementLocationCivic(byte[] buffer) {
+ if (buffer.length < SUBELEMENT_LOCATION_CIVIC_MIN_LENGTH
+ || buffer.length > SUBELEMENT_LOCATION_CIVIC_MAX_LENGTH) {
+ return false;
+ }
+ mCivicLocationCountryCode =
+ new String(Arrays.copyOfRange(buffer, CIVIC_COUNTRY_CODE_INDEX,
+ CIVIC_TLV_LIST_INDEX)).toUpperCase();
+ CivicLocation civicLocation =
+ new CivicLocation(
+ Arrays.copyOfRange(buffer, CIVIC_TLV_LIST_INDEX, buffer.length),
+ mCivicLocationCountryCode);
+ if (!civicLocation.isValid()) {
+ return false;
+ }
+ this.mCivicLocation = civicLocation;
+ mCivicLocationString = civicLocation.toString();
+ return true;
+ }
+
+ /**
+ * Parse the Map Image subelement in the LCR IE.
+ *
+ * @param buffer a buffer containing the subelement
+ * @return boolean true indicates success
+ */
+ private boolean parseSubelementMapImage(byte[] buffer) {
+ if (buffer.length > SUBELEMENT_MAP_IMAGE_URL_MAX_LENGTH) {
+ return false;
+ }
+ int mapImageType = buffer[SUBELEMENT_IMAGE_MAP_TYPE_INDEX];
+ int supportedTypesMax = SUPPORTED_IMAGE_FILE_EXTENSIONS.length - 1;
+ if (mapImageType < MAP_TYPE_URL_DEFINED || mapImageType > supportedTypesMax) {
+ return false;
+ }
+ this.mMapImageType = mapImageType;
+ byte[] urlBytes = Arrays.copyOfRange(buffer, 1, buffer.length);
+ mMapImageUri = Uri.parse(new String(urlBytes, StandardCharsets.UTF_8));
+ return true;
+ }
+
+ /**
+ * Convert an image type code to a Mime type
+ *
+ * @param imageTypeCode encoded as an integer
+ * @return the mime type of the image file
+ */
+ private String imageTypeToMime(int imageTypeCode, String imageUrl) {
+ int supportedExtensionsMax = SUPPORTED_IMAGE_FILE_EXTENSIONS.length - 1;
+ if ((imageTypeCode == 0 && imageUrl == null) || imageTypeCode > supportedExtensionsMax) {
+ return null;
+ }
+ MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
+ if (imageTypeCode == 0) {
+ return mimeTypeMap.getMimeTypeFromExtension(
+ MimeTypeMap.getFileExtensionFromUrl(imageUrl));
+ } else {
+ return mimeTypeMap.getMimeTypeFromExtension(
+ SUPPORTED_IMAGE_FILE_EXTENSIONS[imageTypeCode]);
+ }
+ }
+
+ /**
+ * Converts a byte array containing fields of variable size, into an array of longs where the
+ * field boundaries are defined in a constant int array passed as an argument.
+ *
+ * @param buffer the byte array containing all the fields
+ * @param bitFieldSizes the int array defining the size of each field
+ */
+ private long[] getFieldData(byte[] buffer, int[] bitFieldSizes) {
+ int bufferLengthBits = buffer.length * Byte.SIZE;
+ int sumBitFieldSizes = 0;
+ for (int i : bitFieldSizes) {
+ if (i > Long.SIZE) {
+ return null;
+ }
+ sumBitFieldSizes += i;
+ }
+ if (bufferLengthBits != sumBitFieldSizes) {
+ return null;
+ }
+ long[] fieldData = new long[bitFieldSizes.length];
+ int bufferBitPos = 0;
+ for (int fieldIndex = 0; fieldIndex < bitFieldSizes.length; fieldIndex++) {
+ int bitFieldSize = bitFieldSizes[fieldIndex];
+ long field = 0;
+ for (int n = 0; n < bitFieldSize; n++) {
+ field |= ((long) getBitAtBitOffsetInByteArray(buffer, bufferBitPos + n) << n);
+ }
+ fieldData[fieldIndex] = field;
+ bufferBitPos += bitFieldSize;
+ }
+ return fieldData;
+ }
+
+ /**
+ * Retrieves state of a bit at the bit-offset in a byte array, where the offset represents the
+ * bytes in contiguous data with each value in big endian order.
+ *
+ * @param buffer the data buffer of bytes containing all the fields
+ * @param bufferBitOffset the bit offset into the entire buffer
+ * @return a zero or one value, representing the state of that bit.
+ */
+ private int getBitAtBitOffsetInByteArray(byte[] buffer, int bufferBitOffset) {
+ int bufferIndex = bufferBitOffset / Byte.SIZE; // The byte index that contains the bit
+ int bitOffsetInByte = bufferBitOffset % Byte.SIZE; // The bit offset within that byte
+ int result = (buffer[bufferIndex] & (MSB_IN_BYTE >> bitOffsetInByte)) == 0 ? 0 : 1;
+ return result;
+ }
+
+ /**
+ * Reverses the order of the bits in each byte of a byte array.
+ *
+ * @param buffer the array containing each byte that will be reversed
+ */
+ private void swapEndianByteByByte(byte[] buffer) {
+ for (int n = 0; n < buffer.length; n++) {
+ byte currentByte = buffer[n];
+ byte reversedByte = 0; // Cleared value
+ byte bitSelectorMask = LSB_IN_BYTE;
+ for (int i = 0; i < Byte.SIZE; i++) {
+ reversedByte = (byte) (reversedByte << 1);
+ if ((currentByte & bitSelectorMask) != 0) {
+ reversedByte = (byte) (reversedByte | LSB_IN_BYTE);
+ }
+ bitSelectorMask = (byte) (bitSelectorMask << 1);
+ }
+ buffer[n] = reversedByte;
+ }
+ }
+
+ /**
+ * Sets the LCI subelement fields to the default undefined values.
+ */
+ private void setLciSubelementDefaults() {
+ mIsLciValid = false;
+ mLatitudeUncertainty = UNCERTAINTY_UNDEFINED;
+ mLatitude = 0;
+ mLongitudeUncertainty = UNCERTAINTY_UNDEFINED;
+ mLongitude = 0;
+ mAltitudeType = ALTITUDE_UNDEFINED;
+ mAltitudeUncertainty = UNCERTAINTY_UNDEFINED;
+ mAltitude = 0;
+ mDatum = DATUM_UNDEFINED;
+ mLciRegisteredLocationAgreement = false;
+ mLciRegisteredLocationDse = false;
+ mLciDependentStation = false;
+ mLciVersion = 0;
+ }
+
+ /**
+ * Sets the Z subelement fields to the default values when undefined.
+ */
+ private void setZaxisSubelementDefaults() {
+ mIsZValid = false;
+ mExpectedToMove = 0;
+ mFloorNumber = 0;
+ mHeightAboveFloorMeters = 0;
+ mHeightAboveFloorUncertaintyMeters = 0;
+ }
+
+ /**
+ * Sets the Usage Policy subelement fields to the default undefined values.
+ */
+ private void setUsageSubelementDefaults() {
+ mUsageRetransmit = true;
+ mUsageRetentionExpires = false;
+ mUsageExtraInfoOnAssociation = false;
+ }
+
+ /**
+ * Sets the BSSID List subelement fields to the default values when undefined.
+ */
+ private void setBssidListSubelementDefaults() {
+ mIsBssidListValid = false;
+ mBssidList = new ArrayList<>();
+ }
+
+ /**
+ * Sets the LCR Civic Location subelement field to the default undefined value.
+ *
+ * @hide
+ */
+ public void setCivicLocationSubelementDefaults() {
+ mIsLocationCivicValid = false;
+ mCivicLocationCountryCode = "";
+ mCivicLocationString = "";
+ mCivicLocation = null;
+ }
+
+ /**
+ * Sets the LCR Map Image subelement field to the default values when undefined.
+ */
+ private void setMapImageSubelementDefaults() {
+ mIsMapImageValid = false;
+ mMapImageType = MAP_TYPE_URL_DEFINED;
+ mMapImageUri = null;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ ResponderLocation other = (ResponderLocation) obj;
+ return mIsValid == other.mIsValid
+ && mIsLciValid == other.mIsLciValid
+ && mIsZValid == other.mIsZValid
+ && mIsUsageValid == other.mIsUsageValid
+ && mIsBssidListValid == other.mIsBssidListValid
+ && mIsLocationCivicValid == other.mIsLocationCivicValid
+ && mIsMapImageValid == other.mIsMapImageValid
+ && mLatitudeUncertainty == other.mLatitudeUncertainty
+ && mLatitude == other.mLatitude
+ && mLongitudeUncertainty == other.mLongitudeUncertainty
+ && mLongitude == other.mLongitude
+ && mAltitudeType == other.mAltitudeType
+ && mAltitudeUncertainty == other.mAltitudeUncertainty
+ && mAltitude == other.mAltitude
+ && mDatum == other.mDatum
+ && mLciRegisteredLocationAgreement == other.mLciRegisteredLocationAgreement
+ && mLciRegisteredLocationDse == other.mLciRegisteredLocationDse
+ && mLciDependentStation == other.mLciDependentStation
+ && mLciVersion == other.mLciVersion
+ && mExpectedToMove == other.mExpectedToMove
+ && mFloorNumber == other.mFloorNumber
+ && mHeightAboveFloorMeters == other.mHeightAboveFloorMeters
+ && mHeightAboveFloorUncertaintyMeters
+ == other.mHeightAboveFloorUncertaintyMeters
+ && mUsageRetransmit == other.mUsageRetransmit
+ && mUsageRetentionExpires == other.mUsageRetentionExpires
+ && mUsageExtraInfoOnAssociation == other.mUsageExtraInfoOnAssociation
+ && mBssidList.equals(other.mBssidList)
+ && mCivicLocationCountryCode.equals(other.mCivicLocationCountryCode)
+ && mCivicLocationString.equals(other.mCivicLocationString)
+ && Objects.equals(mCivicLocation, other.mCivicLocation)
+ && mMapImageType == other.mMapImageType
+ && Objects.equals(mMapImageUri, other.mMapImageUri);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mIsValid, mIsLciValid, mIsZValid, mIsUsageValid, mIsBssidListValid,
+ mIsLocationCivicValid, mIsMapImageValid, mLatitudeUncertainty, mLatitude,
+ mLongitudeUncertainty, mLongitude, mAltitudeType, mAltitudeUncertainty, mAltitude,
+ mDatum, mLciRegisteredLocationAgreement,
+ mLciRegisteredLocationDse, mLciDependentStation, mLciVersion,
+ mExpectedToMove, mFloorNumber, mHeightAboveFloorMeters,
+ mHeightAboveFloorUncertaintyMeters, mUsageRetransmit, mUsageRetentionExpires,
+ mUsageExtraInfoOnAssociation, mBssidList, mCivicLocationCountryCode,
+ mCivicLocationString, mCivicLocation, mMapImageType, mMapImageUri);
+ }
+
+ /**
+ * @return true if the ResponderLocation object is valid and contains useful information
+ * relevant to the location of the Responder. If this is ever false, this object will not be
+ * available to developers, and have a null value.
+ *
+ * @hide
+ */
+ public boolean isValid() {
+ return mIsValid;
+ }
+
+ /**
+ * @return true if the LCI subelement (containing Latitude, Longitude and Altitude) is valid.
+ *
+ * <p> This method tells us if the responder has provided its Location Configuration
+ * Information (LCI) directly, and is useful when an external database of responder locations
+ * is not available</p>
+ *
+ * <p>If isLciSubelementValid() returns true, all the LCI values provided by the corresponding
+ * getter methods will have been set as described by the responder, or else if false, they
+ * should not be used and will throw an IllegalStateException.</p>
+ */
+ public boolean isLciSubelementValid() {
+ return mIsLciValid;
+ }
+
+ /**
+ * @return the latitude uncertainty in degrees.
+ * <p>
+ * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception.
+ * </p>
+ * <p> An unknown uncertainty is indicated by 0.</p>
+ */
+ public double getLatitudeUncertainty() {
+ if (!mIsLciValid) {
+ throw new IllegalStateException(
+ "getLatitudeUncertainty(): invoked on an invalid result: mIsLciValid = false.");
+ }
+ return mLatitudeUncertainty;
+ }
+
+ /**
+ * @return the latitude in degrees
+ * <p>
+ * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception.
+ */
+ public double getLatitude() {
+ if (!mIsLciValid) {
+ throw new IllegalStateException(
+ "getLatitude(): invoked on an invalid result: mIsLciValid = false.");
+ }
+ return mLatitude;
+ }
+
+ /**
+ * @return the Longitude uncertainty in degrees.
+ * <p>
+ * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception.
+ * </p>
+ * <p> An unknown uncertainty is indicated by 0.</p>
+ */
+ public double getLongitudeUncertainty() {
+ if (!mIsLciValid) {
+ throw new IllegalStateException(
+ "getLongitudeUncertainty(): invoked on an invalid result: mIsLciValid = false.");
+ }
+ return mLongitudeUncertainty;
+ }
+
+ /**
+ * @return the Longitude in degrees..
+ * <p>
+ * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception.
+ */
+ public double getLongitude() {
+ if (!mIsLciValid) {
+ throw new IllegalStateException(
+ "getLatitudeUncertainty(): invoked on an invalid result: mIsLciValid = false.");
+ }
+ return mLongitude;
+ }
+
+ /**
+ * @return the Altitude type.
+ * <p>
+ * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception.
+ */
+ @AltitudeType
+ public int getAltitudeType() {
+ if (!mIsLciValid) {
+ throw new IllegalStateException(
+ "getLatitudeUncertainty(): invoked on an invalid result: mIsLciValid = false.");
+ }
+ return mAltitudeType;
+ }
+
+ /**
+ * @return the Altitude uncertainty in meters.
+ * <p>
+ * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception.
+ * </p>
+ * <p>An unknown uncertainty is indicated by 0.</p>
+ */
+ public double getAltitudeUncertainty() {
+ if (!mIsLciValid) {
+ throw new IllegalStateException(
+ "getLatitudeUncertainty(): invoked on an invalid result: mIsLciValid = false.");
+ }
+ return mAltitudeUncertainty;
+ }
+
+ /**
+ * @return the Altitude in units defined by the altitude type.
+ * <p>
+ * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception.
+ */
+ public double getAltitude() {
+ if (!mIsLciValid) {
+ throw new IllegalStateException(
+ "getAltitude(): invoked on an invalid result: mIsLciValid = false.");
+ }
+ return mAltitude;
+ }
+
+ /**
+ * @return the Datum used for the LCI positioning information.
+ * <p>
+ * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception.
+ */
+ @DatumType
+ public int getDatum() {
+ if (!mIsLciValid) {
+ throw new IllegalStateException(
+ "getDatum(): invoked on an invalid result: mIsLciValid = false.");
+ }
+ return mDatum;
+ }
+
+ /**
+ * @return true if the station is operating within a national policy area or an international
+ * agreement area near a national border, otherwise false
+ * (see 802.11REVmc Section 11.12.3 - Registered STA Operation).
+ * <p>
+ * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception.
+ */
+ public boolean getRegisteredLocationAgreementIndication() {
+ if (!mIsLciValid) {
+ throw new IllegalStateException(
+ "getRegisteredLocationAgreementIndication(): "
+ + "invoked on an invalid result: mIsLciValid = false.");
+ }
+ return mLciRegisteredLocationAgreement;
+ }
+
+ /**
+ * @return true indicating this is an enabling station, enabling the operation of nearby STAs
+ * with Dynamic Station Enablement (DSE), otherwise false.
+ * (see 802.11REVmc Section 11.12.3 - Registered STA Operation).
+ * <p>
+ * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception.
+ *
+ * @hide
+ */
+ public boolean getRegisteredLocationDseIndication() {
+ if (!mIsLciValid) {
+ throw new IllegalStateException(
+ "getRegisteredLocationDseIndication(): "
+ + "invoked on an invalid result: mIsLciValid = false.");
+ }
+ return mLciRegisteredLocationDse;
+ }
+
+ /**
+ * @return true indicating this is a dependent station that is operating with the enablement of
+ * an enabling station whose LCI is being reported, otherwise false.
+ * (see 802.11REVmc Section 11.12.3 - Registered STA Operation).
+ * <p>
+ * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception.
+ *
+ * @hide
+ */
+ public boolean getDependentStationIndication() {
+ if (!mIsLciValid) {
+ throw new IllegalStateException(
+ "getDependentStationIndication(): "
+ + "invoked on an invalid result: mIsLciValid = false.");
+ }
+ return mLciDependentStation;
+ }
+
+ /**
+ * @return a value greater or equal to 1, indicating the current version number
+ * of the LCI protocol.
+ * <p>
+ * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception.
+ */
+ public int getLciVersion() {
+ if (!mIsLciValid) {
+ throw new IllegalStateException(
+ "getLciVersion(): "
+ + "invoked on an invalid result: mIsLciValid = false.");
+ }
+ return mLciVersion;
+ }
+
+ /**
+ * @return the LCI location represented as a {@link Location} object (best effort).
+ * <p>
+ * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception.
+ */
+ @NonNull
+ public Location toLocation() {
+ if (!mIsLciValid) {
+ throw new IllegalStateException(
+ "toLocation(): "
+ + "invoked on an invalid result: mIsLciValid = false.");
+ }
+ Location location = new Location(LOCATION_PROVIDER);
+ location.setLatitude(mLatitude);
+ location.setLongitude(mLongitude);
+ location.setAccuracy((float) (mLatitudeUncertainty + mLongitudeUncertainty) / 2);
+ location.setAltitude(mAltitude);
+ location.setVerticalAccuracyMeters((float) mAltitudeUncertainty);
+ location.setTime(System.currentTimeMillis());
+ return location;
+ }
+
+ /**
+ * @return if the Z subelement (containing mobility, Floor, Height above floor) is valid.
+ */
+ public boolean isZaxisSubelementValid() {
+ return mIsZValid;
+ }
+
+ /**
+ * @return an integer representing the mobility of the responder.
+ * <p>
+ * Only valid if {@link #isZaxisSubelementValid()} returns true, or will throw an exception.
+ */
+ @ExpectedToMoveType
+ public int getExpectedToMove() {
+ if (!mIsZValid) {
+ throw new IllegalStateException(
+ "getExpectedToMove(): invoked on an invalid result: mIsZValid = false.");
+ }
+ return mExpectedToMove;
+ }
+
+ /**
+ * @return the Z sub element Floor Number.
+ * <p>
+ * Only valid if {@link #isZaxisSubelementValid()} returns true, or will throw an exception.
+ * </p>
+ * <p>Note: this number can be positive or negative, with value increments of +/- 1/16 of a
+ * floor.</p>.
+ */
+ public double getFloorNumber() {
+ if (!mIsZValid) {
+ throw new IllegalStateException(
+ "getFloorNumber(): invoked on an invalid result: mIsZValid = false)");
+ }
+ return mFloorNumber;
+ }
+
+ /**
+ * @return the Z subelement Height above the floor in meters.
+ * <p>
+ * Only valid if {@link #isZaxisSubelementValid()} returns true, or will throw an exception.
+ * </p>
+ * <p>This value can be positive or negative. </p>
+ */
+ public double getHeightAboveFloorMeters() {
+ if (!mIsZValid) {
+ throw new IllegalStateException(
+ "getHeightAboveFloorMeters(): invoked on an invalid result: mIsZValid = false)");
+ }
+ return mHeightAboveFloorMeters;
+ }
+
+ /**
+ * @return the Z subelement Height above the floor uncertainty in meters.
+ * <p>
+ * Only valid if {@link #isZaxisSubelementValid()} returns true, or will throw an exception.
+ * </p>
+ * <p>An unknown uncertainty is indicated by 0.</p>
+ */
+ public double getHeightAboveFloorUncertaintyMeters() {
+ if (!mIsZValid) {
+ throw new IllegalStateException(
+ "getHeightAboveFloorUncertaintyMeters():"
+ + "invoked on an invalid result: mIsZValid = false)");
+ }
+ return mHeightAboveFloorUncertaintyMeters;
+ }
+
+ /**
+ * @return true if the location information received from the responder can be
+ * retransmitted to another device, physically separate from the one that received it.
+ *
+ * @hide
+ */
+ public boolean getRetransmitPolicyIndication() {
+ return mUsageRetransmit;
+ }
+
+ /**
+ * @return true if location-data received should expire (and be deleted)
+ * by the time provided in the getRelativeExpirationTimeHours() method.
+ *
+ * @hide
+ */
+ public boolean getRetentionExpiresIndication() {
+ return mUsageRetentionExpires;
+ }
+
+ /**
+ * @return true if there is extra location info available on association.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean getExtraInfoOnAssociationIndication() {
+ return mUsageExtraInfoOnAssociation;
+ }
+
+ /**
+ * @return the Immutable list of colocated BSSIDs at the responder.
+ *
+ * <p> Will return an empty list when there are no bssids listed.
+ */
+ public List<MacAddress> getColocatedBssids() {
+ return Collections.unmodifiableList(mBssidList);
+ }
+
+ /**
+ * @return the civic location represented as an {@link Address} object (best effort).
+ *
+ * <p> Will return a {@code null} when there is no Civic Location defined.
+ */
+ @Nullable
+ public Address toCivicLocationAddress() {
+ if (mCivicLocation != null && mCivicLocation.isValid()) {
+ return mCivicLocation.toAddress();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @return the civic location represented as a {@link SparseArray}
+ * <p>
+ * Valid keys to access the SparseArray can be found in {@code CivicLocationKeys}.
+ * </p>
+ * <p> Will return a {@code null} when there is no Civic Location defined.
+ *
+ */
+ @Nullable
+ public SparseArray toCivicLocationSparseArray() {
+ if (mCivicLocation != null && mCivicLocation.isValid()) {
+ return mCivicLocation.toSparseArray();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @return the civic location two upper-case ASCII character country code defined in ISO 3166.
+ *
+ * <p> Will return a {@code null} when there is no country code defined.
+ *
+ * @hide
+ */
+ @Nullable
+ public String getCivicLocationCountryCode() {
+ return mCivicLocationCountryCode;
+ }
+
+ /**
+ * @return the value of the Civic Location String associated with a key.
+ *
+ * <p> Will return a {@code null} when there is no value associated with the key provided.
+ *
+ * @param key used to find a corresponding value in the Civic Location Tuple list
+ *
+ * @hide
+ */
+ @Nullable
+ public String getCivicLocationElementValue(@CivicLocationKeysType int key) {
+ return mCivicLocation.getCivicElementValue(key);
+ }
+
+ /**
+ * @return the Map Image file Mime type, referred to by getMapImageUrl().
+ */
+ @Nullable
+ public String getMapImageMimeType() {
+ if (mMapImageUri == null) {
+ return null;
+ } else {
+ return imageTypeToMime(mMapImageType, mMapImageUri.toString());
+ }
+ }
+
+ /**
+ * @return a URI referencing a map-file showing the local floor plan.
+ *
+ * <p> Will return a {@code null} when there is no URI defined.
+ */
+ @Nullable
+ public Uri getMapImageUri() {
+ return mMapImageUri;
+ }
+}
diff --git a/android/net/wifi/rtt/WifiRttManager.java b/android/net/wifi/rtt/WifiRttManager.java
new file mode 100644
index 0000000..457e904
--- /dev/null
+++ b/android/net/wifi/rtt/WifiRttManager.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.rtt;
+
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.ACCESS_WIFI_STATE;
+import static android.Manifest.permission.CHANGE_WIFI_STATE;
+import static android.Manifest.permission.LOCATION_HARDWARE;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.WorkSource;
+import android.util.Log;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * This class provides the primary API for measuring distance (range) to other devices using the
+ * IEEE 802.11mc Wi-Fi Round Trip Time (RTT) technology.
+ * <p>
+ * The devices which can be ranged include:
+ * <li>Access Points (APs)
+ * <li>Wi-Fi Aware peers
+ * <p>
+ * Ranging requests are triggered using
+ * {@link #startRanging(RangingRequest, Executor, RangingResultCallback)}. Results (in case of
+ * successful operation) are returned in the {@link RangingResultCallback#onRangingResults(List)}
+ * callback.
+ * <p>
+ * Wi-Fi RTT may not be usable at some points, e.g. when Wi-Fi is disabled. To validate that
+ * the functionality is available use the {@link #isAvailable()} function. To track
+ * changes in RTT usability register for the {@link #ACTION_WIFI_RTT_STATE_CHANGED}
+ * broadcast. Note that this broadcast is not sticky - you should register for it and then
+ * check the above API to avoid a race condition.
+ */
+@SystemService(Context.WIFI_RTT_RANGING_SERVICE)
+public class WifiRttManager {
+ private static final String TAG = "WifiRttManager";
+ private static final boolean VDBG = false;
+
+ private final Context mContext;
+ private final IWifiRttManager mService;
+
+ /**
+ * Broadcast intent action to indicate that the state of Wi-Fi RTT availability has changed.
+ * Use the {@link #isAvailable()} to query the current status.
+ * This broadcast is <b>not</b> sticky, use the {@link #isAvailable()} API after registering
+ * the broadcast to check the current state of Wi-Fi RTT.
+ * <p>Note: The broadcast is only delivered to registered receivers - no manifest registered
+ * components will be launched.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_WIFI_RTT_STATE_CHANGED =
+ "android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED";
+
+ /** @hide */
+ public WifiRttManager(Context context, IWifiRttManager service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Returns the current status of RTT API: whether or not RTT is available. To track
+ * changes in the state of RTT API register for the
+ * {@link #ACTION_WIFI_RTT_STATE_CHANGED} broadcast.
+ * <p>Note: availability of RTT does not mean that the app can use the API. The app's
+ * permissions and platform Location Mode are validated at run-time.
+ *
+ * @return A boolean indicating whether the app can use the RTT API at this time (true) or
+ * not (false).
+ */
+ public boolean isAvailable() {
+ try {
+ return mService.isAvailable();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Initiate a request to range to a set of devices specified in the {@link RangingRequest}.
+ * Results will be returned in the {@link RangingResultCallback} set of callbacks.
+ *
+ * @param request A request specifying a set of devices whose distance measurements are
+ * requested.
+ * @param executor The Executor on which to run the callback.
+ * @param callback A callback for the result of the ranging request.
+ */
+ @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, CHANGE_WIFI_STATE, ACCESS_WIFI_STATE})
+ public void startRanging(@NonNull RangingRequest request,
+ @NonNull @CallbackExecutor Executor executor, @NonNull RangingResultCallback callback) {
+ startRanging(null, request, executor, callback);
+ }
+
+ /**
+ * Initiate a request to range to a set of devices specified in the {@link RangingRequest}.
+ * Results will be returned in the {@link RangingResultCallback} set of callbacks.
+ *
+ * @param workSource A mechanism to specify an alternative work-source for the request.
+ * @param request A request specifying a set of devices whose distance measurements are
+ * requested.
+ * @param executor The Executor on which to run the callback.
+ * @param callback A callback for the result of the ranging request.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {LOCATION_HARDWARE, ACCESS_FINE_LOCATION, CHANGE_WIFI_STATE,
+ ACCESS_WIFI_STATE})
+ public void startRanging(@Nullable WorkSource workSource, @NonNull RangingRequest request,
+ @NonNull @CallbackExecutor Executor executor, @NonNull RangingResultCallback callback) {
+ if (VDBG) {
+ Log.v(TAG, "startRanging: workSource=" + workSource + ", request=" + request
+ + ", callback=" + callback + ", executor=" + executor);
+ }
+
+ if (executor == null) {
+ throw new IllegalArgumentException("Null executor provided");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("Null callback provided");
+ }
+
+ Binder binder = new Binder();
+ try {
+ mService.startRanging(binder, mContext.getOpPackageName(), workSource, request,
+ new IRttCallback.Stub() {
+ @Override
+ public void onRangingFailure(int status) throws RemoteException {
+ clearCallingIdentity();
+ executor.execute(() -> callback.onRangingFailure(status));
+ }
+
+ @Override
+ public void onRangingResults(List<RangingResult> results)
+ throws RemoteException {
+ clearCallingIdentity();
+ executor.execute(() -> callback.onRangingResults(results));
+ }
+ });
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Cancel all ranging requests for the specified work sources. The requests have been requested
+ * using {@link #startRanging(WorkSource, RangingRequest, Executor, RangingResultCallback)}.
+ *
+ * @param workSource The work-sources of the requesters.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {LOCATION_HARDWARE})
+ public void cancelRanging(@Nullable WorkSource workSource) {
+ if (VDBG) {
+ Log.v(TAG, "cancelRanging: workSource=" + workSource);
+ }
+
+ try {
+ mService.cancelRanging(workSource);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/android/net/wimax/WimaxManagerConstants.java b/android/net/wimax/WimaxManagerConstants.java
new file mode 100644
index 0000000..b4aaf5b
--- /dev/null
+++ b/android/net/wimax/WimaxManagerConstants.java
@@ -0,0 +1,104 @@
+package android.net.wimax;
+
+/**
+ * {@hide}
+ */
+public class WimaxManagerConstants
+{
+
+ /**
+ * Used by android.net.wimax.WimaxManager for handling management of
+ * Wimax access.
+ */
+ public static final String WIMAX_SERVICE = "WiMax";
+
+ /**
+ * Broadcast intent action indicating that Wimax has been enabled, disabled,
+ * enabling, disabling, or unknown. One extra provides this state as an int.
+ * Another extra provides the previous state, if available.
+ */
+ public static final String NET_4G_STATE_CHANGED_ACTION =
+ "android.net.fourG.NET_4G_STATE_CHANGED";
+
+ /**
+ * The lookup key for an int that indicates whether Wimax is enabled,
+ * disabled, enabling, disabling, or unknown.
+ */
+ public static final String EXTRA_WIMAX_STATUS = "wimax_status";
+
+ /**
+ * Broadcast intent action indicating that Wimax state has been changed
+ * state could be scanning, connecting, connected, disconnecting, disconnected
+ * initializing, initialized, unknown and ready. One extra provides this state as an int.
+ * Another extra provides the previous state, if available.
+ */
+ public static final String WIMAX_NETWORK_STATE_CHANGED_ACTION =
+ "android.net.fourG.wimax.WIMAX_NETWORK_STATE_CHANGED";
+
+ /**
+ * Broadcast intent action indicating that Wimax signal level has been changed.
+ * Level varies from 0 to 3.
+ */
+ public static final String SIGNAL_LEVEL_CHANGED_ACTION =
+ "android.net.wimax.SIGNAL_LEVEL_CHANGED";
+
+ /**
+ * The lookup key for an int that indicates whether Wimax state is
+ * scanning, connecting, connected, disconnecting, disconnected
+ * initializing, initialized, unknown and ready.
+ */
+ public static final String EXTRA_WIMAX_STATE = "WimaxState";
+ public static final String EXTRA_4G_STATE = "4g_state";
+ public static final String EXTRA_WIMAX_STATE_INT = "WimaxStateInt";
+ /**
+ * The lookup key for an int that indicates whether state of Wimax
+ * is idle.
+ */
+ public static final String EXTRA_WIMAX_STATE_DETAIL = "WimaxStateDetail";
+
+ /**
+ * The lookup key for an int that indicates Wimax signal level.
+ */
+ public static final String EXTRA_NEW_SIGNAL_LEVEL = "newSignalLevel";
+
+ /**
+ * Indicatates Wimax is disabled.
+ */
+ public static final int NET_4G_STATE_DISABLED = 1;
+
+ /**
+ * Indicatates Wimax is enabled.
+ */
+ public static final int NET_4G_STATE_ENABLED = 3;
+
+ /**
+ * Indicatates Wimax status is known.
+ */
+ public static final int NET_4G_STATE_UNKNOWN = 4;
+
+ /**
+ * Indicatates Wimax is in idle state.
+ */
+ public static final int WIMAX_IDLE = 6;
+
+ /**
+ * Indicatates Wimax is being deregistered.
+ */
+ public static final int WIMAX_DEREGISTRATION = 8;
+
+ /**
+ * Indicatates wimax state is unknown.
+ */
+ public static final int WIMAX_STATE_UNKNOWN = 0;
+
+ /**
+ * Indicatates wimax state is connected.
+ */
+ public static final int WIMAX_STATE_CONNECTED = 7;
+
+ /**
+ * Indicatates wimax state is disconnected.
+ */
+ public static final int WIMAX_STATE_DISCONNECTED = 9;
+
+}