Merge "Fix google-explicit-constructor warnings in media/mca."
diff --git a/Android.mk b/Android.mk
index 27cf90c..69e57b6 100644
--- a/Android.mk
+++ b/Android.mk
@@ -449,9 +449,9 @@
telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl \
telephony/java/com/android/internal/telephony/IWapPushManager.aidl \
wifi/java/android/net/wifi/IWifiManager.aidl \
- wifi/java/android/net/wifi/nan/IWifiNanEventListener.aidl \
+ wifi/java/android/net/wifi/nan/IWifiNanEventCallback.aidl \
wifi/java/android/net/wifi/nan/IWifiNanManager.aidl \
- wifi/java/android/net/wifi/nan/IWifiNanSessionListener.aidl \
+ wifi/java/android/net/wifi/nan/IWifiNanSessionCallback.aidl \
wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \
wifi/java/android/net/wifi/IWifiScanner.aidl \
wifi/java/android/net/wifi/IRttManager.aidl \
@@ -536,10 +536,8 @@
frameworks/base/media/java/android/media/browse/MediaBrowser.aidl \
frameworks/base/wifi/java/android/net/wifi/ScanSettings.aidl \
frameworks/base/wifi/java/android/net/wifi/nan/ConfigRequest.aidl \
- frameworks/base/wifi/java/android/net/wifi/nan/PublishData.aidl \
- frameworks/base/wifi/java/android/net/wifi/nan/SubscribeData.aidl \
- frameworks/base/wifi/java/android/net/wifi/nan/PublishSettings.aidl \
- frameworks/base/wifi/java/android/net/wifi/nan/SubscribeSettings.aidl \
+ frameworks/base/wifi/java/android/net/wifi/nan/PublishConfig.aidl \
+ frameworks/base/wifi/java/android/net/wifi/nan/SubscribeConfig.aidl \
frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pInfo.aidl \
frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.aidl \
frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pConfig.aidl \
diff --git a/api/current.txt b/api/current.txt
index 195b640..7b76478 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -50450,12 +50450,14 @@
method public static java.lang.Class<?> forName(java.lang.String, boolean, java.lang.ClassLoader) throws java.lang.ClassNotFoundException;
method public A getAnnotation(java.lang.Class<A>);
method public java.lang.annotation.Annotation[] getAnnotations();
+ method public A[] getAnnotationsByType(java.lang.Class<A>);
method public java.lang.String getCanonicalName();
method public java.lang.ClassLoader getClassLoader();
method public java.lang.Class<?>[] getClasses();
method public java.lang.Class<?> getComponentType();
method public java.lang.reflect.Constructor<T> getConstructor(java.lang.Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException;
method public java.lang.reflect.Constructor<?>[] getConstructors() throws java.lang.SecurityException;
+ method public A getDeclaredAnnotation(java.lang.Class<A>);
method public java.lang.annotation.Annotation[] getDeclaredAnnotations();
method public java.lang.Class<?>[] getDeclaredClasses();
method public java.lang.reflect.Constructor<T> getDeclaredConstructor(java.lang.Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException;
@@ -51897,7 +51899,6 @@
public final class Field extends java.lang.reflect.AccessibleObject implements java.lang.reflect.Member {
method public java.lang.Object get(java.lang.Object) throws java.lang.IllegalAccessException, java.lang.IllegalArgumentException;
- method public A getAnnotation(java.lang.Class<A>);
method public boolean getBoolean(java.lang.Object) throws java.lang.IllegalAccessException, java.lang.IllegalArgumentException;
method public byte getByte(java.lang.Object) throws java.lang.IllegalAccessException, java.lang.IllegalArgumentException;
method public char getChar(java.lang.Object) throws java.lang.IllegalAccessException, java.lang.IllegalArgumentException;
@@ -51929,7 +51930,7 @@
method public abstract java.lang.reflect.Type getGenericComponentType();
}
- public abstract interface GenericDeclaration {
+ public abstract interface GenericDeclaration implements java.lang.reflect.AnnotatedElement {
method public abstract java.lang.reflect.TypeVariable<?>[] getTypeParameters();
}
@@ -54970,6 +54971,12 @@
}
public static abstract interface KeyStore.Entry {
+ method public default java.util.Set<java.security.KeyStore.Entry.Attribute> getAttributes();
+ }
+
+ public static abstract interface KeyStore.Entry.Attribute {
+ method public abstract java.lang.String getName();
+ method public abstract java.lang.String getValue();
}
public static abstract interface KeyStore.LoadStoreParameter {
@@ -54978,11 +54985,15 @@
public static class KeyStore.PasswordProtection implements javax.security.auth.Destroyable java.security.KeyStore.ProtectionParameter {
ctor public KeyStore.PasswordProtection(char[]);
+ ctor public KeyStore.PasswordProtection(char[], java.lang.String, java.security.spec.AlgorithmParameterSpec);
method public synchronized char[] getPassword();
+ method public java.lang.String getProtectionAlgorithm();
+ method public java.security.spec.AlgorithmParameterSpec getProtectionParameters();
}
public static final class KeyStore.PrivateKeyEntry implements java.security.KeyStore.Entry {
ctor public KeyStore.PrivateKeyEntry(java.security.PrivateKey, java.security.cert.Certificate[]);
+ ctor public KeyStore.PrivateKeyEntry(java.security.PrivateKey, java.security.cert.Certificate[], java.util.Set<java.security.KeyStore.Entry.Attribute>);
method public java.security.cert.Certificate getCertificate();
method public java.security.cert.Certificate[] getCertificateChain();
method public java.security.PrivateKey getPrivateKey();
@@ -54993,11 +55004,13 @@
public static final class KeyStore.SecretKeyEntry implements java.security.KeyStore.Entry {
ctor public KeyStore.SecretKeyEntry(javax.crypto.SecretKey);
+ ctor public KeyStore.SecretKeyEntry(javax.crypto.SecretKey, java.util.Set<java.security.KeyStore.Entry.Attribute>);
method public javax.crypto.SecretKey getSecretKey();
}
public static final class KeyStore.TrustedCertificateEntry implements java.security.KeyStore.Entry {
ctor public KeyStore.TrustedCertificateEntry(java.security.cert.Certificate);
+ ctor public KeyStore.TrustedCertificateEntry(java.security.cert.Certificate, java.util.Set<java.security.KeyStore.Entry.Attribute>);
method public java.security.cert.Certificate getTrustedCertificate();
}
@@ -55133,10 +55146,11 @@
method public abstract boolean equals(java.lang.Object);
method public abstract java.lang.String getName();
method public abstract int hashCode();
+ method public default boolean implies(javax.security.auth.Subject);
method public abstract java.lang.String toString();
}
- public abstract interface PrivateKey implements java.security.Key {
+ public abstract interface PrivateKey implements javax.security.auth.Destroyable java.security.Key {
field public static final long serialVersionUID = 6034044314589513430L; // 0x53bd3b559a12c6d6L
}
@@ -55588,6 +55602,7 @@
method public abstract java.lang.String toString();
method public abstract void verify(java.security.PublicKey) throws java.security.cert.CertificateException, java.security.InvalidKeyException, java.security.NoSuchAlgorithmException, java.security.NoSuchProviderException, java.security.SignatureException;
method public abstract void verify(java.security.PublicKey, java.lang.String) throws java.security.cert.CertificateException, java.security.InvalidKeyException, java.security.NoSuchAlgorithmException, java.security.NoSuchProviderException, java.security.SignatureException;
+ method public void verify(java.security.PublicKey, java.security.Provider) throws java.security.cert.CertificateException, java.security.InvalidKeyException, java.security.NoSuchAlgorithmException, java.security.SignatureException;
method protected java.lang.Object writeReplace() throws java.io.ObjectStreamException;
}
@@ -55946,7 +55961,6 @@
method public javax.security.auth.x500.X500Principal getSubjectX500Principal();
method public abstract byte[] getTBSCertificate() throws java.security.cert.CertificateEncodingException;
method public abstract int getVersion();
- method public void verify(java.security.PublicKey, java.security.Provider) throws java.security.cert.CertificateException, java.security.InvalidKeyException, java.security.NoSuchAlgorithmException, java.security.SignatureException;
}
public abstract interface X509Extension {
diff --git a/api/system-current.txt b/api/system-current.txt
index 54f987a..60425ce 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -53806,12 +53806,14 @@
method public static java.lang.Class<?> forName(java.lang.String, boolean, java.lang.ClassLoader) throws java.lang.ClassNotFoundException;
method public A getAnnotation(java.lang.Class<A>);
method public java.lang.annotation.Annotation[] getAnnotations();
+ method public A[] getAnnotationsByType(java.lang.Class<A>);
method public java.lang.String getCanonicalName();
method public java.lang.ClassLoader getClassLoader();
method public java.lang.Class<?>[] getClasses();
method public java.lang.Class<?> getComponentType();
method public java.lang.reflect.Constructor<T> getConstructor(java.lang.Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException;
method public java.lang.reflect.Constructor<?>[] getConstructors() throws java.lang.SecurityException;
+ method public A getDeclaredAnnotation(java.lang.Class<A>);
method public java.lang.annotation.Annotation[] getDeclaredAnnotations();
method public java.lang.Class<?>[] getDeclaredClasses();
method public java.lang.reflect.Constructor<T> getDeclaredConstructor(java.lang.Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException;
@@ -55253,7 +55255,6 @@
public final class Field extends java.lang.reflect.AccessibleObject implements java.lang.reflect.Member {
method public java.lang.Object get(java.lang.Object) throws java.lang.IllegalAccessException, java.lang.IllegalArgumentException;
- method public A getAnnotation(java.lang.Class<A>);
method public boolean getBoolean(java.lang.Object) throws java.lang.IllegalAccessException, java.lang.IllegalArgumentException;
method public byte getByte(java.lang.Object) throws java.lang.IllegalAccessException, java.lang.IllegalArgumentException;
method public char getChar(java.lang.Object) throws java.lang.IllegalAccessException, java.lang.IllegalArgumentException;
@@ -55285,7 +55286,7 @@
method public abstract java.lang.reflect.Type getGenericComponentType();
}
- public abstract interface GenericDeclaration {
+ public abstract interface GenericDeclaration implements java.lang.reflect.AnnotatedElement {
method public abstract java.lang.reflect.TypeVariable<?>[] getTypeParameters();
}
@@ -58326,6 +58327,12 @@
}
public static abstract interface KeyStore.Entry {
+ method public default java.util.Set<java.security.KeyStore.Entry.Attribute> getAttributes();
+ }
+
+ public static abstract interface KeyStore.Entry.Attribute {
+ method public abstract java.lang.String getName();
+ method public abstract java.lang.String getValue();
}
public static abstract interface KeyStore.LoadStoreParameter {
@@ -58334,11 +58341,15 @@
public static class KeyStore.PasswordProtection implements javax.security.auth.Destroyable java.security.KeyStore.ProtectionParameter {
ctor public KeyStore.PasswordProtection(char[]);
+ ctor public KeyStore.PasswordProtection(char[], java.lang.String, java.security.spec.AlgorithmParameterSpec);
method public synchronized char[] getPassword();
+ method public java.lang.String getProtectionAlgorithm();
+ method public java.security.spec.AlgorithmParameterSpec getProtectionParameters();
}
public static final class KeyStore.PrivateKeyEntry implements java.security.KeyStore.Entry {
ctor public KeyStore.PrivateKeyEntry(java.security.PrivateKey, java.security.cert.Certificate[]);
+ ctor public KeyStore.PrivateKeyEntry(java.security.PrivateKey, java.security.cert.Certificate[], java.util.Set<java.security.KeyStore.Entry.Attribute>);
method public java.security.cert.Certificate getCertificate();
method public java.security.cert.Certificate[] getCertificateChain();
method public java.security.PrivateKey getPrivateKey();
@@ -58349,11 +58360,13 @@
public static final class KeyStore.SecretKeyEntry implements java.security.KeyStore.Entry {
ctor public KeyStore.SecretKeyEntry(javax.crypto.SecretKey);
+ ctor public KeyStore.SecretKeyEntry(javax.crypto.SecretKey, java.util.Set<java.security.KeyStore.Entry.Attribute>);
method public javax.crypto.SecretKey getSecretKey();
}
public static final class KeyStore.TrustedCertificateEntry implements java.security.KeyStore.Entry {
ctor public KeyStore.TrustedCertificateEntry(java.security.cert.Certificate);
+ ctor public KeyStore.TrustedCertificateEntry(java.security.cert.Certificate, java.util.Set<java.security.KeyStore.Entry.Attribute>);
method public java.security.cert.Certificate getTrustedCertificate();
}
@@ -58489,10 +58502,11 @@
method public abstract boolean equals(java.lang.Object);
method public abstract java.lang.String getName();
method public abstract int hashCode();
+ method public default boolean implies(javax.security.auth.Subject);
method public abstract java.lang.String toString();
}
- public abstract interface PrivateKey implements java.security.Key {
+ public abstract interface PrivateKey implements javax.security.auth.Destroyable java.security.Key {
field public static final long serialVersionUID = 6034044314589513430L; // 0x53bd3b559a12c6d6L
}
@@ -58944,6 +58958,7 @@
method public abstract java.lang.String toString();
method public abstract void verify(java.security.PublicKey) throws java.security.cert.CertificateException, java.security.InvalidKeyException, java.security.NoSuchAlgorithmException, java.security.NoSuchProviderException, java.security.SignatureException;
method public abstract void verify(java.security.PublicKey, java.lang.String) throws java.security.cert.CertificateException, java.security.InvalidKeyException, java.security.NoSuchAlgorithmException, java.security.NoSuchProviderException, java.security.SignatureException;
+ method public void verify(java.security.PublicKey, java.security.Provider) throws java.security.cert.CertificateException, java.security.InvalidKeyException, java.security.NoSuchAlgorithmException, java.security.SignatureException;
method protected java.lang.Object writeReplace() throws java.io.ObjectStreamException;
}
@@ -59302,7 +59317,6 @@
method public javax.security.auth.x500.X500Principal getSubjectX500Principal();
method public abstract byte[] getTBSCertificate() throws java.security.cert.CertificateEncodingException;
method public abstract int getVersion();
- method public void verify(java.security.PublicKey, java.security.Provider) throws java.security.cert.CertificateException, java.security.InvalidKeyException, java.security.NoSuchAlgorithmException, java.security.SignatureException;
}
public abstract interface X509Extension {
diff --git a/api/test-current.txt b/api/test-current.txt
index d06e5cf..ec3b55f 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -50530,12 +50530,14 @@
method public static java.lang.Class<?> forName(java.lang.String, boolean, java.lang.ClassLoader) throws java.lang.ClassNotFoundException;
method public A getAnnotation(java.lang.Class<A>);
method public java.lang.annotation.Annotation[] getAnnotations();
+ method public A[] getAnnotationsByType(java.lang.Class<A>);
method public java.lang.String getCanonicalName();
method public java.lang.ClassLoader getClassLoader();
method public java.lang.Class<?>[] getClasses();
method public java.lang.Class<?> getComponentType();
method public java.lang.reflect.Constructor<T> getConstructor(java.lang.Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException;
method public java.lang.reflect.Constructor<?>[] getConstructors() throws java.lang.SecurityException;
+ method public A getDeclaredAnnotation(java.lang.Class<A>);
method public java.lang.annotation.Annotation[] getDeclaredAnnotations();
method public java.lang.Class<?>[] getDeclaredClasses();
method public java.lang.reflect.Constructor<T> getDeclaredConstructor(java.lang.Class<?>...) throws java.lang.NoSuchMethodException, java.lang.SecurityException;
@@ -51977,7 +51979,6 @@
public final class Field extends java.lang.reflect.AccessibleObject implements java.lang.reflect.Member {
method public java.lang.Object get(java.lang.Object) throws java.lang.IllegalAccessException, java.lang.IllegalArgumentException;
- method public A getAnnotation(java.lang.Class<A>);
method public boolean getBoolean(java.lang.Object) throws java.lang.IllegalAccessException, java.lang.IllegalArgumentException;
method public byte getByte(java.lang.Object) throws java.lang.IllegalAccessException, java.lang.IllegalArgumentException;
method public char getChar(java.lang.Object) throws java.lang.IllegalAccessException, java.lang.IllegalArgumentException;
@@ -52009,7 +52010,7 @@
method public abstract java.lang.reflect.Type getGenericComponentType();
}
- public abstract interface GenericDeclaration {
+ public abstract interface GenericDeclaration implements java.lang.reflect.AnnotatedElement {
method public abstract java.lang.reflect.TypeVariable<?>[] getTypeParameters();
}
@@ -55050,6 +55051,12 @@
}
public static abstract interface KeyStore.Entry {
+ method public default java.util.Set<java.security.KeyStore.Entry.Attribute> getAttributes();
+ }
+
+ public static abstract interface KeyStore.Entry.Attribute {
+ method public abstract java.lang.String getName();
+ method public abstract java.lang.String getValue();
}
public static abstract interface KeyStore.LoadStoreParameter {
@@ -55058,11 +55065,15 @@
public static class KeyStore.PasswordProtection implements javax.security.auth.Destroyable java.security.KeyStore.ProtectionParameter {
ctor public KeyStore.PasswordProtection(char[]);
+ ctor public KeyStore.PasswordProtection(char[], java.lang.String, java.security.spec.AlgorithmParameterSpec);
method public synchronized char[] getPassword();
+ method public java.lang.String getProtectionAlgorithm();
+ method public java.security.spec.AlgorithmParameterSpec getProtectionParameters();
}
public static final class KeyStore.PrivateKeyEntry implements java.security.KeyStore.Entry {
ctor public KeyStore.PrivateKeyEntry(java.security.PrivateKey, java.security.cert.Certificate[]);
+ ctor public KeyStore.PrivateKeyEntry(java.security.PrivateKey, java.security.cert.Certificate[], java.util.Set<java.security.KeyStore.Entry.Attribute>);
method public java.security.cert.Certificate getCertificate();
method public java.security.cert.Certificate[] getCertificateChain();
method public java.security.PrivateKey getPrivateKey();
@@ -55073,11 +55084,13 @@
public static final class KeyStore.SecretKeyEntry implements java.security.KeyStore.Entry {
ctor public KeyStore.SecretKeyEntry(javax.crypto.SecretKey);
+ ctor public KeyStore.SecretKeyEntry(javax.crypto.SecretKey, java.util.Set<java.security.KeyStore.Entry.Attribute>);
method public javax.crypto.SecretKey getSecretKey();
}
public static final class KeyStore.TrustedCertificateEntry implements java.security.KeyStore.Entry {
ctor public KeyStore.TrustedCertificateEntry(java.security.cert.Certificate);
+ ctor public KeyStore.TrustedCertificateEntry(java.security.cert.Certificate, java.util.Set<java.security.KeyStore.Entry.Attribute>);
method public java.security.cert.Certificate getTrustedCertificate();
}
@@ -55213,10 +55226,11 @@
method public abstract boolean equals(java.lang.Object);
method public abstract java.lang.String getName();
method public abstract int hashCode();
+ method public default boolean implies(javax.security.auth.Subject);
method public abstract java.lang.String toString();
}
- public abstract interface PrivateKey implements java.security.Key {
+ public abstract interface PrivateKey implements javax.security.auth.Destroyable java.security.Key {
field public static final long serialVersionUID = 6034044314589513430L; // 0x53bd3b559a12c6d6L
}
@@ -55668,6 +55682,7 @@
method public abstract java.lang.String toString();
method public abstract void verify(java.security.PublicKey) throws java.security.cert.CertificateException, java.security.InvalidKeyException, java.security.NoSuchAlgorithmException, java.security.NoSuchProviderException, java.security.SignatureException;
method public abstract void verify(java.security.PublicKey, java.lang.String) throws java.security.cert.CertificateException, java.security.InvalidKeyException, java.security.NoSuchAlgorithmException, java.security.NoSuchProviderException, java.security.SignatureException;
+ method public void verify(java.security.PublicKey, java.security.Provider) throws java.security.cert.CertificateException, java.security.InvalidKeyException, java.security.NoSuchAlgorithmException, java.security.SignatureException;
method protected java.lang.Object writeReplace() throws java.io.ObjectStreamException;
}
@@ -56026,7 +56041,6 @@
method public javax.security.auth.x500.X500Principal getSubjectX500Principal();
method public abstract byte[] getTBSCertificate() throws java.security.cert.CertificateEncodingException;
method public abstract int getVersion();
- method public void verify(java.security.PublicKey, java.security.Provider) throws java.security.cert.CertificateException, java.security.InvalidKeyException, java.security.NoSuchAlgorithmException, java.security.SignatureException;
}
public abstract interface X509Extension {
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 55744b9..42ddf10 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -515,15 +515,15 @@
}});
registerService(Context.WIFI_NAN_SERVICE, WifiNanManager.class,
- new StaticServiceFetcher<WifiNanManager>() {
+ new CachedServiceFetcher<WifiNanManager>() {
@Override
- public WifiNanManager createService() {
+ public WifiNanManager createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(Context.WIFI_NAN_SERVICE);
IWifiNanManager service = IWifiNanManager.Stub.asInterface(b);
if (service == null) {
return null;
}
- return new WifiNanManager(service);
+ return new WifiNanManager(ctx.getOuterContext(), service);
}});
registerService(Context.WIFI_SCANNING_SERVICE, WifiScanner.class,
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 6107f37..fee927c 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3135,7 +3135,7 @@
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.net.wifi.nan.WifiNanManager} for handling management of
- * Wi-Fi NAN discovery and connections.
+ * Wi-Fi NAN.
*
* @see #getSystemService
* @see android.net.wifi.nan.WifiNanManager
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b06568c..089a420 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2028,8 +2028,7 @@
/**
* Feature for {@link #getSystemAvailableFeatures} and
- * {@link #hasSystemFeature}: The device supports Wi-Fi Aware (NAN)
- * networking.
+ * {@link #hasSystemFeature}: The device supports Wi-Fi NAN.
*
* @hide PROPOSED_NAN_API
*/
diff --git a/core/java/android/net/LocalSocketImpl.java b/core/java/android/net/LocalSocketImpl.java
index b83fb26..0f0e9c4 100644
--- a/core/java/android/net/LocalSocketImpl.java
+++ b/core/java/android/net/LocalSocketImpl.java
@@ -516,13 +516,11 @@
Os.setsockoptLinger(fd, OsConstants.SOL_SOCKET, OsConstants.SO_LINGER, linger);
break;
case SocketOptions.SO_TIMEOUT:
- /*
- * SO_TIMEOUT from the core library gets converted to
- * SO_SNDTIMEO, but the option is supposed to set both
- * send and receive timeouts. Note: The incoming timeout
- * value is in milliseconds.
- */
+ // 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;
diff --git a/core/java/android/net/network-policy-restrictions.md b/core/java/android/net/network-policy-restrictions.md
index fe13f7a..63ce1a2 100644
--- a/core/java/android/net/network-policy-restrictions.md
+++ b/core/java/android/net/network-policy-restrictions.md
@@ -29,8 +29,8 @@
| **DS** | *WL* | ok | blk | ok | ok |
| **ON** | *!WL* | blk | blk | blk | blk |
| | *BL* | blk | blk | blk | blk |
-| **DS** | *WL* | blk | ok | ok | ok |
-| **OFF** | *!WL* | blk | ok | ok | ok |
+| **DS** | *WL* | blk | blk | ok | ok |
+| **OFF** | *!WL* | blk | blk | ok | ok |
| | *BL* | blk | blk | blk | blk |
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 36ba696..e1dd02a 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -23,7 +23,6 @@
import android.net.NetworkStats;
import android.net.RouteInfo;
import android.net.UidRange;
-import android.net.wifi.WifiConfiguration;
import android.os.INetworkActivityListener;
/**
@@ -217,27 +216,6 @@
void detachPppd(String tty);
/**
- * Load firmware for operation in the given mode. Currently the three
- * modes supported are "AP", "STA" and "P2P".
- */
- void wifiFirmwareReload(String wlanIface, String mode);
-
- /**
- * Start Wifi Access Point
- */
- void startAccessPoint(in WifiConfiguration wifiConfig, String iface);
-
- /**
- * Stop Wifi Access Point
- */
- void stopAccessPoint(String iface);
-
- /**
- * Set Access Point config
- */
- void setAccessPoint(in WifiConfiguration wifiConfig, String iface);
-
- /**
** DATA USAGE RELATED
**/
diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java
index 39fd36b..b0d45e1d1 100644
--- a/core/java/com/android/internal/util/StateMachine.java
+++ b/core/java/com/android/internal/util/StateMachine.java
@@ -29,8 +29,8 @@
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
-import java.util.Iterator;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.Vector;
/**
@@ -750,6 +750,14 @@
/** The destination state when transitionTo has been invoked */
private State mDestState;
+ /**
+ * Indicates if a transition is in progress
+ *
+ * This will be true for all calls of State.exit and all calls of State.enter except for the
+ * last enter call for the current destination state.
+ */
+ private boolean mTransitionInProgress = false;
+
/** The list of deferred messages */
private ArrayList<Message> mDeferredMessages = new ArrayList<Message>();
@@ -862,6 +870,8 @@
* invoke the exit methods then the enter methods.
*/
StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
+ // flag is cleared in invokeEnterMethods before entering the target state
+ mTransitionInProgress = true;
invokeExitMethods(commonStateInfo);
int stateStackEnteringIndex = moveTempStateStackToStateStack();
invokeEnterMethods(stateStackEnteringIndex);
@@ -1017,10 +1027,15 @@
*/
private final void invokeEnterMethods(int stateStackEnteringIndex) {
for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
+ if (stateStackEnteringIndex == mStateStackTopIndex) {
+ // Last enter state for transition
+ mTransitionInProgress = false;
+ }
if (mDbg) mSm.log("invokeEnterMethods: " + mStateStack[i].state.getName());
mStateStack[i].state.enter();
mStateStack[i].active = true;
}
+ mTransitionInProgress = false; // ensure flag set to false if no methods called
}
/**
@@ -1196,6 +1211,10 @@
/** @see StateMachine#transitionTo(IState) */
private final void transitionTo(IState destState) {
+ if (mTransitionInProgress) {
+ Log.wtf(mSm.mName, "transitionTo called while transition already in progress to " +
+ mDestState + ", new target state=" + destState);
+ }
mDestState = (State) destState;
if (mDbg) mSm.log("transitionTo: destState=" + mDestState.getName());
}
@@ -1305,7 +1324,7 @@
* @param state the state to add
* @param parent the parent of state
*/
- protected final void addState(State state, State parent) {
+ public final void addState(State state, State parent) {
mSmHandler.addState(state, parent);
}
@@ -1313,7 +1332,7 @@
* Add a new state to the state machine, parent will be null
* @param state to add
*/
- protected final void addState(State state) {
+ public final void addState(State state) {
mSmHandler.addState(state, null);
}
@@ -1323,14 +1342,14 @@
*
* @param initialState is the state which will receive the first message.
*/
- protected final void setInitialState(State initialState) {
+ public final void setInitialState(State initialState) {
mSmHandler.setInitialState(initialState);
}
/**
* @return current message
*/
- protected final Message getCurrentMessage() {
+ public final Message getCurrentMessage() {
// mSmHandler can be null if the state machine has quit.
SmHandler smh = mSmHandler;
if (smh == null) return null;
@@ -1340,7 +1359,7 @@
/**
* @return current state
*/
- protected final IState getCurrentState() {
+ public final IState getCurrentState() {
// mSmHandler can be null if the state machine has quit.
SmHandler smh = mSmHandler;
if (smh == null) return null;
@@ -1361,7 +1380,7 @@
*
* @param destState will be the state that receives the next message.
*/
- protected final void transitionTo(IState destState) {
+ public final void transitionTo(IState destState) {
mSmHandler.transitionTo(destState);
}
@@ -1372,7 +1391,7 @@
* for all subsequent messages haltedProcessMessage
* will be called.
*/
- protected final void transitionToHaltingState() {
+ public final void transitionToHaltingState() {
mSmHandler.transitionTo(mSmHandler.mHaltingState);
}
@@ -1385,7 +1404,7 @@
*
* @param msg is deferred until the next transition.
*/
- protected final void deferMessage(Message msg) {
+ public final void deferMessage(Message msg) {
mSmHandler.deferMessage(msg);
}
@@ -1496,7 +1515,7 @@
*
* @param string
*/
- protected void addLogRec(String string) {
+ public void addLogRec(String string) {
// mSmHandler can be null if the state machine has quit.
SmHandler smh = mSmHandler;
if (smh == null) return;
@@ -1642,7 +1661,7 @@
*
* Message is ignored if state machine has quit.
*/
- public final void sendMessage(int what) {
+ public void sendMessage(int what) {
// mSmHandler can be null if the state machine has quit.
SmHandler smh = mSmHandler;
if (smh == null) return;
@@ -1655,7 +1674,7 @@
*
* Message is ignored if state machine has quit.
*/
- public final void sendMessage(int what, Object obj) {
+ public void sendMessage(int what, Object obj) {
// mSmHandler can be null if the state machine has quit.
SmHandler smh = mSmHandler;
if (smh == null) return;
@@ -1668,7 +1687,7 @@
*
* Message is ignored if state machine has quit.
*/
- public final void sendMessage(int what, int arg1) {
+ public void sendMessage(int what, int arg1) {
// mSmHandler can be null if the state machine has quit.
SmHandler smh = mSmHandler;
if (smh == null) return;
@@ -1681,7 +1700,7 @@
*
* Message is ignored if state machine has quit.
*/
- public final void sendMessage(int what, int arg1, int arg2) {
+ public void sendMessage(int what, int arg1, int arg2) {
// mSmHandler can be null if the state machine has quit.
SmHandler smh = mSmHandler;
if (smh == null) return;
@@ -1694,7 +1713,7 @@
*
* Message is ignored if state machine has quit.
*/
- public final void sendMessage(int what, int arg1, int arg2, Object obj) {
+ public void sendMessage(int what, int arg1, int arg2, Object obj) {
// mSmHandler can be null if the state machine has quit.
SmHandler smh = mSmHandler;
if (smh == null) return;
@@ -1707,7 +1726,7 @@
*
* Message is ignored if state machine has quit.
*/
- public final void sendMessage(Message msg) {
+ public void sendMessage(Message msg) {
// mSmHandler can be null if the state machine has quit.
SmHandler smh = mSmHandler;
if (smh == null) return;
@@ -1720,7 +1739,7 @@
*
* Message is ignored if state machine has quit.
*/
- public final void sendMessageDelayed(int what, long delayMillis) {
+ public void sendMessageDelayed(int what, long delayMillis) {
// mSmHandler can be null if the state machine has quit.
SmHandler smh = mSmHandler;
if (smh == null) return;
@@ -1733,7 +1752,7 @@
*
* Message is ignored if state machine has quit.
*/
- public final void sendMessageDelayed(int what, Object obj, long delayMillis) {
+ public void sendMessageDelayed(int what, Object obj, long delayMillis) {
// mSmHandler can be null if the state machine has quit.
SmHandler smh = mSmHandler;
if (smh == null) return;
@@ -1746,7 +1765,7 @@
*
* Message is ignored if state machine has quit.
*/
- public final void sendMessageDelayed(int what, int arg1, long delayMillis) {
+ public void sendMessageDelayed(int what, int arg1, long delayMillis) {
// mSmHandler can be null if the state machine has quit.
SmHandler smh = mSmHandler;
if (smh == null) return;
@@ -1759,7 +1778,7 @@
*
* Message is ignored if state machine has quit.
*/
- public final void sendMessageDelayed(int what, int arg1, int arg2, long delayMillis) {
+ public void sendMessageDelayed(int what, int arg1, int arg2, long delayMillis) {
// mSmHandler can be null if the state machine has quit.
SmHandler smh = mSmHandler;
if (smh == null) return;
@@ -1772,7 +1791,7 @@
*
* Message is ignored if state machine has quit.
*/
- public final void sendMessageDelayed(int what, int arg1, int arg2, Object obj,
+ public void sendMessageDelayed(int what, int arg1, int arg2, Object obj,
long delayMillis) {
// mSmHandler can be null if the state machine has quit.
SmHandler smh = mSmHandler;
@@ -1786,7 +1805,7 @@
*
* Message is ignored if state machine has quit.
*/
- public final void sendMessageDelayed(Message msg, long delayMillis) {
+ public void sendMessageDelayed(Message msg, long delayMillis) {
// mSmHandler can be null if the state machine has quit.
SmHandler smh = mSmHandler;
if (smh == null) return;
@@ -1947,7 +1966,7 @@
/**
* Quit the state machine after all currently queued up messages are processed.
*/
- protected final void quit() {
+ public final void quit() {
// mSmHandler can be null if the state machine is already stopped.
SmHandler smh = mSmHandler;
if (smh == null) return;
@@ -1958,7 +1977,7 @@
/**
* Quit the state machine immediately all currently queued messages will be discarded.
*/
- protected final void quitNow() {
+ public final void quitNow() {
// mSmHandler can be null if the state machine is already stopped.
SmHandler smh = mSmHandler;
if (smh == null) return;
diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java
index 992cb4e..83d5ce3 100644
--- a/core/java/com/android/internal/util/XmlUtils.java
+++ b/core/java/com/android/internal/util/XmlUtils.java
@@ -392,10 +392,10 @@
StringBuilder sb = new StringBuilder(val.length*2);
for (int i=0; i<N; i++) {
int b = val[i];
- int h = b>>4;
- sb.append(h >= 10 ? ('a'+h-10) : ('0'+h));
- h = b&0xff;
- sb.append(h >= 10 ? ('a'+h-10) : ('0'+h));
+ int h = (b >> 4) & 0x0f;
+ sb.append((char)(h >= 10 ? ('a'+h-10) : ('0'+h)));
+ h = b & 0x0f;
+ sb.append((char)(h >= 10 ? ('a'+h-10) : ('0'+h)));
}
out.text(sb.toString());
@@ -996,6 +996,73 @@
}
/**
+ * Read a byte[] object from an XmlPullParser. The XML data could
+ * previously have been generated by writeByteArrayXml(). The XmlPullParser
+ * must be positioned <em>after</em> the tag that begins the list.
+ *
+ * @param parser The XmlPullParser from which to read the list data.
+ * @param endTag Name of the tag that will end the list, usually "list".
+ * @param name An array of one string, used to return the name attribute
+ * of the list's tag.
+ *
+ * @return Returns a newly generated byte[].
+ *
+ * @see #writeByteArrayXml
+ */
+ public static final byte[] readThisByteArrayXml(XmlPullParser parser,
+ String endTag, String[] name)
+ throws XmlPullParserException, java.io.IOException {
+
+ int num;
+ try {
+ num = Integer.parseInt(parser.getAttributeValue(null, "num"));
+ } catch (NullPointerException e) {
+ throw new XmlPullParserException(
+ "Need num attribute in byte-array");
+ } catch (NumberFormatException e) {
+ throw new XmlPullParserException(
+ "Not a number in num attribute in byte-array");
+ }
+
+ byte[] array = new byte[num];
+
+ int eventType = parser.getEventType();
+ do {
+ if (eventType == parser.TEXT) {
+ if (num > 0) {
+ String values = parser.getText();
+ if (values == null || values.length() != num * 2) {
+ throw new XmlPullParserException(
+ "Invalid value found in byte-array: " + values);
+ }
+ // This is ugly, but keeping it to mirror the logic in #writeByteArrayXml.
+ for (int i = 0; i < num; i ++) {
+ char nibbleHighChar = values.charAt(2 * i);
+ char nibbleLowChar = values.charAt(2 * i + 1);
+ int nibbleHigh = nibbleHighChar > 'a' ? (nibbleHighChar - 'a' + 10)
+ : (nibbleHighChar - '0');
+ int nibbleLow = nibbleLowChar > 'a' ? (nibbleLowChar - 'a' + 10)
+ : (nibbleLowChar - '0');
+ array[i] = (byte) ((nibbleHigh & 0x0F) << 4 | (nibbleLow & 0x0F));
+ }
+ }
+ } else if (eventType == parser.END_TAG) {
+ if (parser.getName().equals(endTag)) {
+ return array;
+ } else {
+ throw new XmlPullParserException(
+ "Expected " + endTag + " end tag at: "
+ + parser.getName());
+ }
+ }
+ eventType = parser.next();
+ } while (eventType != parser.END_DOCUMENT);
+
+ throw new XmlPullParserException(
+ "Document ended before " + endTag + " end tag");
+ }
+
+ /**
* Read an int[] object from an XmlPullParser. The XML data could
* previously have been generated by writeIntArrayXml(). The XmlPullParser
* must be positioned <em>after</em> the tag that begins the list.
@@ -1018,10 +1085,10 @@
num = Integer.parseInt(parser.getAttributeValue(null, "num"));
} catch (NullPointerException e) {
throw new XmlPullParserException(
- "Need num attribute in byte-array");
+ "Need num attribute in int-array");
} catch (NumberFormatException e) {
throw new XmlPullParserException(
- "Not a number in num attribute in byte-array");
+ "Not a number in num attribute in int-array");
}
parser.next();
@@ -1377,6 +1444,11 @@
"Unexpected end of document in <string>");
} else if ((res = readThisPrimitiveValueXml(parser, tagName)) != null) {
// all work already done by readThisPrimitiveValueXml
+ } else if (tagName.equals("byte-array")) {
+ res = readThisByteArrayXml(parser, "byte-array", name);
+ name[0] = valueName;
+ //System.out.println("Returning value for " + valueName + ": " + res);
+ return res;
} else if (tagName.equals("int-array")) {
res = readThisIntArrayXml(parser, "int-array", name);
name[0] = valueName;
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index 5baa8f8..f6a9bc7 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -127,7 +127,7 @@
*/
class JavaPixelAllocator : public SkBRDAllocator {
public:
- JavaPixelAllocator(JNIEnv* env);
+ explicit JavaPixelAllocator(JNIEnv* env);
~JavaPixelAllocator();
virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) override;
@@ -215,7 +215,7 @@
class AshmemPixelAllocator : public SkBitmap::Allocator {
public:
- AshmemPixelAllocator(JNIEnv* env);
+ explicit AshmemPixelAllocator(JNIEnv* env);
~AshmemPixelAllocator();
virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable);
android::Bitmap* getStorageObjAndReset() {
diff --git a/core/jni/android/graphics/Utils.h b/core/jni/android/graphics/Utils.h
index d1a74a0..fffec5b 100644
--- a/core/jni/android/graphics/Utils.h
+++ b/core/jni/android/graphics/Utils.h
@@ -28,7 +28,7 @@
class AssetStreamAdaptor : public SkStreamRewindable {
public:
- AssetStreamAdaptor(Asset*);
+ explicit AssetStreamAdaptor(Asset*);
virtual bool rewind();
virtual size_t read(void* buffer, size_t size);
@@ -53,7 +53,7 @@
*/
class AutoFDSeek {
public:
- AutoFDSeek(int fd) : fFD(fd) {
+ explicit AutoFDSeek(int fd) : fFD(fd) {
fCurr = ::lseek(fd, 0, SEEK_CUR);
}
~AutoFDSeek() {
diff --git a/core/jni/android/graphics/YuvToJpegEncoder.h b/core/jni/android/graphics/YuvToJpegEncoder.h
index 1ea844a..7e7b935 100644
--- a/core/jni/android/graphics/YuvToJpegEncoder.h
+++ b/core/jni/android/graphics/YuvToJpegEncoder.h
@@ -18,7 +18,7 @@
*/
static YuvToJpegEncoder* create(int pixelFormat, int* strides);
- YuvToJpegEncoder(int* strides);
+ explicit YuvToJpegEncoder(int* strides);
/** Encode YUV data to jpeg, which is output to a stream.
*
@@ -47,7 +47,7 @@
class Yuv420SpToJpegEncoder : public YuvToJpegEncoder {
public:
- Yuv420SpToJpegEncoder(int* strides);
+ explicit Yuv420SpToJpegEncoder(int* strides);
virtual ~Yuv420SpToJpegEncoder() {}
private:
@@ -61,7 +61,7 @@
class Yuv422IToJpegEncoder : public YuvToJpegEncoder {
public:
- Yuv422IToJpegEncoder(int* strides);
+ explicit Yuv422IToJpegEncoder(int* strides);
virtual ~Yuv422IToJpegEncoder() {}
private:
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f289eeb..f712c34 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -294,6 +294,7 @@
<protected-broadcast android:name="android.net.wifi.CONFIGURED_NETWORKS_CHANGE" />
<protected-broadcast android:name="android.net.wifi.supplicant.CONNECTION_CHANGE" />
<protected-broadcast android:name="android.net.wifi.supplicant.STATE_CHANGE" />
+ <protected-broadcast android:name="android.net.wifi.nan.STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.p2p.STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.p2p.DISCOVERY_STATE_CHANGE" />
<protected-broadcast android:name="android.net.wifi.p2p.THIS_DEVICE_CHANGED" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 87cb2e8..05d9a28 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -427,8 +427,11 @@
<!-- Boolean indicating whether or not wifi firmware debugging is enabled -->
<bool translatable="false" name="config_wifi_enable_wifi_firmware_debugging">true</bool>
- <!-- Integer size limit, in KB, for a single WifiLogger ringbuffer -->
- <integer translatable="false" name="config_wifi_logger_ring_buffer_size_limit_kb">32</integer>
+ <!-- Integer size limit, in KB, for a single WifiLogger ringbuffer, in default logging mode -->
+ <integer translatable="false" name="config_wifi_logger_ring_buffer_default_size_limit_kb">32</integer>
+
+ <!-- Integer size limit, in KB, for a single WifiLogger ringbuffer, in verbose logging mode -->
+ <integer translatable="false" name="config_wifi_logger_ring_buffer_verbose_size_limit_kb">1024</integer>
<!-- Boolean indicating whether or not wifi should turn off when emergency call is made -->
<bool translatable="false" name="config_wifi_turn_off_during_emergency_call">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d154b03..5018e2f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -297,7 +297,8 @@
<java-symbol type="bool" name="config_wifi_enable_disconnection_debounce" />
<java-symbol type="bool" name="config_wifi_revert_country_code_on_cellular_loss" />
<java-symbol type="bool" name="config_wifi_enable_wifi_firmware_debugging" />
- <java-symbol type="integer" name="config_wifi_logger_ring_buffer_size_limit_kb" />
+ <java-symbol type="integer" name="config_wifi_logger_ring_buffer_default_size_limit_kb" />
+ <java-symbol type="integer" name="config_wifi_logger_ring_buffer_verbose_size_limit_kb" />
<java-symbol type="bool" name="config_wifi_turn_off_during_emergency_call" />
<java-symbol type="bool" name="config_supportMicNearUltrasound" />
<java-symbol type="bool" name="config_supportSpeakerNearUltrasound" />
diff --git a/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java b/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java
index 302aa87..d29b572 100644
--- a/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java
@@ -39,7 +39,6 @@
/**
* Test for StateMachine.
*/
-@Suppress // Failing
public class StateMachineTest extends TestCase {
private static final String ENTER = "enter";
private static final String EXIT = "exit";
@@ -100,7 +99,7 @@
@Override
public void onQuitting() {
- log("onQuitting");
+ tlog("onQuitting");
addLogRec(ON_QUITTING);
mLogRecs = mThisSm.copyLogRecs();
synchronized (mThisSm) {
@@ -111,7 +110,7 @@
class S1 extends State {
@Override
public void exit() {
- log("S1.exit");
+ tlog("S1.exit");
addLogRec(EXIT);
}
@Override
@@ -119,13 +118,13 @@
switch(message.what) {
// Sleep and assume the other messages will be queued up.
case TEST_CMD_1: {
- log("TEST_CMD_1");
+ tlog("TEST_CMD_1");
sleep(500);
quit();
break;
}
default: {
- log("default what=" + message.what);
+ tlog("default what=" + message.what);
break;
}
}
@@ -201,7 +200,7 @@
@Override
public void onQuitting() {
- log("onQuitting");
+ tlog("onQuitting");
addLogRec(ON_QUITTING);
// Get a copy of the log records since we're quitting and they will disappear
mLogRecs = mThisSm.copyLogRecs();
@@ -214,7 +213,7 @@
class S1 extends State {
@Override
public void exit() {
- log("S1.exit");
+ tlog("S1.exit");
addLogRec(EXIT);
}
@Override
@@ -222,13 +221,13 @@
switch(message.what) {
// Sleep and assume the other messages will be queued up.
case TEST_CMD_1: {
- log("TEST_CMD_1");
+ tlog("TEST_CMD_1");
sleep(500);
quitNow();
break;
}
default: {
- log("default what=" + message.what);
+ tlog("default what=" + message.what);
break;
}
}
@@ -309,12 +308,12 @@
// Test transitions in enter on the initial state work
addLogRec(ENTER);
transitionTo(mS2);
- log("S1.enter");
+ tlog("S1.enter");
}
@Override
public void exit() {
addLogRec(EXIT);
- log("S1.exit");
+ tlog("S1.exit");
}
}
@@ -322,7 +321,7 @@
@Override
public void enter() {
addLogRec(ENTER);
- log("S2.enter");
+ tlog("S2.enter");
}
@Override
public void exit() {
@@ -332,14 +331,14 @@
assertEquals(TEST_CMD_1, getCurrentMessage().what);
addLogRec(EXIT);
- log("S2.exit");
+ tlog("S2.exit");
}
@Override
public boolean processMessage(Message message) {
// Start a transition to S3 but it will be
// changed to a transition to S4 in exit
transitionTo(mS3);
- log("S2.processMessage");
+ tlog("S2.processMessage");
return HANDLED;
}
}
@@ -348,12 +347,12 @@
@Override
public void enter() {
addLogRec(ENTER);
- log("S3.enter");
+ tlog("S3.enter");
}
@Override
public void exit() {
addLogRec(EXIT);
- log("S3.exit");
+ tlog("S3.exit");
}
}
@@ -363,12 +362,12 @@
addLogRec(ENTER);
// Test that we can do halting in an enter/exit
transitionToHaltingState();
- log("S4.enter");
+ tlog("S4.enter");
}
@Override
public void exit() {
addLogRec(EXIT);
- log("S4.exit");
+ tlog("S4.exit");
}
}
@@ -572,7 +571,7 @@
// Set the initial state
setInitialState(mS1);
- if (DBG) log("StateMachine1: ctor X");
+ if (DBG) tlog("StateMachine1: ctor X");
}
class S1 extends State {
@@ -673,7 +672,7 @@
// Set the initial state
setInitialState(mS1);
- if (DBG) log("StateMachine2: ctor X");
+ if (DBG) tlog("StateMachine2: ctor X");
}
class S1 extends State {
@@ -782,7 +781,7 @@
// Set the initial state will be the child
setInitialState(mChildState);
- if (DBG) log("StateMachine3: ctor X");
+ if (DBG) tlog("StateMachine3: ctor X");
}
class ParentState extends State {
@@ -869,7 +868,7 @@
// Set the initial state will be child 1
setInitialState(mChildState1);
- if (DBG) log("StateMachine4: ctor X");
+ if (DBG) tlog("StateMachine4: ctor X");
}
class ParentState extends State {
@@ -969,7 +968,7 @@
// Set the initial state will be the child
setInitialState(mChildState1);
- if (DBG) log("StateMachine5: ctor X");
+ if (DBG) tlog("StateMachine5: ctor X");
}
class ParentState1 extends State {
@@ -1296,7 +1295,7 @@
// Set the initial state
setInitialState(mS1);
- if (DBG) log("StateMachine6: ctor X");
+ if (DBG) tlog("StateMachine6: ctor X");
}
class S1 extends State {
@@ -1383,7 +1382,7 @@
// Set the initial state
setInitialState(mS1);
- if (DBG) log("StateMachine7: ctor X");
+ if (DBG) tlog("StateMachine7: ctor X");
}
class S1 extends State {
@@ -1654,7 +1653,7 @@
Hsm1(String name) {
super(name);
- log("ctor E");
+ tlog("ctor E");
// Add states, use indentation to show hierarchy
addState(mP1);
@@ -1664,22 +1663,22 @@
// Set the initial state
setInitialState(mS1);
- log("ctor X");
+ tlog("ctor X");
}
class P1 extends State {
@Override
public void enter() {
- log("P1.enter");
+ tlog("P1.enter");
}
@Override
public void exit() {
- log("P1.exit");
+ tlog("P1.exit");
}
@Override
public boolean processMessage(Message message) {
boolean retVal;
- log("P1.processMessage what=" + message.what);
+ tlog("P1.processMessage what=" + message.what);
switch(message.what) {
case CMD_2:
// CMD_2 will arrive in mS2 before CMD_3
@@ -1700,15 +1699,15 @@
class S1 extends State {
@Override
public void enter() {
- log("S1.enter");
+ tlog("S1.enter");
}
@Override
public void exit() {
- log("S1.exit");
+ tlog("S1.exit");
}
@Override
public boolean processMessage(Message message) {
- log("S1.processMessage what=" + message.what);
+ tlog("S1.processMessage what=" + message.what);
if (message.what == CMD_1) {
// Transition to ourself to show that enter/exit is called
transitionTo(mS1);
@@ -1723,16 +1722,16 @@
class S2 extends State {
@Override
public void enter() {
- log("S2.enter");
+ tlog("S2.enter");
}
@Override
public void exit() {
- log("S2.exit");
+ tlog("S2.exit");
}
@Override
public boolean processMessage(Message message) {
boolean retVal;
- log("S2.processMessage what=" + message.what);
+ tlog("S2.processMessage what=" + message.what);
switch(message.what) {
case(CMD_2):
sendMessage(CMD_4);
@@ -1754,16 +1753,16 @@
class P2 extends State {
@Override
public void enter() {
- log("P2.enter");
+ tlog("P2.enter");
sendMessage(CMD_5);
}
@Override
public void exit() {
- log("P2.exit");
+ tlog("P2.exit");
}
@Override
public boolean processMessage(Message message) {
- log("P2.processMessage what=" + message.what);
+ tlog("P2.processMessage what=" + message.what);
switch(message.what) {
case(CMD_3):
break;
@@ -1779,7 +1778,7 @@
@Override
protected void onHalting() {
- log("halting");
+ tlog("halting");
synchronized (this) {
this.notifyAll();
}
@@ -1852,11 +1851,11 @@
if (DBG) tlog("testStateMachineSharedThread X");
}
- private void tlog(String s) {
+ private static void tlog(String s) {
Log.d(TAG, s);
}
- private void tloge(String s) {
+ private static void tloge(String s) {
Log.e(TAG, s);
}
}
diff --git a/media/jni/soundpool/SoundPool.h b/media/jni/soundpool/SoundPool.h
index 98d2fa2..aff101f 100644
--- a/media/jni/soundpool/SoundPool.h
+++ b/media/jni/soundpool/SoundPool.h
@@ -37,7 +37,7 @@
// for queued events
class SoundPoolEvent {
public:
- SoundPoolEvent(int msg, int arg1=0, int arg2=0) :
+ explicit SoundPoolEvent(int msg, int arg1=0, int arg2=0) :
mMsg(msg), mArg1(arg1), mArg2(arg2) {}
int mMsg;
int mArg1;
diff --git a/media/jni/soundpool/SoundPoolThread.h b/media/jni/soundpool/SoundPoolThread.h
index 9096aeb..7b3e1dd 100644
--- a/media/jni/soundpool/SoundPoolThread.h
+++ b/media/jni/soundpool/SoundPoolThread.h
@@ -40,7 +40,7 @@
*/
class SoundPoolThread {
public:
- SoundPoolThread(SoundPool* SoundPool);
+ explicit SoundPoolThread(SoundPool* SoundPool);
~SoundPoolThread();
void loadSample(int sampleID);
void quit();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/LocalPreferences.java b/packages/DocumentsUI/src/com/android/documentsui/LocalPreferences.java
index b3db037..d2e9885 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/LocalPreferences.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/LocalPreferences.java
@@ -22,7 +22,6 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
import android.os.UserHandle;
import android.preference.PreferenceManager;
@@ -86,15 +85,6 @@
public @interface PermissionStatus {}
/**
- * Clears all preferences associated with a given package.
- *
- * <p>Typically called when a package is removed or when user asked to clear its data.
- */
- static void clearPackagePreferences(Context context, String packageName) {
- clearScopedAccessPreferences(context, packageName);
- }
-
- /**
* Methods below are used to keep track of denied user requests on scoped directory access so
* the dialog is not offered when user checked the 'Do not ask again' box
*
@@ -118,23 +108,6 @@
getPrefs(context).edit().putInt(key, status).apply();
}
- private static void clearScopedAccessPreferences(Context context, String packageName) {
- final String keySubstring = "|" + packageName + "|";
- final SharedPreferences prefs = getPrefs(context);
- Editor editor = null;
- for (final String key : prefs.getAll().keySet()) {
- if (key.contains(keySubstring)) {
- if (editor == null) {
- editor = prefs.edit();
- }
- editor.remove(key);
- }
- }
- if (editor != null) {
- editor.apply();
- }
- }
-
private static String getScopedAccessDenialsKey(String packageName, String uuid,
String directory) {
final int userId = UserHandle.myUserId();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/PackageReceiver.java b/packages/DocumentsUI/src/com/android/documentsui/PackageReceiver.java
index fd1183f..aef63af 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/PackageReceiver.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/PackageReceiver.java
@@ -23,7 +23,7 @@
import android.net.Uri;
/**
- * Cleans up {@link RecentsProvider} and {@link LocalPreferences} when packages are removed.
+ * Clean up {@link RecentsProvider} when packages are removed.
*/
public class PackageReceiver extends BroadcastReceiver {
@Override
@@ -31,19 +31,15 @@
final ContentResolver resolver = context.getContentResolver();
final String action = intent.getAction();
- final Uri data = intent.getData();
- final String packageName = data == null ? null : data.getSchemeSpecificPart();
-
if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) {
resolver.call(RecentsProvider.buildRecent(), RecentsProvider.METHOD_PURGE, null, null);
- if (packageName != null) {
- LocalPreferences.clearPackagePreferences(context, packageName);
- }
+
} else if (Intent.ACTION_PACKAGE_DATA_CLEARED.equals(action)) {
- if (packageName != null) {
+ final Uri data = intent.getData();
+ if (data != null) {
+ final String packageName = data.getSchemeSpecificPart();
resolver.call(RecentsProvider.buildRecent(), RecentsProvider.METHOD_PURGE_PACKAGE,
packageName, null);
- LocalPreferences.clearPackagePreferences(context, packageName);
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java b/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java
index d368de9..151e0ea 100644
--- a/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java
@@ -16,18 +16,11 @@
package com.android.settingslib;
import android.content.Context;
-import android.net.wifi.WifiManager;
import android.os.SystemProperties;
import android.telephony.CarrierConfigManager;
public class TetherUtil {
- public static boolean setWifiTethering(boolean enable, Context context) {
- final WifiManager wifiManager =
- (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- return wifiManager.setWifiApEnabled(null, enable);
- }
-
private static boolean isEntitlementCheckRequired(Context context) {
final CarrierConfigManager configManager = (CarrierConfigManager) context
.getSystemService(Context.CARRIER_CONFIG_SERVICE);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 380fcd4..0a3f0c0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -36,6 +36,7 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.support.annotation.NonNull;
import android.text.Spannable;
@@ -43,12 +44,13 @@
import android.text.TextUtils;
import android.text.style.TtsSpan;
import android.util.Log;
-import android.util.LruCache;
import com.android.settingslib.R;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
public class AccessPoint implements Comparable<AccessPoint> {
@@ -81,7 +83,9 @@
* For now this data is used only with Verbose Logging so as to show the band and number
* of BSSIDs on which that network is seen.
*/
- public LruCache<String, ScanResult> mScanResultCache = new LruCache<String, ScanResult>(32);
+ private final ConcurrentHashMap<String, ScanResult> mScanResultCache =
+ new ConcurrentHashMap<String, ScanResult>(32);
+ private static final long MAX_SCAN_RESULT_AGE_MS = 15000;
private static final String KEY_NETWORKINFO = "key_networkinfo";
private static final String KEY_WIFIINFO = "key_wifiinfo";
@@ -149,7 +153,7 @@
if (savedState.containsKey(KEY_SCANRESULTCACHE)) {
ArrayList<ScanResult> scanResultArrayList =
savedState.getParcelableArrayList(KEY_SCANRESULTCACHE);
- mScanResultCache.evictAll();
+ mScanResultCache.clear();
for (ScanResult result : scanResultArrayList) {
mScanResultCache.put(result.BSSID, result);
}
@@ -233,6 +237,17 @@
return builder.append(')').toString();
}
+ private void evictOldScanResults() {
+ long nowMs = SystemClock.elapsedRealtime();
+ for (Iterator<ScanResult> iter = mScanResultCache.values().iterator(); iter.hasNext(); ) {
+ ScanResult result = iter.next();
+ // result timestamp is in microseconds
+ if (nowMs - result.timestamp / 1000 > MAX_SCAN_RESULT_AGE_MS) {
+ iter.remove();
+ }
+ }
+ }
+
public boolean matches(ScanResult result) {
return ssid.equals(result.SSID) && security == getSecurity(result);
}
@@ -268,8 +283,9 @@
}
public int getRssi() {
+ evictOldScanResults();
int rssi = Integer.MIN_VALUE;
- for (ScanResult result : mScanResultCache.snapshot().values()) {
+ for (ScanResult result : mScanResultCache.values()) {
if (result.level > rssi) {
rssi = result.level;
}
@@ -279,8 +295,9 @@
}
public long getSeen() {
+ evictOldScanResults();
long seen = 0;
- for (ScanResult result : mScanResultCache.snapshot().values()) {
+ for (ScanResult result : mScanResultCache.values()) {
if (result.timestamp > seen) {
seen = result.timestamp;
}
@@ -505,9 +522,9 @@
int numBlackListed = 0;
int n24 = 0; // Number scan results we included in the string
int n5 = 0; // Number scan results we included in the string
- Map<String, ScanResult> list = mScanResultCache.snapshot();
+ evictOldScanResults();
// TODO: sort list by RSSI or age
- for (ScanResult result : list.values()) {
+ for (ScanResult result : mScanResultCache.values()) {
if (result.frequency >= LOWER_FREQ_5GHZ
&& result.frequency <= HIGHER_FREQ_5GHZ) {
@@ -684,8 +701,9 @@
savedState.putInt(KEY_PSKTYPE, pskType);
if (mConfig != null) savedState.putParcelable(KEY_CONFIG, mConfig);
savedState.putParcelable(KEY_WIFIINFO, mInfo);
+ evictOldScanResults();
savedState.putParcelableArrayList(KEY_SCANRESULTCACHE,
- new ArrayList<ScanResult>(mScanResultCache.snapshot().values()));
+ new ArrayList<ScanResult>(mScanResultCache.values()));
if (mNetworkInfo != null) {
savedState.putParcelable(KEY_NETWORKINFO, mNetworkInfo);
}
@@ -697,9 +715,6 @@
boolean update(ScanResult result) {
if (matches(result)) {
- /* Update the LRU timestamp, if BSSID exists */
- mScanResultCache.get(result.BSSID);
-
/* Add or update the scan result for the BSSID */
mScanResultCache.put(result.BSSID, result);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index f8fb1e5..920df2e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -28,12 +28,9 @@
import android.net.NetworkPolicyManager;
import android.net.Uri;
import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.net.wifi.WifiManager;
-import android.os.FileUtils;
import android.os.Handler;
import android.os.ParcelFileDescriptor;
-import android.os.Process;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.BackupUtils;
@@ -41,34 +38,19 @@
import com.android.internal.widget.LockPatternUtils;
-import libcore.io.IoUtils;
-
import java.io.BufferedOutputStream;
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
-import java.io.CharArrayReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.FileWriter;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.zip.CRC32;
/**
@@ -86,10 +68,11 @@
private static final String KEY_LOCK_SETTINGS = "lock_settings";
private static final String KEY_SOFTAP_CONFIG = "softap_config";
private static final String KEY_NETWORK_POLICIES = "network_policies";
+ private static final String KEY_WIFI_NEW_CONFIG = "wifi_new_config";
// Versioning of the state file. Increment this version
// number any time the set of state items is altered.
- private static final int STATE_VERSION = 6;
+ private static final int STATE_VERSION = 7;
// Versioning of the Network Policies backup payload.
private static final int NETWORK_POLICIES_BACKUP_VERSION = 1;
@@ -106,8 +89,9 @@
private static final int STATE_LOCK_SETTINGS = 6;
private static final int STATE_SOFTAP_CONFIG = 7;
private static final int STATE_NETWORK_POLICIES = 8;
+ private static final int STATE_WIFI_NEW_CONFIG = 9;
- private static final int STATE_SIZE = 9; // The current number of state items
+ private static final int STATE_SIZE = 10; // The current number of state items
// Number of entries in the checksum array at various version numbers
private static final int STATE_SIZES[] = {
@@ -117,16 +101,18 @@
6, // version 3 added STATE_GLOBAL
7, // version 4 added STATE_LOCK_SETTINGS
8, // version 5 added STATE_SOFTAP_CONFIG
- STATE_SIZE // version 6 added STATE_NETWORK_POLICIES
+ 9, // version 6 added STATE_NETWORK_POLICIES
+ STATE_SIZE // version 7 added STATE_WIFI_NEW_CONFIG
};
// Versioning of the 'full backup' format
// Increment this version any time a new item is added
- private static final int FULL_BACKUP_VERSION = 5;
+ private static final int FULL_BACKUP_VERSION = 6;
private static final int FULL_BACKUP_ADDED_GLOBAL = 2; // added the "global" entry
private static final int FULL_BACKUP_ADDED_LOCK_SETTINGS = 3; // added the "lock_settings" entry
private static final int FULL_BACKUP_ADDED_SOFTAP_CONF = 4; //added the "softap_config" entry
private static final int FULL_BACKUP_ADDED_NETWORK_POLICIES = 5; //added "network_policies"
+ private static final int FULL_BACKUP_ADDED_WIFI_NEW = 6; // added "wifi_new_config" entry
private static final int INTEGER_BYTE_COUNT = Integer.SIZE / Byte.SIZE;
@@ -139,10 +125,6 @@
Settings.NameValueTable.VALUE
};
- private static final String FILE_WIFI_SUPPLICANT = "/data/misc/wifi/wpa_supplicant.conf";
- private static final String FILE_WIFI_SUPPLICANT_TEMPLATE =
- "/system/etc/wifi/wpa_supplicant.conf";
-
// the key to store the WIFI data under, should be sorted as last, so restore happens last.
// use very late unicode character to quasi-guarantee last sort position.
private static final String KEY_WIFI_SUPPLICANT = "\uffedWIFI";
@@ -156,259 +138,17 @@
// stored in the full-backup tarfile as well, so should not be changed.
private static final String STAGE_FILE = "flattened-data";
- // Delay in milliseconds between the restore operation and when we will bounce
- // wifi in order to rewrite the supplicant config etc.
- private static final long WIFI_BOUNCE_DELAY_MILLIS = 60 * 1000; // one minute
-
private SettingsHelper mSettingsHelper;
- private WifiManager mWfm;
- private String mWifiConfigFile;
- // Chain of asynchronous operations used when rewriting the wifi supplicant config file
- WifiDisableRunnable mWifiDisable = null;
- WifiRestoreRunnable mWifiRestore = null;
- int mRetainedWifiState; // used only during config file rewrite
-
- // Class for capturing a network definition from the wifi supplicant config file
- static class Network {
- String ssid = ""; // equals() and hashCode() need these to be non-null
- String key_mgmt = "";
- boolean certUsed = false;
- boolean hasWepKey = false;
- boolean isEap = false;
- final ArrayList<String> rawLines = new ArrayList<String>();
-
- public static Network readFromStream(BufferedReader in) {
- final Network n = new Network();
- String line;
- try {
- while (in.ready()) {
- line = in.readLine();
- if (line == null || line.startsWith("}")) {
- break;
- }
- n.rememberLine(line);
- }
- } catch (IOException e) {
- return null;
- }
- return n;
- }
-
- void rememberLine(String line) {
- // can't rely on particular whitespace patterns so strip leading/trailing
- line = line.trim();
- if (line.isEmpty()) return; // only whitespace; drop the line
- rawLines.add(line);
-
- // remember the ssid and key_mgmt lines for duplicate culling
- if (line.startsWith("ssid=")) {
- ssid = line;
- } else if (line.startsWith("key_mgmt=")) {
- key_mgmt = line;
- if (line.contains("EAP")) {
- isEap = true;
- }
- } else if (line.startsWith("client_cert=")) {
- certUsed = true;
- } else if (line.startsWith("ca_cert=")) {
- certUsed = true;
- } else if (line.startsWith("ca_path=")) {
- certUsed = true;
- } else if (line.startsWith("wep_")) {
- hasWepKey = true;
- } else if (line.startsWith("eap=")) {
- isEap = true;
- }
- }
-
- public void write(Writer w) throws IOException {
- w.write("\nnetwork={\n");
- for (String line : rawLines) {
- w.write("\t" + line + "\n");
- }
- w.write("}\n");
- }
-
- public void dump() {
- Log.v(TAG, "network={");
- for (String line : rawLines) {
- Log.v(TAG, " " + line);
- }
- Log.v(TAG, "}");
- }
-
- // Calculate the equivalent of WifiConfiguration's configKey()
- public String configKey() {
- if (ssid == null) {
- // No SSID => malformed network definition
- return null;
- }
-
- final String bareSsid = ssid.substring(ssid.indexOf('=') + 1);
-
- final BitSet types = new BitSet();
- if (key_mgmt == null) {
- // no key_mgmt specified; this is defined as equivalent to "WPA-PSK WPA-EAP"
- types.set(KeyMgmt.WPA_PSK);
- types.set(KeyMgmt.WPA_EAP);
- } else {
- // Need to parse the key_mgmt line
- final String bareKeyMgmt = key_mgmt.substring(key_mgmt.indexOf('=') + 1);
- String[] typeStrings = bareKeyMgmt.split("\\s+");
-
- // Parse out all the key management regimes permitted for this network. The literal
- // strings here are the standard values permitted in wpa_supplicant.conf.
- for (int i = 0; i < typeStrings.length; i++) {
- final String ktype = typeStrings[i];
- if (ktype.equals("WPA-PSK")) {
- Log.v(TAG, " + setting WPA_PSK bit");
- types.set(KeyMgmt.WPA_PSK);
- } else if (ktype.equals("WPA-EAP")) {
- Log.v(TAG, " + setting WPA_EAP bit");
- types.set(KeyMgmt.WPA_EAP);
- } else if (ktype.equals("IEEE8021X")) {
- Log.v(TAG, " + setting IEEE8021X bit");
- types.set(KeyMgmt.IEEE8021X);
- }
- }
- }
-
- // Now build the canonical config key paralleling the WifiConfiguration semantics
- final String key;
- if (types.get(KeyMgmt.WPA_PSK)) {
- key = bareSsid + KeyMgmt.strings[KeyMgmt.WPA_PSK];
- } else if (types.get(KeyMgmt.WPA_EAP) || types.get(KeyMgmt.IEEE8021X)) {
- key = bareSsid + KeyMgmt.strings[KeyMgmt.WPA_EAP];
- } else if (hasWepKey) {
- key = bareSsid + "WEP"; // hardcoded this way in WifiConfiguration
- } else {
- key = bareSsid + KeyMgmt.strings[KeyMgmt.NONE];
- }
- return key;
- }
-
- // Same approach as Pair.equals() and Pair.hashCode()
- @Override
- public boolean equals(Object o) {
- if (o == this) return true;
- if (!(o instanceof Network)) return false;
- final Network other;
- try {
- other = (Network) o;
- } catch (ClassCastException e) {
- return false;
- }
- return ssid.equals(other.ssid) && key_mgmt.equals(other.key_mgmt);
- }
-
- @Override
- public int hashCode() {
- int result = 17;
- result = 31 * result + ssid.hashCode();
- result = 31 * result + key_mgmt.hashCode();
- return result;
- }
- }
-
- boolean networkInWhitelist(Network net, List<WifiConfiguration> whitelist) {
- final String netConfigKey = net.configKey();
- final int N = whitelist.size();
- for (int i = 0; i < N; i++) {
- if (Objects.equals(netConfigKey, whitelist.get(i).configKey(true))) {
- return true;
- }
- }
- return false;
- }
-
- // Ingest multiple wifi config file fragments, looking for network={} blocks
- // and eliminating duplicates
- class WifiNetworkSettings {
- // One for fast lookup, one for maintaining ordering
- final HashSet<Network> mKnownNetworks = new HashSet<Network>();
- final ArrayList<Network> mNetworks = new ArrayList<Network>(8);
-
- public void readNetworks(BufferedReader in, List<WifiConfiguration> whitelist,
- boolean acceptEapNetworks) {
- try {
- String line;
- while (in.ready()) {
- line = in.readLine();
- if (line != null) {
- // Parse out 'network=' decls so we can ignore duplicates
- if (line.startsWith("network")) {
- Network net = Network.readFromStream(in);
- if (whitelist != null) {
- if (!networkInWhitelist(net, whitelist)) {
- if (DEBUG_BACKUP) {
- Log.v(TAG, "Network not in whitelist, skipping: "
- + net.ssid + " / " + net.key_mgmt);
- }
- continue;
- }
- }
- // Don't propagate EAP network definitions
- if (net.isEap && !acceptEapNetworks) {
- if (DEBUG_BACKUP) {
- Log.v(TAG, "Skipping EAP network " + net.ssid + " / " + net.key_mgmt);
- }
- continue;
- }
- if (!mKnownNetworks.contains(net)) {
- if (DEBUG_BACKUP) {
- Log.v(TAG, "Adding " + net.ssid + " / " + net.key_mgmt);
- }
- mKnownNetworks.add(net);
- mNetworks.add(net);
- } else {
- if (DEBUG_BACKUP) {
- Log.v(TAG, "Dupe; skipped " + net.ssid + " / " + net.key_mgmt);
- }
- }
- }
- }
- }
- } catch (IOException e) {
- // whatever happened, we're done now
- }
- }
-
- public void write(Writer w) throws IOException {
- for (Network net : mNetworks) {
- if (net.certUsed) {
- // Networks that use certificates for authentication can't be restored
- // because the certificates they need don't get restored (because they
- // are stored in keystore, and can't be restored)
- continue;
- }
-
- if (net.isEap) {
- // Similarly, omit EAP network definitions to avoid propagating
- // controlled enterprise network definitions.
- continue;
- }
-
- net.write(w);
- }
- }
-
- public void dump() {
- for (Network net : mNetworks) {
- net.dump();
- }
- }
- }
+ private WifiManager mWifiManager;
@Override
public void onCreate() {
if (DEBUG_BACKUP) Log.d(TAG, "onCreate() invoked");
mSettingsHelper = new SettingsHelper(this);
+ mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
super.onCreate();
-
- WifiManager mWfm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
- if (mWfm != null) mWifiConfigFile = mWfm.getConfigFile();
}
@Override
@@ -420,10 +160,9 @@
byte[] globalSettingsData = getGlobalSettings();
byte[] lockSettingsData = getLockSettings();
byte[] locale = mSettingsHelper.getLocaleData();
- byte[] wifiSupplicantData = getWifiSupplicant(FILE_WIFI_SUPPLICANT);
- byte[] wifiConfigData = getFileData(mWifiConfigFile);
byte[] softApConfigData = getSoftAPConfiguration();
byte[] netPoliciesData = getNetworkPolicies();
+ byte[] wifiFullConfigData = getNewWifiConfigData();
long[] stateChecksums = readOldChecksums(oldState);
@@ -435,12 +174,8 @@
writeIfChanged(stateChecksums[STATE_GLOBAL], KEY_GLOBAL, globalSettingsData, data);
stateChecksums[STATE_LOCALE] =
writeIfChanged(stateChecksums[STATE_LOCALE], KEY_LOCALE, locale, data);
- stateChecksums[STATE_WIFI_SUPPLICANT] =
- writeIfChanged(stateChecksums[STATE_WIFI_SUPPLICANT], KEY_WIFI_SUPPLICANT,
- wifiSupplicantData, data);
- stateChecksums[STATE_WIFI_CONFIG] =
- writeIfChanged(stateChecksums[STATE_WIFI_CONFIG], KEY_WIFI_CONFIG, wifiConfigData,
- data);
+ stateChecksums[STATE_WIFI_SUPPLICANT] = 0;
+ stateChecksums[STATE_WIFI_CONFIG] = 0;
stateChecksums[STATE_LOCK_SETTINGS] =
writeIfChanged(stateChecksums[STATE_LOCK_SETTINGS], KEY_LOCK_SETTINGS,
lockSettingsData, data);
@@ -450,112 +185,13 @@
stateChecksums[STATE_NETWORK_POLICIES] =
writeIfChanged(stateChecksums[STATE_NETWORK_POLICIES], KEY_NETWORK_POLICIES,
netPoliciesData, data);
+ stateChecksums[STATE_WIFI_NEW_CONFIG] =
+ writeIfChanged(stateChecksums[STATE_WIFI_NEW_CONFIG], KEY_WIFI_NEW_CONFIG,
+ wifiFullConfigData, data);
writeNewChecksums(stateChecksums, newState);
}
- class WifiDisableRunnable implements Runnable {
- final WifiRestoreRunnable mNextPhase;
-
- public WifiDisableRunnable(WifiRestoreRunnable next) {
- mNextPhase = next;
- }
-
- @Override
- public void run() {
- if (DEBUG_BACKUP) {
- Log.v(TAG, "Disabling wifi during restore");
- }
- final ContentResolver cr = getContentResolver();
- final int scanAlways = Settings.Global.getInt(cr,
- Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0);
- final int retainedWifiState = enableWifi(false);
- if (scanAlways != 0) {
- Settings.Global.putInt(cr,
- Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0);
- }
-
- // Tell the final stage how to clean up after itself
- mNextPhase.setPriorState(retainedWifiState, scanAlways);
-
- // And run it after a modest pause to give broadcasts and content
- // observers time an opportunity to run on this looper thread, so
- // that the wifi stack actually goes all the way down.
- new Handler(getMainLooper()).postDelayed(mNextPhase, 2500);
- }
- }
-
- class WifiRestoreRunnable implements Runnable {
- private byte[] restoredSupplicantData;
- private byte[] restoredWifiConfigFile;
- private int retainedWifiState; // provided by disable stage
- private int scanAlways; // provided by disable stage
-
- void setPriorState(int retainedState, int always) {
- retainedWifiState = retainedState;
- scanAlways = always;
- }
-
- void incorporateWifiSupplicant(BackupDataInput data) {
- restoredSupplicantData = new byte[data.getDataSize()];
- if (restoredSupplicantData.length <= 0) return;
- try {
- data.readEntityData(restoredSupplicantData, 0, data.getDataSize());
- } catch (IOException e) {
- Log.w(TAG, "Unable to read supplicant data");
- restoredSupplicantData = null;
- }
- }
-
- void incorporateWifiConfigFile(BackupDataInput data) {
- restoredWifiConfigFile = new byte[data.getDataSize()];
- if (restoredWifiConfigFile.length <= 0) return;
- try {
- data.readEntityData(restoredWifiConfigFile, 0, data.getDataSize());
- } catch (IOException e) {
- Log.w(TAG, "Unable to read config file");
- restoredWifiConfigFile = null;
- }
- }
-
- @Override
- public void run() {
- if (restoredSupplicantData != null || restoredWifiConfigFile != null) {
- if (DEBUG_BACKUP) {
- Log.v(TAG, "Applying restored wifi data");
- }
- if (restoredSupplicantData != null) {
- restoreWifiSupplicant(FILE_WIFI_SUPPLICANT,
- restoredSupplicantData, restoredSupplicantData.length);
- FileUtils.setPermissions(FILE_WIFI_SUPPLICANT,
- FileUtils.S_IRUSR | FileUtils.S_IWUSR
- | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
- Process.myUid(), Process.WIFI_UID);
- }
- if (restoredWifiConfigFile != null) {
- restoreFileData(mWifiConfigFile,
- restoredWifiConfigFile, restoredWifiConfigFile.length);
- }
- // restore the previous WIFI state.
- if (scanAlways != 0) {
- Settings.Global.putInt(getContentResolver(),
- Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, scanAlways);
- }
- enableWifi(retainedWifiState == WifiManager.WIFI_STATE_ENABLED
- || retainedWifiState == WifiManager.WIFI_STATE_ENABLING);
- }
- }
- }
-
- // Instantiate the wifi-config restore runnable, scheduling it for execution
- // a minute hence
- void initWifiRestoreIfNecessary() {
- if (mWifiRestore == null) {
- mWifiRestore = new WifiRestoreRunnable();
- mWifiDisable = new WifiDisableRunnable(mWifiRestore);
- }
- }
-
@Override
public void onRestore(BackupDataInput data, int appVersionCode,
ParcelFileDescriptor newState) throws IOException {
@@ -563,6 +199,8 @@
HashSet<String> movedToGlobal = new HashSet<String>();
Settings.System.getMovedToGlobalSettings(movedToGlobal);
Settings.Secure.getMovedToGlobalSettings(movedToGlobal);
+ byte[] restoredWifiSupplicantData = null;
+ byte[] restoredWifiIpConfigData = null;
while (data.readNextHeader()) {
final String key = data.getKey();
@@ -582,8 +220,8 @@
break;
case KEY_WIFI_SUPPLICANT :
- initWifiRestoreIfNecessary();
- mWifiRestore.incorporateWifiSupplicant(data);
+ restoredWifiSupplicantData = new byte[size];
+ data.readEntityData(restoredWifiSupplicantData, 0, size);
break;
case KEY_LOCALE :
@@ -593,8 +231,8 @@
break;
case KEY_WIFI_CONFIG :
- initWifiRestoreIfNecessary();
- mWifiRestore.incorporateWifiConfigFile(data);
+ restoredWifiIpConfigData = new byte[size];
+ data.readEntityData(restoredWifiIpConfigData, 0, size);
break;
case KEY_LOCK_SETTINGS :
@@ -613,23 +251,22 @@
restoreNetworkPolicies(netPoliciesData);
break;
+ case KEY_WIFI_NEW_CONFIG:
+ byte[] restoredWifiNewConfigData = new byte[size];
+ data.readEntityData(restoredWifiNewConfigData, 0, size);
+ restoreNewWifiConfigData(restoredWifiNewConfigData);
+ break;
+
default :
data.skipEntityData();
}
}
- // If we have wifi data to restore, post a runnable to perform the
- // bounce-and-update operation a little ways in the future. The
- // 'disable' runnable brings down the stack and remembers its state,
- // and in turn schedules the 'restore' runnable to do the rewrite
- // and cleanup operations.
- if (mWifiRestore != null) {
- long wifiBounceDelayMillis = Settings.Global.getLong(
- getContentResolver(),
- Settings.Global.WIFI_BOUNCE_DELAY_OVERRIDE_MS,
- WIFI_BOUNCE_DELAY_MILLIS);
- new Handler(getMainLooper()).postDelayed(mWifiDisable, wifiBounceDelayMillis);
+ // Do this at the end so that we also pull in the ipconfig data.
+ if (restoredWifiSupplicantData != null) {
+ restoreSupplicantWifiConfigData(
+ restoredWifiSupplicantData, restoredWifiIpConfigData);
}
}
@@ -640,10 +277,9 @@
byte[] globalSettingsData = getGlobalSettings();
byte[] lockSettingsData = getLockSettings();
byte[] locale = mSettingsHelper.getLocaleData();
- byte[] wifiSupplicantData = getWifiSupplicant(FILE_WIFI_SUPPLICANT);
- byte[] wifiConfigData = getFileData(mWifiConfigFile);
byte[] softApConfigData = getSoftAPConfiguration();
byte[] netPoliciesData = getNetworkPolicies();
+ byte[] wifiFullConfigData = getNewWifiConfigData();
// Write the data to the staging file, then emit that as our tarfile
// representation of the backed-up settings.
@@ -673,14 +309,6 @@
if (DEBUG_BACKUP) Log.d(TAG, locale.length + " bytes of locale data");
out.writeInt(locale.length);
out.write(locale);
- if (DEBUG_BACKUP) {
- Log.d(TAG, wifiSupplicantData.length + " bytes of wifi supplicant data");
- }
- out.writeInt(wifiSupplicantData.length);
- out.write(wifiSupplicantData);
- if (DEBUG_BACKUP) Log.d(TAG, wifiConfigData.length + " bytes of wifi config data");
- out.writeInt(wifiConfigData.length);
- out.write(wifiConfigData);
if (DEBUG_BACKUP) Log.d(TAG, lockSettingsData.length + " bytes of lock settings data");
out.writeInt(lockSettingsData.length);
out.write(lockSettingsData);
@@ -690,6 +318,11 @@
if (DEBUG_BACKUP) Log.d(TAG, netPoliciesData.length + " bytes of net policies data");
out.writeInt(netPoliciesData.length);
out.write(netPoliciesData);
+ if (DEBUG_BACKUP) {
+ Log.d(TAG, wifiFullConfigData.length + " bytes of wifi config data");
+ }
+ out.writeInt(wifiFullConfigData.length);
+ out.write(wifiFullConfigData);
out.flush(); // also flushes downstream
@@ -750,27 +383,21 @@
in.readFully(buffer, 0, nBytes);
mSettingsHelper.setLocaleData(buffer, nBytes);
- // wifi supplicant
- nBytes = in.readInt();
- if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of wifi supplicant data");
- if (nBytes > buffer.length) buffer = new byte[nBytes];
- in.readFully(buffer, 0, nBytes);
- int retainedWifiState = enableWifi(false);
- restoreWifiSupplicant(FILE_WIFI_SUPPLICANT, buffer, nBytes);
- FileUtils.setPermissions(FILE_WIFI_SUPPLICANT,
- FileUtils.S_IRUSR | FileUtils.S_IWUSR
- | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
- Process.myUid(), Process.WIFI_UID);
- // retain the previous WIFI state.
- enableWifi(retainedWifiState == WifiManager.WIFI_STATE_ENABLED
- || retainedWifiState == WifiManager.WIFI_STATE_ENABLING);
+ // Restore older backups performing the necessary migrations.
+ if (version < FULL_BACKUP_ADDED_WIFI_NEW) {
+ // wifi supplicant
+ int supplicant_size = in.readInt();
+ if (DEBUG_BACKUP) Log.d(TAG, supplicant_size + " bytes of wifi supplicant data");
+ byte[] supplicant_buffer = new byte[supplicant_size];
+ in.readFully(supplicant_buffer, 0, supplicant_size);
- // wifi config
- nBytes = in.readInt();
- if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of wifi config data");
- if (nBytes > buffer.length) buffer = new byte[nBytes];
- in.readFully(buffer, 0, nBytes);
- restoreFileData(mWifiConfigFile, buffer, nBytes);
+ // ip config
+ int ipconfig_size = in.readInt();
+ if (DEBUG_BACKUP) Log.d(TAG, ipconfig_size + " bytes of ip config data");
+ byte[] ipconfig_buffer = new byte[ipconfig_size];
+ in.readFully(ipconfig_buffer, 0, nBytes);
+ restoreSupplicantWifiConfigData(supplicant_buffer, ipconfig_buffer);
+ }
if (version >= FULL_BACKUP_ADDED_LOCK_SETTINGS) {
nBytes = in.readInt();
@@ -801,6 +428,15 @@
restoreNetworkPolicies(buffer);
}
}
+ // Restore full wifi config data
+ if (version >= FULL_BACKUP_ADDED_WIFI_NEW) {
+ nBytes = in.readInt();
+ if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of full wifi config data");
+ if (nBytes > buffer.length) buffer = new byte[nBytes];
+ in.readFully(buffer, 0, nBytes);
+ restoreNewWifiConfigData(buffer);
+ }
+
if (DEBUG_BACKUP) Log.d(TAG, "Full restore complete.");
} else {
data.close();
@@ -1114,146 +750,16 @@
return result;
}
- private byte[] getFileData(String filename) {
- InputStream is = null;
- try {
- File file = new File(filename);
- is = new FileInputStream(file);
-
- //Will truncate read on a very long file,
- //should not happen for a config file
- byte[] bytes = new byte[(int) file.length()];
-
- int offset = 0;
- int numRead = 0;
- while (offset < bytes.length
- && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
- offset += numRead;
- }
-
- //read failure
- if (offset < bytes.length) {
- Log.w(TAG, "Couldn't backup " + filename);
- return EMPTY_DATA;
- }
- return bytes;
- } catch (IOException ioe) {
- Log.w(TAG, "Couldn't backup " + filename);
- return EMPTY_DATA;
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- }
- }
+ private void restoreSupplicantWifiConfigData(byte[] supplicant_bytes, byte[] ipconfig_bytes) {
+ if (DEBUG_BACKUP) {
+ Log.v(TAG, "Applying restored supplicant wifi data");
}
- }
-
- private void restoreFileData(String filename, byte[] bytes, int size) {
- try {
- File file = new File(filename);
- if (file.exists()) file.delete();
-
- OutputStream os = new BufferedOutputStream(new FileOutputStream(filename, true));
- os.write(bytes, 0, size);
- os.close();
- } catch (IOException ioe) {
- Log.w(TAG, "Couldn't restore " + filename);
- }
- }
-
-
- private byte[] getWifiSupplicant(String filename) {
- BufferedReader br = null;
- try {
- File file = new File(filename);
- if (!file.exists()) {
- return EMPTY_DATA;
- }
-
- WifiManager wifi = (WifiManager) getSystemService(WIFI_SERVICE);
- List<WifiConfiguration> configs = wifi.getConfiguredNetworks();
-
- WifiNetworkSettings fromFile = new WifiNetworkSettings();
- br = new BufferedReader(new FileReader(file));
- fromFile.readNetworks(br, configs, false);
-
- // Write the parsed networks into a packed byte array
- if (fromFile.mKnownNetworks.size() > 0) {
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- OutputStreamWriter out = new OutputStreamWriter(bos);
- fromFile.write(out);
- out.flush();
- return bos.toByteArray();
- } else {
- return EMPTY_DATA;
- }
- } catch (IOException ioe) {
- Log.w(TAG, "Couldn't backup " + filename);
- return EMPTY_DATA;
- } finally {
- IoUtils.closeQuietly(br);
- }
- }
-
- private void restoreWifiSupplicant(String filename, byte[] bytes, int size) {
- try {
- WifiNetworkSettings supplicantImage = new WifiNetworkSettings();
-
- File supplicantFile = new File(FILE_WIFI_SUPPLICANT);
- if (supplicantFile.exists()) {
- // Retain the existing APs; we'll append the restored ones to them
- BufferedReader in = new BufferedReader(new FileReader(FILE_WIFI_SUPPLICANT));
- supplicantImage.readNetworks(in, null, true);
- in.close();
-
- supplicantFile.delete();
- }
-
- // Incorporate the restore AP information
- if (size > 0) {
- char[] restoredAsBytes = new char[size];
- for (int i = 0; i < size; i++) restoredAsBytes[i] = (char) bytes[i];
- BufferedReader in = new BufferedReader(new CharArrayReader(restoredAsBytes));
- supplicantImage.readNetworks(in, null, false);
-
- if (DEBUG_BACKUP) {
- Log.v(TAG, "Final AP list:");
- supplicantImage.dump();
- }
- }
-
- // Install the correct default template
- BufferedWriter bw = new BufferedWriter(new FileWriter(FILE_WIFI_SUPPLICANT));
- copyWifiSupplicantTemplate(bw);
-
- // Write the restored supplicant config and we're done
- supplicantImage.write(bw);
- bw.close();
- } catch (IOException ioe) {
- Log.w(TAG, "Couldn't restore " + filename);
- }
- }
-
- private void copyWifiSupplicantTemplate(BufferedWriter bw) {
- try {
- BufferedReader br = new BufferedReader(new FileReader(FILE_WIFI_SUPPLICANT_TEMPLATE));
- char[] temp = new char[1024];
- int size;
- while ((size = br.read(temp)) > 0) {
- bw.write(temp, 0, size);
- }
- br.close();
- } catch (IOException ioe) {
- Log.w(TAG, "Couldn't copy wpa_supplicant file");
- }
+ mWifiManager.restoreSupplicantBackupData(supplicant_bytes, ipconfig_bytes);
}
private byte[] getSoftAPConfiguration() {
- WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
try {
- return wifiManager.getWifiApConfiguration().getBytesForBackup();
+ return mWifiManager.getWifiApConfiguration().getBytesForBackup();
} catch (IOException ioe) {
Log.e(TAG, "Failed to marshal SoftAPConfiguration" + ioe.getMessage());
return new byte[0];
@@ -1261,12 +767,11 @@
}
private void restoreSoftApConfiguration(byte[] data) {
- WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
try {
WifiConfiguration config = WifiConfiguration
.getWifiConfigFromBackup(new DataInputStream(new ByteArrayInputStream(data)));
if (DEBUG) Log.d(TAG, "Successfully unMarshaled WifiConfiguration ");
- wifiManager.setWifiApConfiguration(config);
+ mWifiManager.setWifiApConfiguration(config);
} catch (IOException | BackupUtils.BadVersionException e) {
Log.e(TAG, "Failed to unMarshal SoftAPConfiguration " + e.getMessage());
}
@@ -1300,6 +805,17 @@
return baos.toByteArray();
}
+ private byte[] getNewWifiConfigData() {
+ return mWifiManager.retrieveBackupData();
+ }
+
+ private void restoreNewWifiConfigData(byte[] bytes) {
+ if (DEBUG_BACKUP) {
+ Log.v(TAG, "Applying restored wifi data");
+ }
+ mWifiManager.restoreBackupData(bytes);
+ }
+
private void restoreNetworkPolicies(byte[] data) {
NetworkPolicyManager networkPolicyManager =
(NetworkPolicyManager) getSystemService(NETWORK_POLICY_SERVICE);
@@ -1358,18 +874,4 @@
| ((in[pos + 3] & 0xFF) << 0);
return result;
}
-
- private int enableWifi(boolean enable) {
- if (mWfm == null) {
- mWfm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
- }
- if (mWfm != null) {
- int state = mWfm.getWifiState();
- mWfm.setWifiEnabled(enable);
- return state;
- } else {
- Log.e(TAG, "Failed to fetch WifiManager instance");
- }
- return WifiManager.WIFI_STATE_UNKNOWN;
- }
}
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index f236877..d3d7331 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -216,7 +216,7 @@
private CountDownLatch mConnectedSignal = new CountDownLatch(1);
private final RemoteCallbackList<INetworkManagementEventObserver> mObservers =
- new RemoteCallbackList<INetworkManagementEventObserver>();
+ new RemoteCallbackList<>();
private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory();
@@ -289,7 +289,7 @@
private int mLastPowerStateFromWifi = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
private final RemoteCallbackList<INetworkActivityListener> mNetworkActivityListeners =
- new RemoteCallbackList<INetworkActivityListener>();
+ new RemoteCallbackList<>();
private boolean mNetworkActive;
/**
@@ -1161,7 +1161,7 @@
private ArrayList<String> readRouteList(String filename) {
FileInputStream fstream = null;
- ArrayList<String> list = new ArrayList<String>();
+ ArrayList<String> list = new ArrayList<>();
try {
fstream = new FileInputStream(filename);
@@ -1285,7 +1285,7 @@
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
}
- List<RouteInfo> routes = new ArrayList<RouteInfo>();
+ List<RouteInfo> routes = new ArrayList<>();
// The RouteInfo constructor truncates the LinkAddress to a network prefix, thus making it
// suitable to use as a route destination.
routes.add(new RouteInfo(getInterfaceConfig(iface).getLinkAddress(), null, iface));
@@ -1345,7 +1345,7 @@
}
private List<InterfaceAddress> excludeLinkLocal(List<InterfaceAddress> addresses) {
- ArrayList<InterfaceAddress> filtered = new ArrayList<InterfaceAddress>(addresses.size());
+ ArrayList<InterfaceAddress> filtered = new ArrayList<>(addresses.size());
for (InterfaceAddress ia : addresses) {
if (!ia.getAddress().isLinkLocalAddress())
filtered.add(ia);
@@ -1458,122 +1458,6 @@
}
}
- /**
- * Private method used to call execute for a command given the provided arguments.
- *
- * This function checks the returned NativeDaemonEvent for the provided expected response code
- * and message. If either of these is not correct, an error is logged.
- *
- * @param String command The command to execute.
- * @param Object[] args If needed, arguments for the command to execute.
- * @param int expectedResponseCode The code expected to be returned in the corresponding event.
- * @param String expectedResponseMessage The message expected in the returned event.
- * @param String logMsg The message to log as an error (TAG will be applied).
- */
- private void executeOrLogWithMessage(String command, Object[] args,
- int expectedResponseCode, String expectedResponseMessage, String logMsg)
- throws NativeDaemonConnectorException {
- NativeDaemonEvent event = mConnector.execute(command, args);
- if (event.getCode() != expectedResponseCode
- || !event.getMessage().equals(expectedResponseMessage)) {
- Log.e(TAG, logMsg + ": event = " + event);
- }
- }
-
- @Override
- public void startAccessPoint(WifiConfiguration wifiConfig, String wlanIface) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- Object[] args;
- String logMsg = "startAccessPoint Error setting up softap";
- try {
- if (wifiConfig == null) {
- args = new Object[] {"set", wlanIface};
- } else {
- args = new Object[] {"set", wlanIface, wifiConfig.SSID,
- "broadcast", Integer.toString(wifiConfig.apChannel),
- getSecurityType(wifiConfig), new SensitiveArg(wifiConfig.preSharedKey)};
- }
- executeOrLogWithMessage(SOFT_AP_COMMAND, args, NetdResponseCode.SoftapStatusResult,
- SOFT_AP_COMMAND_SUCCESS, logMsg);
-
- logMsg = "startAccessPoint Error starting softap";
- args = new Object[] {"startap"};
- executeOrLogWithMessage(SOFT_AP_COMMAND, args, NetdResponseCode.SoftapStatusResult,
- SOFT_AP_COMMAND_SUCCESS, logMsg);
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
- }
- }
-
- private static String getSecurityType(WifiConfiguration wifiConfig) {
- switch (wifiConfig.getAuthType()) {
- case KeyMgmt.WPA_PSK:
- return "wpa-psk";
- case KeyMgmt.WPA2_PSK:
- return "wpa2-psk";
- default:
- return "open";
- }
- }
-
- /* @param mode can be "AP", "STA" or "P2P" */
- @Override
- public void wifiFirmwareReload(String wlanIface, String mode) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- Object[] args = {"fwreload", wlanIface, mode};
- String logMsg = "wifiFirmwareReload Error reloading "
- + wlanIface + " fw in " + mode + " mode";
- try {
- executeOrLogWithMessage(SOFT_AP_COMMAND, args, NetdResponseCode.SoftapStatusResult,
- SOFT_AP_COMMAND_SUCCESS, logMsg);
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
- }
-
- // Ensure that before we return from this command, any asynchronous
- // notifications generated before the command completed have been
- // processed by all NetworkManagementEventObservers.
- mConnector.waitForCallbacks();
- }
-
- @Override
- public void stopAccessPoint(String wlanIface) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- Object[] args = {"stopap"};
- String logMsg = "stopAccessPoint Error stopping softap";
-
- try {
- executeOrLogWithMessage(SOFT_AP_COMMAND, args, NetdResponseCode.SoftapStatusResult,
- SOFT_AP_COMMAND_SUCCESS, logMsg);
- wifiFirmwareReload(wlanIface, "STA");
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
- }
- }
-
- @Override
- public void setAccessPoint(WifiConfiguration wifiConfig, String wlanIface) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- Object[] args;
- String logMsg = "startAccessPoint Error setting up softap";
- try {
- if (wifiConfig == null) {
- args = new Object[] {"set", wlanIface};
- } else {
- // TODO: understand why this is set to "6" instead of
- // Integer.toString(wifiConfig.apChannel) as in startAccessPoint
- // TODO: should startAccessPoint call this instead of repeating code?
- args = new Object[] {"set", wlanIface, wifiConfig.SSID,
- "broadcast", "6",
- getSecurityType(wifiConfig), new SensitiveArg(wifiConfig.preSharedKey)};
- }
- executeOrLogWithMessage(SOFT_AP_COMMAND, args, NetdResponseCode.SoftapStatusResult,
- SOFT_AP_COMMAND_SUCCESS, logMsg);
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
- }
- }
-
@Override
public void addIdleTimer(String iface, int timeout, final int type) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 1012f9a..e9b6690 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -34,8 +34,6 @@
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.INetworkStatsService;
-import android.net.InterfaceConfiguration;
-import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
@@ -58,18 +56,20 @@
import android.telephony.CarrierConfigManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.util.IState;
import com.android.internal.util.IndentingPrintWriter;
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 com.android.server.IoThread;
+import com.android.server.connectivity.tethering.IControlsTethering;
+import com.android.server.connectivity.tethering.TetherInterfaceStateMachine;
import com.android.server.net.BaseNetworkObserver;
import java.io.FileDescriptor;
@@ -81,18 +81,16 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
-import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @hide
*
- * Timeout
- *
- * TODO - look for parent classes and code sharing
+ * This class holds much of the business logic to allow Android devices
+ * to act as IP gateways via USB, BT, and WiFi interfaces.
*/
-public class Tethering extends BaseNetworkObserver {
+public class Tethering extends BaseNetworkObserver implements IControlsTethering {
private final Context mContext;
private final static String TAG = "Tethering";
@@ -100,7 +98,7 @@
private final static boolean VDBG = false;
private static final Class[] messageClasses = {
- Tethering.class, TetherMasterSM.class, TetherInterfaceSM.class
+ Tethering.class, TetherMasterSM.class, TetherInterfaceStateMachine.class
};
private static final SparseArray<String> sMagicDecoderRing =
MessageUtils.findMessageNames(messageClasses);
@@ -126,17 +124,25 @@
private final INetworkStatsService mStatsService;
private final Looper mLooper;
- private HashMap<String, TetherInterfaceSM> mIfaces; // all tethered/tetherable ifaces
+ private static class TetherState {
+ public final TetherInterfaceStateMachine mStateMachine;
+ public int mLastState;
+ public int mLastError;
+ public TetherState(TetherInterfaceStateMachine sm) {
+ mStateMachine = sm;
+ // Assume all state machines start out available and with no errors.
+ mLastState = IControlsTethering.STATE_AVAILABLE;
+ mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+ }
+ }
+ private final ArrayMap<String, TetherState> mTetherStates;
- private BroadcastReceiver mStateReceiver;
+ private final BroadcastReceiver mStateReceiver;
// {@link ComponentName} of the Service used to run tether provisioning.
private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(Resources
.getSystem().getString(com.android.internal.R.string.config_wifi_tether_enable));
- private static final String USB_NEAR_IFACE_ADDR = "192.168.42.129";
- private static final int USB_PREFIX_LENGTH = 24;
-
// USB is 192.168.42.1 and 255.255.255.0
// Wifi is 192.168.43.1 and 255.255.255.0
// BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1
@@ -166,6 +172,9 @@
private boolean mUsbTetherRequested; // true if USB tethering should be started
// when RNDIS is enabled
+ // True iff WiFi tethering should be started when soft AP is ready.
+ private boolean mWifiTetherRequested;
+
public Tethering(Context context, INetworkManagementService nmService,
INetworkStatsService statsService) {
mContext = context;
@@ -174,7 +183,7 @@
mPublicSync = new Object();
- mIfaces = new HashMap<String, TetherInterfaceSM>();
+ mTetherStates = new ArrayMap<>();
// make our own thread so we don't anr the system
mLooper = IoThread.get().getLooper();
@@ -187,6 +196,7 @@
IntentFilter filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_STATE);
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
mContext.registerReceiver(mStateReceiver, filter);
@@ -227,7 +237,7 @@
int ifaceTypes[] = mContext.getResources().getIntArray(
com.android.internal.R.array.config_tether_upstream_types);
- Collection<Integer> upstreamIfaceTypes = new ArrayList();
+ Collection<Integer> upstreamIfaceTypes = new ArrayList<>();
for (int i : ifaceTypes) {
upstreamIfaceTypes.add(new Integer(i));
}
@@ -248,34 +258,28 @@
// Never called directly: only called from interfaceLinkStateChanged.
// See NetlinkHandler.cpp:71.
if (VDBG) Log.d(TAG, "interfaceStatusChanged " + iface + ", " + up);
- boolean found = false;
- boolean usb = false;
synchronized (mPublicSync) {
- if (isWifi(iface)) {
- found = true;
- } else if (isUsb(iface)) {
- found = true;
- usb = true;
- } else if (isBluetooth(iface)) {
- found = true;
+ int interfaceType = ifaceNameToType(iface);
+ if (interfaceType == ConnectivityManager.TETHERING_INVALID) {
+ return;
}
- if (found == false) return;
- TetherInterfaceSM sm = mIfaces.get(iface);
+ TetherState tetherState = mTetherStates.get(iface);
if (up) {
- if (sm == null) {
- sm = new TetherInterfaceSM(iface, mLooper, usb);
- mIfaces.put(iface, sm);
- sm.start();
+ if (tetherState == null) {
+ trackNewTetherableInterface(iface, interfaceType);
}
} else {
- if (isUsb(iface)) {
- // ignore usb0 down after enabling RNDIS
- // we will handle disconnect in interfaceRemoved instead
+ if (interfaceType == ConnectivityManager.TETHERING_BLUETOOTH) {
+ tetherState.mStateMachine.sendMessage(
+ TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
+ mTetherStates.remove(iface);
+ } else {
+ // Ignore usb0 down after enabling RNDIS.
+ // We will handle disconnect in interfaceRemoved.
+ // Similarly, ignore interface down for WiFi. We monitor WiFi AP status
+ // through the WifiManager.WIFI_AP_STATE_CHANGED_ACTION intent.
if (VDBG) Log.d(TAG, "ignore interface down for " + iface);
- } else if (sm != null) {
- sm.sendMessage(TetherInterfaceSM.CMD_INTERFACE_DOWN);
- mIfaces.remove(iface);
}
}
}
@@ -295,7 +299,7 @@
}
}
- public boolean isWifi(String iface) {
+ private boolean isWifi(String iface) {
synchronized (mPublicSync) {
for (String regex : mTetherableWifiRegexs) {
if (iface.matches(regex)) return true;
@@ -304,7 +308,7 @@
}
}
- public boolean isBluetooth(String iface) {
+ private boolean isBluetooth(String iface) {
synchronized (mPublicSync) {
for (String regex : mTetherableBluetoothRegexs) {
if (iface.matches(regex)) return true;
@@ -313,35 +317,33 @@
}
}
+ private int ifaceNameToType(String iface) {
+ if (isWifi(iface)) {
+ return ConnectivityManager.TETHERING_WIFI;
+ } else if (isUsb(iface)) {
+ return ConnectivityManager.TETHERING_USB;
+ } else if (isBluetooth(iface)) {
+ return ConnectivityManager.TETHERING_BLUETOOTH;
+ }
+ return ConnectivityManager.TETHERING_INVALID;
+ }
+
@Override
public void interfaceAdded(String iface) {
if (VDBG) Log.d(TAG, "interfaceAdded " + iface);
- boolean found = false;
- boolean usb = false;
synchronized (mPublicSync) {
- if (isWifi(iface)) {
- found = true;
- }
- if (isUsb(iface)) {
- found = true;
- usb = true;
- }
- if (isBluetooth(iface)) {
- found = true;
- }
- if (found == false) {
+ int interfaceType = ifaceNameToType(iface);
+ if (interfaceType == ConnectivityManager.TETHERING_INVALID) {
if (VDBG) Log.d(TAG, iface + " is not a tetherable iface, ignoring");
return;
}
- TetherInterfaceSM sm = mIfaces.get(iface);
- if (sm != null) {
+ TetherState tetherState = mTetherStates.get(iface);
+ if (tetherState == null) {
+ trackNewTetherableInterface(iface, interfaceType);
+ } else {
if (VDBG) Log.d(TAG, "active iface (" + iface + ") reported as added, ignoring");
- return;
}
- sm = new TetherInterfaceSM(iface, mLooper, usb);
- mIfaces.put(iface, sm);
- sm.start();
}
}
@@ -349,15 +351,15 @@
public void interfaceRemoved(String iface) {
if (VDBG) Log.d(TAG, "interfaceRemoved " + iface);
synchronized (mPublicSync) {
- TetherInterfaceSM sm = mIfaces.get(iface);
- if (sm == null) {
+ TetherState tetherState = mTetherStates.get(iface);
+ if (tetherState == null) {
if (VDBG) {
Log.e(TAG, "attempting to remove unknown iface (" + iface + "), ignoring");
}
return;
}
- sm.sendMessage(TetherInterfaceSM.CMD_INTERFACE_DOWN);
- mIfaces.remove(iface);
+ tetherState.mStateMachine.sendMessage(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
+ mTetherStates.remove(iface);
}
}
@@ -413,24 +415,19 @@
* for the specified interface.
*/
private void enableTetheringInternal(int type, boolean enable, ResultReceiver receiver) {
- boolean isProvisioningRequired = isTetherProvisioningRequired();
+ boolean isProvisioningRequired = enable && isTetherProvisioningRequired();
+ int result;
switch (type) {
case ConnectivityManager.TETHERING_WIFI:
- final WifiManager wifiManager =
- (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
- if (wifiManager.setWifiApEnabled(null, enable)) {
- sendTetherResult(receiver, ConnectivityManager.TETHER_ERROR_NO_ERROR);
- if (enable && isProvisioningRequired) {
- scheduleProvisioningRechecks(type);
- }
- } else{
- sendTetherResult(receiver, ConnectivityManager.TETHER_ERROR_MASTER_ERROR);
+ result = setWifiTethering(enable);
+ if (isProvisioningRequired && result == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+ scheduleProvisioningRechecks(type);
}
+ sendTetherResult(receiver, result);
break;
case ConnectivityManager.TETHERING_USB:
- int result = setUsbTethering(enable);
- if (enable && isProvisioningRequired &&
- result == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+ result = setUsbTethering(enable);
+ if (isProvisioningRequired && result == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
scheduleProvisioningRechecks(type);
}
sendTetherResult(receiver, result);
@@ -450,6 +447,18 @@
}
}
+ private int setWifiTethering(final boolean enable) {
+ synchronized (mPublicSync) {
+ mWifiTetherRequested = enable;
+ final WifiManager wifiManager =
+ (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ if (wifiManager.setWifiApEnabled(null /* use existing wifi config */, enable)) {
+ return ConnectivityManager.TETHER_ERROR_NO_ERROR;
+ }
+ return ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
+ }
+ }
+
private void setBluetoothTethering(final boolean enable, final ResultReceiver receiver) {
final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null || !adapter.isEnabled()) {
@@ -576,62 +585,62 @@
public int tether(String iface) {
if (DBG) Log.d(TAG, "Tethering " + iface);
- TetherInterfaceSM sm = null;
synchronized (mPublicSync) {
- sm = mIfaces.get(iface);
+ TetherState tetherState = mTetherStates.get(iface);
+ if (tetherState == null) {
+ Log.e(TAG, "Tried to Tether an unknown iface :" + iface + ", ignoring");
+ return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
+ }
+ // Ignore the error status of the interface. If the interface is available,
+ // the errors are referring to past tethering attempts anyway.
+ if (tetherState.mLastState != IControlsTethering.STATE_AVAILABLE) {
+ Log.e(TAG, "Tried to Tether an unavailable iface :" + iface + ", ignoring");
+ return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
+ }
+ tetherState.mStateMachine.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
+ return ConnectivityManager.TETHER_ERROR_NO_ERROR;
}
- if (sm == null) {
- Log.e(TAG, "Tried to Tether an unknown iface :" + iface + ", ignoring");
- return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
- }
- if (!sm.isAvailable() && !sm.isErrored()) {
- Log.e(TAG, "Tried to Tether an unavailable iface :" + iface + ", ignoring");
- return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
- }
- sm.sendMessage(TetherInterfaceSM.CMD_TETHER_REQUESTED);
- return ConnectivityManager.TETHER_ERROR_NO_ERROR;
}
public int untether(String iface) {
if (DBG) Log.d(TAG, "Untethering " + iface);
- TetherInterfaceSM sm = null;
synchronized (mPublicSync) {
- sm = mIfaces.get(iface);
+ TetherState tetherState = mTetherStates.get(iface);
+ if (tetherState == null) {
+ Log.e(TAG, "Tried to Untether an unknown iface :" + iface + ", ignoring");
+ return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
+ }
+ if (tetherState.mLastState != IControlsTethering.STATE_TETHERED) {
+ Log.e(TAG, "Tried to untether an untethered iface :" + iface + ", ignoring");
+ return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
+ }
+ tetherState.mStateMachine.sendMessage(
+ TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
+ return ConnectivityManager.TETHER_ERROR_NO_ERROR;
}
- if (sm == null) {
- Log.e(TAG, "Tried to Untether an unknown iface :" + iface + ", ignoring");
- return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
- }
- if (sm.isErrored()) {
- Log.e(TAG, "Tried to Untethered an errored iface :" + iface + ", ignoring");
- return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
- }
- sm.sendMessage(TetherInterfaceSM.CMD_TETHER_UNREQUESTED);
- return ConnectivityManager.TETHER_ERROR_NO_ERROR;
}
public void untetherAll() {
- if (DBG) Log.d(TAG, "Untethering " + mIfaces);
- for (String iface : mIfaces.keySet()) {
- untether(iface);
+ synchronized (mPublicSync) {
+ if (DBG) Log.d(TAG, "Untethering " + mTetherStates.keySet());
+ for (int i = 0; i < mTetherStates.size(); i++) {
+ untether(mTetherStates.keyAt(i));
+ }
}
}
public int getLastTetherError(String iface) {
- TetherInterfaceSM sm = null;
synchronized (mPublicSync) {
- sm = mIfaces.get(iface);
- if (sm == null) {
+ TetherState tetherState = mTetherStates.get(iface);
+ if (tetherState == null) {
Log.e(TAG, "Tried to getLastTetherError on an unknown iface :" + iface +
", ignoring");
return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
}
- return sm.getLastError();
+ return tetherState.mLastError;
}
}
- // TODO - move all private methods used only by the state machine into the state machine
- // to clarify what needs synchronized protection.
private void sendTetherStateChangedBroadcast() {
if (!getConnectivityManager().isTetheringSupported()) return;
@@ -644,24 +653,22 @@
boolean bluetoothTethered = false;
synchronized (mPublicSync) {
- Set ifaces = mIfaces.keySet();
- for (Object iface : ifaces) {
- TetherInterfaceSM sm = mIfaces.get(iface);
- if (sm != null) {
- if (sm.isErrored()) {
- erroredList.add((String)iface);
- } else if (sm.isAvailable()) {
- availableList.add((String)iface);
- } else if (sm.isTethered()) {
- if (isUsb((String)iface)) {
- usbTethered = true;
- } else if (isWifi((String)iface)) {
- wifiTethered = true;
- } else if (isBluetooth((String)iface)) {
- bluetoothTethered = true;
- }
- activeList.add((String)iface);
+ for (int i = 0; i < mTetherStates.size(); i++) {
+ TetherState tetherState = mTetherStates.valueAt(i);
+ String iface = mTetherStates.keyAt(i);
+ if (tetherState.mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+ erroredList.add(iface);
+ } else if (tetherState.mLastState == IControlsTethering.STATE_AVAILABLE) {
+ availableList.add(iface);
+ } else if (tetherState.mLastState == IControlsTethering.STATE_TETHERED) {
+ if (isUsb(iface)) {
+ usbTethered = true;
+ } else if (isWifi(iface)) {
+ wifiTethered = true;
+ } else if (isBluetooth(iface)) {
+ bluetoothTethered = true;
}
+ activeList.add(iface);
}
}
}
@@ -770,7 +777,7 @@
mRndisEnabled = intent.getBooleanExtra(UsbManager.USB_FUNCTION_RNDIS, false);
// start tethering if we have a request pending
if (usbConnected && mRndisEnabled && mUsbTetherRequested) {
- tetherUsb(true);
+ tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB);
}
mUsbTetherRequested = false;
}
@@ -782,69 +789,80 @@
if (VDBG) Log.d(TAG, "Tethering got CONNECTIVITY_ACTION");
mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED);
}
+ } else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
+ synchronized (Tethering.this.mPublicSync) {
+ int curState = intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE,
+ WifiManager.WIFI_AP_STATE_DISABLED);
+ switch (curState) {
+ case WifiManager.WIFI_AP_STATE_ENABLING:
+ // We can see this state on the way to both enabled and failure states.
+ break;
+ case WifiManager.WIFI_AP_STATE_ENABLED:
+ // When the AP comes up and we've been requested to tether it, do so.
+ if (mWifiTetherRequested) {
+ tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_WIFI);
+ }
+ break;
+ case WifiManager.WIFI_AP_STATE_DISABLED:
+ case WifiManager.WIFI_AP_STATE_DISABLING:
+ case WifiManager.WIFI_AP_STATE_FAILED:
+ default:
+ if (DBG) {
+ Log.d(TAG, "Canceling WiFi tethering request - AP_STATE=" +
+ curState);
+ }
+ // Tell appropriate interface state machines that they should tear
+ // themselves down.
+ for (int i = 0; i < mTetherStates.size(); i++) {
+ TetherInterfaceStateMachine tism =
+ mTetherStates.valueAt(i).mStateMachine;
+ if (tism.interfaceType() == ConnectivityManager.TETHERING_WIFI) {
+ tism.sendMessage(
+ TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
+ break; // There should be at most one of these.
+ }
+ }
+ // Regardless of whether we requested this transition, the AP has gone
+ // down. Don't try to tether again unless we're requested to do so.
+ mWifiTetherRequested = false;
+ break;
+ }
+ }
} else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
updateConfiguration();
}
}
}
- private void tetherUsb(boolean enable) {
- if (VDBG) Log.d(TAG, "tetherUsb " + enable);
+ private void tetherMatchingInterfaces(boolean enable, int interfaceType) {
+ if (VDBG) Log.d(TAG, "tetherMatchingInterfaces(" + enable + ", " + interfaceType + ")");
- String[] ifaces = new String[0];
+ String[] ifaces = null;
try {
ifaces = mNMService.listInterfaces();
} catch (Exception e) {
Log.e(TAG, "Error listing Interfaces", e);
return;
}
- for (String iface : ifaces) {
- if (isUsb(iface)) {
- int result = (enable ? tether(iface) : untether(iface));
- if (result == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
- return;
+ String chosenIface = null;
+ if (ifaces != null) {
+ for (String iface : ifaces) {
+ if (ifaceNameToType(iface) == interfaceType) {
+ chosenIface = iface;
+ break;
}
}
}
- Log.e(TAG, "unable start or stop USB tethering");
- }
-
- // configured when we start tethering and unconfig'd on error or conclusion
- private boolean configureUsbIface(boolean enabled) {
- if (VDBG) Log.d(TAG, "configureUsbIface(" + enabled + ")");
-
- // toggle the USB interfaces
- String[] ifaces = new String[0];
- try {
- ifaces = mNMService.listInterfaces();
- } catch (Exception e) {
- Log.e(TAG, "Error listing Interfaces", e);
- return false;
+ if (chosenIface == null) {
+ Log.e(TAG, "could not find iface of type " + interfaceType);
+ return;
}
- for (String iface : ifaces) {
- if (isUsb(iface)) {
- InterfaceConfiguration ifcg = null;
- try {
- ifcg = mNMService.getInterfaceConfig(iface);
- if (ifcg != null) {
- InetAddress addr = NetworkUtils.numericToInetAddress(USB_NEAR_IFACE_ADDR);
- ifcg.setLinkAddress(new LinkAddress(addr, USB_PREFIX_LENGTH));
- if (enabled) {
- ifcg.setInterfaceUp();
- } else {
- ifcg.setInterfaceDown();
- }
- ifcg.clearFlag("running");
- mNMService.setInterfaceConfig(iface, ifcg);
- }
- } catch (Exception e) {
- Log.e(TAG, "Error configuring interface " + iface, e);
- return false;
- }
- }
- }
- return true;
+ int result = (enable ? tether(chosenIface) : untether(chosenIface));
+ if (result != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+ Log.e(TAG, "unable start or stop tethering on iface " + chosenIface);
+ return;
+ }
}
// TODO - return copies so people can't tamper
@@ -869,7 +887,7 @@
if (mRndisEnabled) {
final long ident = Binder.clearCallingIdentity();
try {
- tetherUsb(true);
+ tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -880,7 +898,7 @@
} else {
final long ident = Binder.clearCallingIdentity();
try {
- tetherUsb(false);
+ tetherMatchingInterfaces(false, ConnectivityManager.TETHERING_USB);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -952,37 +970,27 @@
public String[] getTetheredIfaces() {
ArrayList<String> list = new ArrayList<String>();
synchronized (mPublicSync) {
- Set keys = mIfaces.keySet();
- for (Object key : keys) {
- TetherInterfaceSM sm = mIfaces.get(key);
- if (sm.isTethered()) {
- list.add((String)key);
+ for (int i = 0; i < mTetherStates.size(); i++) {
+ TetherState tetherState = mTetherStates.valueAt(i);
+ if (tetherState.mLastState == IControlsTethering.STATE_TETHERED) {
+ list.add(mTetherStates.keyAt(i));
}
}
}
- String[] retVal = new String[list.size()];
- for (int i=0; i < list.size(); i++) {
- retVal[i] = list.get(i);
- }
- return retVal;
+ return list.toArray(new String[list.size()]);
}
public String[] getTetherableIfaces() {
ArrayList<String> list = new ArrayList<String>();
synchronized (mPublicSync) {
- Set keys = mIfaces.keySet();
- for (Object key : keys) {
- TetherInterfaceSM sm = mIfaces.get(key);
- if (sm.isAvailable()) {
- list.add((String)key);
+ for (int i = 0; i < mTetherStates.size(); i++) {
+ TetherState tetherState = mTetherStates.valueAt(i);
+ if (tetherState.mLastState == IControlsTethering.STATE_AVAILABLE) {
+ list.add(mTetherStates.keyAt(i));
}
}
}
- String[] retVal = new String[list.size()];
- for (int i=0; i < list.size(); i++) {
- retVal[i] = list.get(i);
- }
- return retVal;
+ return list.toArray(new String[list.size()]);
}
public String[] getTetheredDhcpRanges() {
@@ -992,19 +1000,14 @@
public String[] getErroredIfaces() {
ArrayList<String> list = new ArrayList<String>();
synchronized (mPublicSync) {
- Set keys = mIfaces.keySet();
- for (Object key : keys) {
- TetherInterfaceSM sm = mIfaces.get(key);
- if (sm.isErrored()) {
- list.add((String)key);
+ for (int i = 0; i < mTetherStates.size(); i++) {
+ TetherState tetherState = mTetherStates.valueAt(i);
+ if (tetherState.mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+ list.add(mTetherStates.keyAt(i));
}
}
}
- String[] retVal = new String[list.size()];
- for (int i= 0; i< list.size(); i++) {
- retVal[i] = list.get(i);
- }
- return retVal;
+ return list.toArray(new String[list.size()]);
}
private void maybeLogMessage(State state, int what) {
@@ -1014,401 +1017,6 @@
}
}
- class TetherInterfaceSM extends StateMachine {
- private static final int BASE_IFACE = Protocol.BASE_TETHERING + 100;
- // notification from the master SM that it's not in tether mode
- static final int CMD_TETHER_MODE_DEAD = BASE_IFACE + 1;
- // request from the user that it wants to tether
- static final int CMD_TETHER_REQUESTED = BASE_IFACE + 2;
- // request from the user that it wants to untether
- static final int CMD_TETHER_UNREQUESTED = BASE_IFACE + 3;
- // notification that this interface is down
- static final int CMD_INTERFACE_DOWN = BASE_IFACE + 4;
- // notification that this interface is up
- static final int CMD_INTERFACE_UP = BASE_IFACE + 5;
- // notification from the master SM that it had an error turning on cellular dun
- static final int CMD_CELL_DUN_ERROR = BASE_IFACE + 6;
- // notification from the master SM that it had trouble enabling IP Forwarding
- static final int CMD_IP_FORWARDING_ENABLE_ERROR = BASE_IFACE + 7;
- // notification from the master SM that it had trouble disabling IP Forwarding
- static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IFACE + 8;
- // notification from the master SM that it had trouble starting tethering
- static final int CMD_START_TETHERING_ERROR = BASE_IFACE + 9;
- // notification from the master SM that it had trouble stopping tethering
- static final int CMD_STOP_TETHERING_ERROR = BASE_IFACE + 10;
- // notification from the master SM that it had trouble setting the DNS forwarders
- static final int CMD_SET_DNS_FORWARDERS_ERROR = BASE_IFACE + 11;
- // the upstream connection has changed
- static final int CMD_TETHER_CONNECTION_CHANGED = BASE_IFACE + 12;
-
- private State mDefaultState;
-
- private State mInitialState;
- private State mStartingState;
- private State mTetheredState;
-
- private State mUnavailableState;
-
- private boolean mAvailable;
- private boolean mTethered;
- int mLastError;
-
- String mIfaceName;
- String mMyUpstreamIfaceName; // may change over time
-
- boolean mUsb;
-
- TetherInterfaceSM(String name, Looper looper, boolean usb) {
- super(name, looper);
- mIfaceName = name;
- mUsb = usb;
- setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
-
- mInitialState = new InitialState();
- addState(mInitialState);
- mStartingState = new StartingState();
- addState(mStartingState);
- mTetheredState = new TetheredState();
- addState(mTetheredState);
- mUnavailableState = new UnavailableState();
- addState(mUnavailableState);
-
- setInitialState(mInitialState);
- }
-
- public String toString() {
- String res = new String();
- res += mIfaceName + " - ";
- IState current = getCurrentState();
- if (current == mInitialState) res += "InitialState";
- if (current == mStartingState) res += "StartingState";
- if (current == mTetheredState) res += "TetheredState";
- if (current == mUnavailableState) res += "UnavailableState";
- if (mAvailable) res += " - Available";
- if (mTethered) res += " - Tethered";
- res += " - lastError =" + mLastError;
- return res;
- }
-
- public int getLastError() {
- synchronized (Tethering.this.mPublicSync) {
- return mLastError;
- }
- }
-
- private void setLastError(int error) {
- synchronized (Tethering.this.mPublicSync) {
- mLastError = error;
-
- if (isErrored()) {
- if (mUsb) {
- // note everything's been unwound by this point so nothing to do on
- // further error..
- Tethering.this.configureUsbIface(false);
- }
- }
- }
- }
-
- public boolean isAvailable() {
- synchronized (Tethering.this.mPublicSync) {
- return mAvailable;
- }
- }
-
- private void setAvailable(boolean available) {
- synchronized (Tethering.this.mPublicSync) {
- mAvailable = available;
- }
- }
-
- public boolean isTethered() {
- synchronized (Tethering.this.mPublicSync) {
- return mTethered;
- }
- }
-
- private void setTethered(boolean tethered) {
- synchronized (Tethering.this.mPublicSync) {
- mTethered = tethered;
- }
- }
-
- public boolean isErrored() {
- synchronized (Tethering.this.mPublicSync) {
- return (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR);
- }
- }
-
- class InitialState extends State {
- @Override
- public void enter() {
- setAvailable(true);
- setTethered(false);
- sendTetherStateChangedBroadcast();
- }
-
- @Override
- public boolean processMessage(Message message) {
- maybeLogMessage(this, message.what);
- boolean retValue = true;
- switch (message.what) {
- case CMD_TETHER_REQUESTED:
- setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
- mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_REQUESTED,
- TetherInterfaceSM.this);
- transitionTo(mStartingState);
- break;
- case CMD_INTERFACE_DOWN:
- transitionTo(mUnavailableState);
- break;
- default:
- retValue = false;
- break;
- }
- return retValue;
- }
- }
-
- class StartingState extends State {
- @Override
- public void enter() {
- setAvailable(false);
- if (mUsb) {
- if (!Tethering.this.configureUsbIface(true)) {
- mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED,
- TetherInterfaceSM.this);
- setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
-
- transitionTo(mInitialState);
- return;
- }
- }
- sendTetherStateChangedBroadcast();
-
- // Skipping StartingState
- transitionTo(mTetheredState);
- }
- @Override
- public boolean processMessage(Message message) {
- maybeLogMessage(this, message.what);
- boolean retValue = true;
- switch (message.what) {
- // maybe a parent class?
- case CMD_TETHER_UNREQUESTED:
- mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED,
- TetherInterfaceSM.this);
- if (mUsb) {
- if (!Tethering.this.configureUsbIface(false)) {
- setLastErrorAndTransitionToInitialState(
- ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
- break;
- }
- }
- transitionTo(mInitialState);
- break;
- case CMD_CELL_DUN_ERROR:
- 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:
- setLastErrorAndTransitionToInitialState(
- ConnectivityManager.TETHER_ERROR_MASTER_ERROR);
- break;
- case CMD_INTERFACE_DOWN:
- mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED,
- TetherInterfaceSM.this);
- transitionTo(mUnavailableState);
- break;
- default:
- retValue = false;
- }
- return retValue;
- }
- }
-
- class TetheredState extends State {
- @Override
- public void enter() {
- try {
- mNMService.tetherInterface(mIfaceName);
- } catch (Exception e) {
- Log.e(TAG, "Error Tethering: " + e.toString());
- setLastError(ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR);
-
- try {
- mNMService.untetherInterface(mIfaceName);
- } catch (Exception ee) {
- Log.e(TAG, "Error untethering after failure!" + ee.toString());
- }
- transitionTo(mInitialState);
- return;
- }
- if (DBG) Log.d(TAG, "Tethered " + mIfaceName);
- setAvailable(false);
- setTethered(true);
- sendTetherStateChangedBroadcast();
- }
-
- private void cleanupUpstream() {
- if (mMyUpstreamIfaceName != null) {
- // 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, mMyUpstreamIfaceName);
- } catch (Exception e) {
- if (VDBG) Log.e(
- TAG, "Exception in removeInterfaceForward: " + e.toString());
- }
- try {
- mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName);
- } catch (Exception e) {
- if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString());
- }
- mMyUpstreamIfaceName = null;
- }
- return;
- }
-
- @Override
- public boolean processMessage(Message message) {
- maybeLogMessage(this, message.what);
- boolean retValue = true;
- boolean error = false;
- switch (message.what) {
- case CMD_TETHER_UNREQUESTED:
- case CMD_INTERFACE_DOWN:
- cleanupUpstream();
- try {
- mNMService.untetherInterface(mIfaceName);
- } catch (Exception e) {
- setLastErrorAndTransitionToInitialState(
- ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR);
- break;
- }
- mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED,
- TetherInterfaceSM.this);
- if (message.what == CMD_TETHER_UNREQUESTED) {
- if (mUsb) {
- if (!Tethering.this.configureUsbIface(false)) {
- setLastError(
- ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
- }
- }
- transitionTo(mInitialState);
- } else if (message.what == CMD_INTERFACE_DOWN) {
- transitionTo(mUnavailableState);
- }
- if (DBG) Log.d(TAG, "Untethered " + mIfaceName);
- break;
- case CMD_TETHER_CONNECTION_CHANGED:
- String newUpstreamIfaceName = (String)(message.obj);
- if ((mMyUpstreamIfaceName == null && newUpstreamIfaceName == null) ||
- (mMyUpstreamIfaceName != null &&
- mMyUpstreamIfaceName.equals(newUpstreamIfaceName))) {
- if (VDBG) Log.d(TAG, "Connection changed noop - dropping");
- break;
- }
- cleanupUpstream();
- if (newUpstreamIfaceName != null) {
- try {
- mNMService.enableNat(mIfaceName, newUpstreamIfaceName);
- mNMService.startInterfaceForwarding(mIfaceName,
- newUpstreamIfaceName);
- } catch (Exception e) {
- Log.e(TAG, "Exception enabling Nat: " + e.toString());
- try {
- mNMService.disableNat(mIfaceName, newUpstreamIfaceName);
- } catch (Exception ee) {}
- try {
- mNMService.untetherInterface(mIfaceName);
- } catch (Exception ee) {}
-
- setLastError(ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR);
- transitionTo(mInitialState);
- return true;
- }
- }
- mMyUpstreamIfaceName = newUpstreamIfaceName;
- break;
- case CMD_CELL_DUN_ERROR:
- 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:
- error = true;
- // fall through
- case CMD_TETHER_MODE_DEAD:
- cleanupUpstream();
- try {
- mNMService.untetherInterface(mIfaceName);
- } catch (Exception e) {
- setLastErrorAndTransitionToInitialState(
- ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR);
- break;
- }
- if (error) {
- setLastErrorAndTransitionToInitialState(
- ConnectivityManager.TETHER_ERROR_MASTER_ERROR);
- break;
- }
- if (DBG) Log.d(TAG, "Tether lost upstream connection " + mIfaceName);
- sendTetherStateChangedBroadcast();
- if (mUsb) {
- if (!Tethering.this.configureUsbIface(false)) {
- setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
- }
- }
- transitionTo(mInitialState);
- break;
- default:
- retValue = false;
- break;
- }
- return retValue;
- }
- }
-
- class UnavailableState extends State {
- @Override
- public void enter() {
- setAvailable(false);
- setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
- setTethered(false);
- sendTetherStateChangedBroadcast();
- }
- @Override
- public boolean processMessage(Message message) {
- boolean retValue = true;
- switch (message.what) {
- case CMD_INTERFACE_UP:
- transitionTo(mInitialState);
- break;
- default:
- retValue = false;
- break;
- }
- return retValue;
- }
- }
-
- void setLastErrorAndTransitionToInitialState(int error) {
- setLastError(error);
- transitionTo(mInitialState);
- }
-
- }
-
/**
* A NetworkCallback class that relays information of interest to the
* tethering master state machine thread for subsequent processing.
@@ -1442,7 +1050,7 @@
* could/should be moved here.
*/
class UpstreamNetworkMonitor {
- final HashMap<Network, NetworkState> mNetworkMap = new HashMap();
+ final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>();
NetworkCallback mDefaultNetworkCallback;
NetworkCallback mDunTetheringCallback;
@@ -1520,12 +1128,6 @@
static final int EVENT_UPSTREAM_LINKPROPERTIES_CHANGED = BASE_MASTER + 5;
static final int EVENT_UPSTREAM_LOST = BASE_MASTER + 6;
- // This indicates what a timeout event relates to. A state that
- // sends itself a delayed timeout event and handles incoming timeout events
- // should inc this when it is entered and whenever it sends a new timeout event.
- // We do not flush the old ones.
- private int mSequenceNumber;
-
private State mInitialState;
private State mTetherModeAliveState;
@@ -1535,7 +1137,19 @@
private State mStopTetheringErrorState;
private State mSetDnsForwardersErrorState;
- private ArrayList<TetherInterfaceSM> mNotifyList;
+ // This list is a little subtle. It contains all the interfaces that currently are
+ // requesting tethering, regardless of whether these interfaces are still members of
+ // mTetherStates. This allows us to maintain the following predicates:
+ //
+ // 1) mTetherStates contains the set of all currently existing, tetherable, link state up
+ // interfaces.
+ // 2) mNotifyList contains all state machines that may have outstanding tethering state
+ // that needs to be torn down.
+ //
+ // Because we excise interfaces immediately from mTetherStates, we must maintain mNotifyList
+ // so that the garbage collector does not clean up the state machine before it has a chance
+ // to tear itself down.
+ private ArrayList<TetherInterfaceStateMachine> mNotifyList;
private int mMobileApnReserved = ConnectivityManager.TYPE_NONE;
private NetworkCallback mMobileUpstreamCallback;
@@ -1562,7 +1176,7 @@
mSetDnsForwardersErrorState = new SetDnsForwardersErrorState();
addState(mSetDnsForwardersErrorState);
- mNotifyList = new ArrayList<TetherInterfaceSM>();
+ mNotifyList = new ArrayList<>();
setInitialState(mInitialState);
}
@@ -1777,8 +1391,8 @@
protected void notifyTetheredOfNewUpstreamIface(String ifaceName) {
if (DBG) Log.d(TAG, "Notifying tethered with upstream=" + ifaceName);
mCurrentUpstreamIface = ifaceName;
- for (TetherInterfaceSM sm : mNotifyList) {
- sm.sendMessage(TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED,
+ for (TetherInterfaceStateMachine sm : mNotifyList) {
+ sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED,
ifaceName);
}
}
@@ -1845,20 +1459,16 @@
config_mobile_hotspot_provision_app_no_ui).isEmpty() == false) {
ArrayList<Integer> tethered = new ArrayList<Integer>();
synchronized (mPublicSync) {
- Set ifaces = mIfaces.keySet();
- for (Object iface : ifaces) {
- TetherInterfaceSM sm = mIfaces.get(iface);
- if (sm != null && sm.isTethered()) {
- if (isUsb((String)iface)) {
- tethered.add(new Integer(
- ConnectivityManager.TETHERING_USB));
- } else if (isWifi((String)iface)) {
- tethered.add(new Integer(
- ConnectivityManager.TETHERING_WIFI));
- } else if (isBluetooth((String)iface)) {
- tethered.add(new Integer(
- ConnectivityManager.TETHERING_BLUETOOTH));
- }
+ for (int i = 0; i < mTetherStates.size(); i++) {
+ TetherState tetherState = mTetherStates.valueAt(i);
+ if (tetherState.mLastState !=
+ IControlsTethering.STATE_TETHERED) {
+ continue; // Skip interfaces that aren't tethered.
+ }
+ String iface = mTetherStates.keyAt(i);
+ int interfaceType = ifaceNameToType(iface);
+ if (interfaceType != ConnectivityManager.TETHERING_INVALID) {
+ tethered.add(new Integer(interfaceType));
}
}
}
@@ -1885,26 +1495,22 @@
class InitialState extends TetherMasterUtilState {
@Override
- public void enter() {
- }
- @Override
public boolean processMessage(Message message) {
maybeLogMessage(this, message.what);
boolean retValue = true;
switch (message.what) {
case CMD_TETHER_MODE_REQUESTED:
- TetherInterfaceSM who = (TetherInterfaceSM)message.obj;
+ TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
if (VDBG) Log.d(TAG, "Tether Mode requested by " + who);
- mNotifyList.add(who);
+ if (mNotifyList.indexOf(who) < 0) {
+ mNotifyList.add(who);
+ }
transitionTo(mTetherModeAliveState);
break;
case CMD_TETHER_MODE_UNREQUESTED:
- who = (TetherInterfaceSM)message.obj;
+ who = (TetherInterfaceStateMachine)message.obj;
if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who);
- int index = mNotifyList.indexOf(who);
- if (index != -1) {
- mNotifyList.remove(who);
- }
+ mNotifyList.remove(who);
break;
default:
retValue = false;
@@ -1941,26 +1547,28 @@
boolean retValue = true;
switch (message.what) {
case CMD_TETHER_MODE_REQUESTED:
- TetherInterfaceSM who = (TetherInterfaceSM)message.obj;
+ TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
if (VDBG) Log.d(TAG, "Tether Mode requested by " + who);
- mNotifyList.add(who);
- who.sendMessage(TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED,
+ if (mNotifyList.indexOf(who) < 0) {
+ mNotifyList.add(who);
+ }
+ who.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED,
mCurrentUpstreamIface);
break;
case CMD_TETHER_MODE_UNREQUESTED:
- who = (TetherInterfaceSM)message.obj;
+ who = (TetherInterfaceStateMachine)message.obj;
if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who);
- int index = mNotifyList.indexOf(who);
- if (index != -1) {
+ if (mNotifyList.remove(who)) {
if (DBG) Log.d(TAG, "TetherModeAlive removing notifyee " + who);
- mNotifyList.remove(index);
if (mNotifyList.isEmpty()) {
turnOffMasterTetherSettings(); // transitions appropriately
} else {
if (DBG) {
Log.d(TAG, "TetherModeAlive still has " + mNotifyList.size() +
" live requests:");
- for (Object o : mNotifyList) Log.d(TAG, " " + o);
+ for (TetherInterfaceStateMachine o : mNotifyList) {
+ Log.d(TAG, " " + o);
+ }
}
}
} else {
@@ -2010,7 +1618,7 @@
boolean retValue = true;
switch (message.what) {
case CMD_TETHER_MODE_REQUESTED:
- TetherInterfaceSM who = (TetherInterfaceSM)message.obj;
+ TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
who.sendMessage(mErrorNotification);
break;
default:
@@ -2020,8 +1628,7 @@
}
void notify(int msgType) {
mErrorNotification = msgType;
- for (Object o : mNotifyList) {
- TetherInterfaceSM sm = (TetherInterfaceSM)o;
+ for (TetherInterfaceStateMachine sm : mNotifyList) {
sm.sendMessage(msgType);
}
}
@@ -2031,7 +1638,7 @@
@Override
public void enter() {
Log.e(TAG, "Error in setIpForwardingEnabled");
- notify(TetherInterfaceSM.CMD_IP_FORWARDING_ENABLE_ERROR);
+ notify(TetherInterfaceStateMachine.CMD_IP_FORWARDING_ENABLE_ERROR);
}
}
@@ -2039,7 +1646,7 @@
@Override
public void enter() {
Log.e(TAG, "Error in setIpForwardingDisabled");
- notify(TetherInterfaceSM.CMD_IP_FORWARDING_DISABLE_ERROR);
+ notify(TetherInterfaceStateMachine.CMD_IP_FORWARDING_DISABLE_ERROR);
}
}
@@ -2047,7 +1654,7 @@
@Override
public void enter() {
Log.e(TAG, "Error in startTethering");
- notify(TetherInterfaceSM.CMD_START_TETHERING_ERROR);
+ notify(TetherInterfaceStateMachine.CMD_START_TETHERING_ERROR);
try {
mNMService.setIpForwardingEnabled(false);
} catch (Exception e) {}
@@ -2058,7 +1665,7 @@
@Override
public void enter() {
Log.e(TAG, "Error in stopTethering");
- notify(TetherInterfaceSM.CMD_STOP_TETHERING_ERROR);
+ notify(TetherInterfaceStateMachine.CMD_STOP_TETHERING_ERROR);
try {
mNMService.setIpForwardingEnabled(false);
} catch (Exception e) {}
@@ -2069,7 +1676,7 @@
@Override
public void enter() {
Log.e(TAG, "Error in setDnsForwarders");
- notify(TetherInterfaceSM.CMD_SET_DNS_FORWARDERS_ERROR);
+ notify(TetherInterfaceStateMachine.CMD_SET_DNS_FORWARDERS_ERROR);
try {
mNMService.stopTethering();
} catch (Exception e) {}
@@ -2080,9 +1687,11 @@
}
}
+ @Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ // Binder.java closes the resource for us.
+ @SuppressWarnings("resource")
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
-
if (mContext.checkCallingOrSelfPermission(
android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
pw.println("Permission Denial: can't dump ConnectivityService.Tether " +
@@ -2102,12 +1711,67 @@
pw.println("Tether state:");
pw.increaseIndent();
- for (Object o : mIfaces.values()) {
- pw.println(o);
+ for (int i = 0; i < mTetherStates.size(); i++) {
+ final String iface = mTetherStates.keyAt(i);
+ final TetherState tetherState = mTetherStates.valueAt(i);
+ pw.print(iface + " - ");
+
+ switch (tetherState.mLastState) {
+ case IControlsTethering.STATE_UNAVAILABLE:
+ pw.print("UnavailableState");
+ break;
+ case IControlsTethering.STATE_AVAILABLE:
+ pw.print("AvailableState");
+ break;
+ case IControlsTethering.STATE_TETHERED:
+ pw.print("TetheredState");
+ break;
+ default:
+ pw.print("UnknownState");
+ break;
+ }
+ pw.println(" - lastError = " + tetherState.mLastError);
}
pw.decreaseIndent();
}
pw.decreaseIndent();
- return;
+ }
+
+ @Override
+ public void notifyInterfaceStateChange(String iface, TetherInterfaceStateMachine who,
+ int state, int error) {
+ synchronized (mPublicSync) {
+ TetherState tetherState = mTetherStates.get(iface);
+ if (tetherState != null && tetherState.mStateMachine.equals(who)) {
+ tetherState.mLastState = state;
+ tetherState.mLastError = error;
+ } else {
+ if (DBG) Log.d(TAG, "got notification from stale iface " + iface);
+ }
+ }
+
+ if (DBG) {
+ Log.d(TAG, "iface " + iface + " notified that it was in state " + state +
+ " with error " + error);
+ }
+
+ switch (state) {
+ case IControlsTethering.STATE_UNAVAILABLE:
+ case IControlsTethering.STATE_AVAILABLE:
+ mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED, who);
+ break;
+ case IControlsTethering.STATE_TETHERED:
+ mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_REQUESTED, who);
+ break;
+ }
+ sendTetherStateChangedBroadcast();
+ }
+
+ private void trackNewTetherableInterface(String iface, int interfaceType) {
+ TetherState tetherState;
+ tetherState = new TetherState(new TetherInterfaceStateMachine(iface, mLooper,
+ interfaceType, mNMService, mStatsService, this));
+ mTetherStates.put(iface, tetherState);
+ tetherState.mStateMachine.start();
}
}
diff --git a/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java
new file mode 100644
index 0000000..449b8a8
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.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 com.android.server.connectivity.tethering;
+
+/**
+ * @hide
+ *
+ * Interface with methods necessary to notify that a given interface is ready for tethering.
+ */
+public interface IControlsTethering {
+ public final int STATE_UNAVAILABLE = 0;
+ public final int STATE_AVAILABLE = 1;
+ public final int STATE_TETHERED = 2;
+
+ /**
+ * Notify that |who| has changed its tethering state. This may be called from any thread.
+ *
+ * @param iface a network interface (e.g. "wlan0")
+ * @param who corresponding instance of a TetherInterfaceStateMachine
+ * @param state one of IControlsTethering.STATE_*
+ * @param lastError one of ConnectivityManager.TETHER_ERROR_*
+ */
+ void notifyInterfaceStateChange(String iface, TetherInterfaceStateMachine who,
+ int state, int lastError);
+}
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
new file mode 100644
index 0000000..50bb022
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -0,0 +1,326 @@
+/*
+ * 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 com.android.server.connectivity.tethering;
+
+import android.net.ConnectivityManager;
+import android.net.INetworkStatsService;
+import android.net.InterfaceConfiguration;
+import android.net.LinkAddress;
+import android.net.NetworkUtils;
+import android.os.INetworkManagementService;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+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.InetAddress;
+
+/**
+ * @hide
+ *
+ * Tracks the eligibility of a given network interface for tethering.
+ */
+public class TetherInterfaceStateMachine extends StateMachine {
+ 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;
+
+ private final static String TAG = "TetherInterfaceSM";
+ private final static boolean DBG = false;
+ private final static boolean VDBG = false;
+ private static final Class[] messageClasses = {
+ TetherInterfaceStateMachine.class
+ };
+ private static final SparseArray<String> sMagicDecoderRing =
+ MessageUtils.findMessageNames(messageClasses);
+
+ 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;
+
+ private final State mInitialState;
+ private final State mTetheredState;
+ private final State mUnavailableState;
+
+ private final INetworkManagementService mNMService;
+ private final INetworkStatsService mStatsService;
+ private final IControlsTethering mTetherController;
+
+ private final String mIfaceName;
+ private final int mInterfaceType;
+
+ private int mLastError;
+ private String mMyUpstreamIfaceName; // may change over time
+
+ public TetherInterfaceStateMachine(String ifaceName, Looper looper, int interfaceType,
+ INetworkManagementService nMService, INetworkStatsService statsService,
+ IControlsTethering tetherController) {
+ super(ifaceName, looper);
+ mNMService = nMService;
+ mStatsService = statsService;
+ mTetherController = tetherController;
+ mIfaceName = ifaceName;
+ mInterfaceType = interfaceType;
+ mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+
+ mInitialState = new InitialState();
+ addState(mInitialState);
+ mTetheredState = new TetheredState();
+ addState(mTetheredState);
+ mUnavailableState = new UnavailableState();
+ addState(mUnavailableState);
+
+ setInitialState(mInitialState);
+ }
+
+ public int interfaceType() {
+ return mInterfaceType;
+ }
+
+ // configured when we start tethering and unconfig'd on error or conclusion
+ private boolean configureIfaceIp(boolean enabled) {
+ if (VDBG) Log.d(TAG, "configureIfaceIp(" + enabled + ")");
+
+ 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 = WIFI_HOST_IFACE_ADDR;
+ prefixLen = WIFI_HOST_IFACE_PREFIX_LENGTH;
+ } else {
+ // Nothing to do, BT does this elsewhere.
+ return true;
+ }
+
+ InterfaceConfiguration ifcg = null;
+ try {
+ ifcg = mNMService.getInterfaceConfig(mIfaceName);
+ if (ifcg != null) {
+ InetAddress addr = NetworkUtils.numericToInetAddress(ipAsString);
+ ifcg.setLinkAddress(new LinkAddress(addr, prefixLen));
+ if (enabled) {
+ ifcg.setInterfaceUp();
+ } else {
+ ifcg.setInterfaceDown();
+ }
+ ifcg.clearFlag("running");
+ mNMService.setInterfaceConfig(mIfaceName, ifcg);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error configuring interface " + mIfaceName, e);
+ return false;
+ }
+
+ return true;
+ }
+
+ private void maybeLogMessage(State state, int what) {
+ if (DBG) {
+ Log.d(TAG, state.getName() + " got " +
+ sMagicDecoderRing.get(what, Integer.toString(what)));
+ }
+ }
+
+ class InitialState extends State {
+ @Override
+ public void enter() {
+ mTetherController.notifyInterfaceStateChange(
+ mIfaceName, TetherInterfaceStateMachine.this,
+ IControlsTethering.STATE_AVAILABLE, mLastError);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ maybeLogMessage(this, message.what);
+ boolean retValue = true;
+ switch (message.what) {
+ case CMD_TETHER_REQUESTED:
+ mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+ transitionTo(mTetheredState);
+ break;
+ case CMD_INTERFACE_DOWN:
+ transitionTo(mUnavailableState);
+ break;
+ default:
+ retValue = false;
+ break;
+ }
+ return retValue;
+ }
+ }
+
+ class TetheredState extends State {
+ @Override
+ public void enter() {
+ if (!configureIfaceIp(true)) {
+ mLastError = ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR;
+ transitionTo(mInitialState);
+ return;
+ }
+
+ try {
+ mNMService.tetherInterface(mIfaceName);
+ } catch (Exception e) {
+ Log.e(TAG, "Error Tethering: " + e.toString());
+ mLastError = ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+ transitionTo(mInitialState);
+ return;
+ }
+ if (DBG) Log.d(TAG, "Tethered " + mIfaceName);
+ mTetherController.notifyInterfaceStateChange(
+ mIfaceName, TetherInterfaceStateMachine.this,
+ IControlsTethering.STATE_TETHERED, mLastError);
+ }
+
+ @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.
+ cleanupUpstream();
+
+ try {
+ mNMService.untetherInterface(mIfaceName);
+ } catch (Exception ee) {
+ mLastError = ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
+ Log.e(TAG, "Failed to untether interface: " + ee.toString());
+ }
+
+ configureIfaceIp(false);
+ }
+
+ private void cleanupUpstream() {
+ if (mMyUpstreamIfaceName != null) {
+ // 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, mMyUpstreamIfaceName);
+ } catch (Exception e) {
+ if (VDBG) Log.e(
+ TAG, "Exception in removeInterfaceForward: " + e.toString());
+ }
+ try {
+ mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName);
+ } catch (Exception e) {
+ if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString());
+ }
+ mMyUpstreamIfaceName = null;
+ }
+ return;
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ maybeLogMessage(this, message.what);
+ boolean retValue = true;
+ 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_TETHER_CONNECTION_CHANGED:
+ String newUpstreamIfaceName = (String)(message.obj);
+ if ((mMyUpstreamIfaceName == null && newUpstreamIfaceName == null) ||
+ (mMyUpstreamIfaceName != null &&
+ mMyUpstreamIfaceName.equals(newUpstreamIfaceName))) {
+ if (VDBG) Log.d(TAG, "Connection changed noop - dropping");
+ break;
+ }
+ cleanupUpstream();
+ if (newUpstreamIfaceName != null) {
+ try {
+ mNMService.enableNat(mIfaceName, newUpstreamIfaceName);
+ mNMService.startInterfaceForwarding(mIfaceName,
+ newUpstreamIfaceName);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception enabling Nat: " + e.toString());
+ mLastError = ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
+ transitionTo(mInitialState);
+ return true;
+ }
+ }
+ mMyUpstreamIfaceName = newUpstreamIfaceName;
+ 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:
+ retValue = false;
+ break;
+ }
+ return retValue;
+ }
+ }
+
+ /**
+ * 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;
+ mTetherController.notifyInterfaceStateChange(
+ mIfaceName, TetherInterfaceStateMachine.this,
+ IControlsTethering.STATE_UNAVAILABLE, mLastError);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/net/IpConfigStore.java b/services/core/java/com/android/server/net/IpConfigStore.java
index 2807ec8..4d56468 100644
--- a/services/core/java/com/android/server/net/IpConfigStore.java
+++ b/services/core/java/com/android/server/net/IpConfigStore.java
@@ -27,6 +27,7 @@
import android.util.Log;
import android.util.SparseArray;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.net.DelayedDiskWrite;
import java.io.BufferedInputStream;
@@ -34,7 +35,9 @@
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStream;
import java.net.InetAddress;
import java.net.Inet4Address;
@@ -67,7 +70,8 @@
this(new DelayedDiskWrite());
}
- private boolean writeConfig(DataOutputStream out, int configKey,
+ @VisibleForTesting
+ public static boolean writeConfig(DataOutputStream out, int configKey,
IpConfiguration config) throws IOException {
boolean written = false;
@@ -171,12 +175,25 @@
});
}
- public SparseArray<IpConfiguration> readIpAndProxyConfigurations(String filePath) {
- SparseArray<IpConfiguration> networks = new SparseArray<IpConfiguration>();
+ public static SparseArray<IpConfiguration> readIpAndProxyConfigurations(String filePath) {
+ BufferedInputStream bufferedInputStream;
+ try {
+ bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath));
+ } catch (FileNotFoundException e) {
+ // Return an empty array here because callers expect an empty array when the file is
+ // not present.
+ loge("Error opening configuration file: " + e);
+ return new SparseArray<>();
+ }
+ return readIpAndProxyConfigurations(bufferedInputStream);
+ }
+ public static SparseArray<IpConfiguration> readIpAndProxyConfigurations(
+ InputStream inputStream) {
+ SparseArray<IpConfiguration> networks = new SparseArray<IpConfiguration>();
DataInputStream in = null;
try {
- in = new DataInputStream(new BufferedInputStream(new FileInputStream(filePath)));
+ in = new DataInputStream(inputStream);
int version = in.readInt();
if (version != 2 && version != 1) {
@@ -327,11 +344,11 @@
return networks;
}
- protected void loge(String s) {
+ protected static void loge(String s) {
Log.e(TAG, s);
}
- protected void log(String s) {
+ protected static void log(String s) {
Log.d(TAG, s);
}
}
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index 0437e1d..d1bd505 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -12,6 +12,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_STATIC_JAVA_LIBRARIES := \
+ frameworks-base-testutils \
services.core \
services.devicepolicy \
services.net \
diff --git a/services/tests/servicestests/src/android/net/IpUtilsTest.java b/services/tests/servicestests/src/android/net/util/IpUtilsTest.java
similarity index 100%
rename from services/tests/servicestests/src/android/net/IpUtilsTest.java
rename to services/tests/servicestests/src/android/net/util/IpUtilsTest.java
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
new file mode 100644
index 0000000..a30b362
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
@@ -0,0 +1,277 @@
+/*
+ * 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 com.android.server.connectivity.tethering;
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
+import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
+import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+import static android.net.ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
+import static com.android.server.connectivity.tethering.IControlsTethering.STATE_AVAILABLE;
+import static com.android.server.connectivity.tethering.IControlsTethering.STATE_TETHERED;
+import static com.android.server.connectivity.tethering.IControlsTethering.STATE_UNAVAILABLE;
+
+import android.net.ConnectivityManager;
+import android.net.INetworkStatsService;
+import android.net.InterfaceConfiguration;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TetherInterfaceStateMachineTest {
+ private static final String IFACE_NAME = "testnet1";
+ private static final String UPSTREAM_IFACE = "upstream0";
+ private static final String UPSTREAM_IFACE2 = "upstream1";
+
+ @Mock private INetworkManagementService mNMService;
+ @Mock private INetworkStatsService mStatsService;
+ @Mock private IControlsTethering mTetherHelper;
+ @Mock private InterfaceConfiguration mInterfaceConfiguration;
+
+ private final TestLooper mLooper = new TestLooper();
+ private TetherInterfaceStateMachine mTestedSm;
+
+ private void initStateMachine(int interfaceType) throws Exception {
+ mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), interfaceType,
+ mNMService, mStatsService, mTetherHelper);
+ mTestedSm.start();
+ // Starting the state machine always puts us in a consistent state and notifies
+ // the test of the world that we've changed from an unknown to available state.
+ mLooper.dispatchAll();
+ reset(mNMService, mStatsService, mTetherHelper);
+ when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
+ }
+
+ private void initTetheredStateMachine(int interfaceType, String upstreamIface) throws Exception {
+ initStateMachine(interfaceType);
+ dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
+ if (upstreamIface != null) {
+ dispatchTetherConnectionChanged(upstreamIface);
+ }
+ reset(mNMService, mStatsService, mTetherHelper);
+ when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
+ }
+
+ @Before public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void startsOutAvailable() {
+ mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(),
+ ConnectivityManager.TETHERING_BLUETOOTH, mNMService, mStatsService, mTetherHelper);
+ mTestedSm.start();
+ mLooper.dispatchAll();
+ verify(mTetherHelper).notifyInterfaceStateChange(
+ IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
+ verifyNoMoreInteractions(mTetherHelper, mNMService, mStatsService);
+ }
+
+ @Test
+ public void shouldDoNothingUntilRequested() throws Exception {
+ initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH);
+ final int [] NOOP_COMMANDS = {
+ TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED,
+ TetherInterfaceStateMachine.CMD_IP_FORWARDING_ENABLE_ERROR,
+ TetherInterfaceStateMachine.CMD_IP_FORWARDING_DISABLE_ERROR,
+ TetherInterfaceStateMachine.CMD_START_TETHERING_ERROR,
+ TetherInterfaceStateMachine.CMD_STOP_TETHERING_ERROR,
+ TetherInterfaceStateMachine.CMD_SET_DNS_FORWARDERS_ERROR,
+ TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED
+ };
+ for (int command : NOOP_COMMANDS) {
+ // None of these commands should trigger us to request action from
+ // the rest of the system.
+ dispatchCommand(command);
+ verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+ }
+ }
+
+ @Test
+ public void handlesImmediateInterfaceDown() throws Exception {
+ initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH);
+
+ dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
+ verify(mTetherHelper).notifyInterfaceStateChange(
+ IFACE_NAME, mTestedSm, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
+ verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+ }
+
+ @Test
+ public void canBeTethered() throws Exception {
+ initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH);
+
+ dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
+ InOrder inOrder = inOrder(mTetherHelper, mNMService);
+ inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
+ inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
+ IFACE_NAME, mTestedSm, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
+ verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+ }
+
+ @Test
+ public void canUnrequestTethering() throws Exception {
+ initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, null);
+
+ dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
+ InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper);
+ inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
+ inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
+ IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
+ verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+ }
+
+ @Test
+ public void canBeTetheredAsUsb() throws Exception {
+ initStateMachine(ConnectivityManager.TETHERING_USB);
+
+ dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
+ InOrder inOrder = inOrder(mTetherHelper, mNMService);
+ inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME);
+ inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
+ inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
+ inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
+ IFACE_NAME, mTestedSm, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
+ verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+ }
+
+ @Test
+ public void handlesFirstUpstreamChange() throws Exception {
+ initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, null);
+
+ // Telling the state machine about its upstream interface triggers a little more configuration.
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+ InOrder inOrder = inOrder(mNMService);
+ inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
+ verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+ }
+
+ @Test
+ public void handlesChangingUpstream() throws Exception {
+ initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, UPSTREAM_IFACE);
+
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
+ InOrder inOrder = inOrder(mNMService, mStatsService);
+ inOrder.verify(mStatsService).forceUpdate();
+ inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
+ inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
+ verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+ }
+
+ @Test
+ public void canUnrequestTetheringWithUpstream() throws Exception {
+ initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, UPSTREAM_IFACE);
+
+ dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
+ InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper);
+ inOrder.verify(mStatsService).forceUpdate();
+ inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
+ inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
+ IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
+ verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+ }
+
+ @Test
+ public void interfaceDownLeadsToUnavailable() throws Exception {
+ for (boolean shouldThrow : new boolean[]{true, false}) {
+ initTetheredStateMachine(ConnectivityManager.TETHERING_USB, null);
+
+ if (shouldThrow) {
+ doThrow(RemoteException.class).when(mNMService).untetherInterface(IFACE_NAME);
+ }
+ dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
+ InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mTetherHelper);
+ usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
+ usbTeardownOrder.verify(mNMService).setInterfaceConfig(
+ IFACE_NAME, mInterfaceConfiguration);
+ usbTeardownOrder.verify(mTetherHelper).notifyInterfaceStateChange(
+ IFACE_NAME, mTestedSm, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
+ }
+ }
+
+ @Test
+ public void usbShouldBeTornDownOnTetherError() throws Exception {
+ initStateMachine(ConnectivityManager.TETHERING_USB);
+
+ doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME);
+ dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
+ InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mTetherHelper);
+ usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
+ usbTeardownOrder.verify(mNMService).setInterfaceConfig(
+ IFACE_NAME, mInterfaceConfiguration);
+ usbTeardownOrder.verify(mTetherHelper).notifyInterfaceStateChange(
+ IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_TETHER_IFACE_ERROR);
+ }
+
+ @Test
+ public void shouldTearDownUsbOnUpstreamError() throws Exception {
+ initTetheredStateMachine(ConnectivityManager.TETHERING_USB, null);
+
+ doThrow(RemoteException.class).when(mNMService).enableNat(anyString(), anyString());
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+ InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mTetherHelper);
+ usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
+ usbTeardownOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
+ usbTeardownOrder.verify(mTetherHelper).notifyInterfaceStateChange(
+ IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_ENABLE_NAT_ERROR);
+ }
+
+ /**
+ * Send a command to the state machine under test, and run the event loop to idle.
+ *
+ * @param command One of the TetherInterfaceStateMachine.CMD_* constants.
+ */
+ private void dispatchCommand(int command) {
+ mTestedSm.sendMessage(command);
+ mLooper.dispatchAll();
+ }
+
+ /**
+ * Special override to tell the state machine that the upstream interface has changed.
+ *
+ * @see #dispatchCommand(int)
+ * @param upstreamIface String name of upstream interface (or null)
+ */
+ private void dispatchTetherConnectionChanged(String upstreamIface) {
+ mTestedSm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED,
+ upstreamIface);
+ mLooper.dispatchAll();
+ }
+}
\ No newline at end of file
diff --git a/tests/utils/testutils/Android.mk b/tests/utils/testutils/Android.mk
new file mode 100644
index 0000000..4bfd234
--- /dev/null
+++ b/tests/utils/testutils/Android.mk
@@ -0,0 +1,30 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := frameworks-base-testutils
+LOCAL_MODULE_TAG := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under,java)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ mockito-target-minus-junit4
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/utils/testutils/java/android/app/test/MockAnswerUtil.java b/tests/utils/testutils/java/android/app/test/MockAnswerUtil.java
new file mode 100644
index 0000000..746c77d
--- /dev/null
+++ b/tests/utils/testutils/java/android/app/test/MockAnswerUtil.java
@@ -0,0 +1,62 @@
+/*
+ * 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.app.test;
+
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+/**
+ * Utilities for creating Answers for mock objects
+ */
+public class MockAnswerUtil {
+
+ /**
+ * Answer that calls the method in the Answer called "answer" that matches the type signature of
+ * the method being answered. An error will be thrown at runtime if the signature does not match
+ * exactly.
+ */
+ public static class AnswerWithArguments implements Answer<Object> {
+ @Override
+ public final Object answer(InvocationOnMock invocation) throws Throwable {
+ Method method = invocation.getMethod();
+ try {
+ Method implementation = getClass().getMethod("answer", method.getParameterTypes());
+ if (!implementation.getReturnType().equals(method.getReturnType())) {
+ throw new RuntimeException("Found answer method does not have expected return "
+ + "type. Expected: " + method.getReturnType() + ", got "
+ + implementation.getReturnType());
+ }
+ Object[] args = invocation.getArguments();
+ try {
+ return implementation.invoke(this, args);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException("Error invoking answer method", e);
+ } catch (InvocationTargetException e) {
+ throw e.getCause();
+ }
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException("Could not find answer method with the expected args "
+ + Arrays.toString(method.getParameterTypes()), e);
+ }
+ }
+ }
+
+}
diff --git a/tests/utils/testutils/java/android/app/test/TestAlarmManager.java b/tests/utils/testutils/java/android/app/test/TestAlarmManager.java
new file mode 100644
index 0000000..e90ea1e
--- /dev/null
+++ b/tests/utils/testutils/java/android/app/test/TestAlarmManager.java
@@ -0,0 +1,188 @@
+/*
+ * 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.app.test;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+
+import android.app.AlarmManager;
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
+import android.os.Handler;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Creates an AlarmManager whose alarm dispatch can be controlled
+ * Currently only supports alarm listeners
+ *
+ * Alarm listeners will be dispatched to the handler provided or will
+ * be dispatched immediately if they would have been sent to the main
+ * looper (handler was null).
+ */
+public class TestAlarmManager {
+ private final AlarmManager mAlarmManager;
+ private final List<PendingAlarm> mPendingAlarms;
+
+ public TestAlarmManager() throws Exception {
+ mPendingAlarms = new ArrayList<>();
+
+ mAlarmManager = mock(AlarmManager.class);
+ doAnswer(new SetListenerAnswer()).when(mAlarmManager).set(anyInt(), anyLong(), anyString(),
+ any(AlarmManager.OnAlarmListener.class), any(Handler.class));
+ doAnswer(new SetListenerAnswer()).when(mAlarmManager).setExact(anyInt(), anyLong(),
+ anyString(), any(AlarmManager.OnAlarmListener.class), any(Handler.class));
+ doAnswer(new CancelListenerAnswer())
+ .when(mAlarmManager).cancel(any(AlarmManager.OnAlarmListener.class));
+ }
+
+ public AlarmManager getAlarmManager() {
+ return mAlarmManager;
+ }
+
+ /**
+ * Dispatch a pending alarm with the given tag
+ * @return if any alarm was dispatched
+ */
+ public boolean dispatch(String tag) {
+ for (int i = 0; i < mPendingAlarms.size(); ++i) {
+ PendingAlarm alarm = mPendingAlarms.get(i);
+ if (Objects.equals(tag, alarm.getTag())) {
+ mPendingAlarms.remove(i);
+ alarm.dispatch();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @return if an alarm with the given tag is pending
+ */
+ public boolean isPending(String tag) {
+ for (int i = 0; i < mPendingAlarms.size(); ++i) {
+ PendingAlarm alarm = mPendingAlarms.get(i);
+ if (Objects.equals(tag, alarm.getTag())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @return trigger time of an pending alarm with the given tag
+ * -1 if no pending alarm with the given tag
+ */
+ public long getTriggerTimeMillis(String tag) {
+ for (int i = 0; i < mPendingAlarms.size(); ++i) {
+ PendingAlarm alarm = mPendingAlarms.get(i);
+ if (Objects.equals(tag, alarm.getTag())) {
+ return alarm.getTriggerTimeMillis();
+ }
+ }
+ return -1;
+ }
+
+ private static class PendingAlarm {
+ private final int mType;
+ private final long mTriggerAtMillis;
+ private final String mTag;
+ private final Runnable mCallback;
+
+ public PendingAlarm(int type, long triggerAtMillis, String tag, Runnable callback) {
+ mType = type;
+ mTriggerAtMillis = triggerAtMillis;
+ mTag = tag;
+ mCallback = callback;
+ }
+
+ public void dispatch() {
+ if (mCallback != null) {
+ mCallback.run();
+ }
+ }
+
+ public Runnable getCallback() {
+ return mCallback;
+ }
+
+ public String getTag() {
+ return mTag;
+ }
+
+ public long getTriggerTimeMillis() {
+ return mTriggerAtMillis;
+ }
+ }
+
+ private class SetListenerAnswer extends AnswerWithArguments {
+ public void answer(int type, long triggerAtMillis, String tag,
+ AlarmManager.OnAlarmListener listener, Handler handler) {
+ mPendingAlarms.add(new PendingAlarm(type, triggerAtMillis, tag,
+ new AlarmListenerRunnable(listener, handler)));
+ }
+ }
+
+ private class CancelListenerAnswer extends AnswerWithArguments {
+ public void answer(AlarmManager.OnAlarmListener listener) {
+ Iterator<PendingAlarm> alarmItr = mPendingAlarms.iterator();
+ while (alarmItr.hasNext()) {
+ PendingAlarm alarm = alarmItr.next();
+ if (alarm.getCallback() instanceof AlarmListenerRunnable) {
+ AlarmListenerRunnable alarmCallback =
+ (AlarmListenerRunnable) alarm.getCallback();
+ if (alarmCallback.getListener() == listener) {
+ alarmItr.remove();
+ }
+ }
+ }
+ }
+ }
+
+ private static class AlarmListenerRunnable implements Runnable {
+ private final AlarmManager.OnAlarmListener mListener;
+ private final Handler mHandler;
+ public AlarmListenerRunnable(AlarmManager.OnAlarmListener listener, Handler handler) {
+ mListener = listener;
+ mHandler = handler;
+ }
+
+ public AlarmManager.OnAlarmListener getListener() {
+ return mListener;
+ }
+
+ @Override
+ public void run() {
+ if (mHandler != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onAlarm();
+ }
+ });
+ } else { // normally gets dispatched in main looper
+ mListener.onAlarm();
+ }
+ }
+ }
+}
diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java
new file mode 100644
index 0000000..e8ceb4a
--- /dev/null
+++ b/tests/utils/testutils/java/android/os/test/TestLooper.java
@@ -0,0 +1,283 @@
+/*
+ * 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.os.test;
+
+import static org.junit.Assert.assertTrue;
+
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Creates a looper whose message queue can be manipulated
+ * This allows testing code that uses a looper to dispatch messages in a deterministic manner
+ * Creating a TestLooper will also install it as the looper for the current thread
+ */
+public class TestLooper {
+ protected final Looper mLooper;
+
+ private static final Constructor<Looper> LOOPER_CONSTRUCTOR;
+ private static final Field THREAD_LOCAL_LOOPER_FIELD;
+ private static final Field MESSAGE_QUEUE_MESSAGES_FIELD;
+ private static final Field MESSAGE_NEXT_FIELD;
+ private static final Field MESSAGE_WHEN_FIELD;
+ private static final Method MESSAGE_MARK_IN_USE_METHOD;
+ private static final String TAG = "TestLooper";
+
+ private AutoDispatchThread mAutoDispatchThread;
+
+ static {
+ try {
+ LOOPER_CONSTRUCTOR = Looper.class.getDeclaredConstructor(Boolean.TYPE);
+ LOOPER_CONSTRUCTOR.setAccessible(true);
+ THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal");
+ THREAD_LOCAL_LOOPER_FIELD.setAccessible(true);
+ MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
+ MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
+ MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
+ MESSAGE_NEXT_FIELD.setAccessible(true);
+ MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
+ MESSAGE_WHEN_FIELD.setAccessible(true);
+ MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse");
+ MESSAGE_MARK_IN_USE_METHOD.setAccessible(true);
+ } catch (NoSuchFieldException | NoSuchMethodException e) {
+ throw new RuntimeException("Failed to initialize TestLooper", e);
+ }
+ }
+
+
+ public TestLooper() {
+ try {
+ mLooper = LOOPER_CONSTRUCTOR.newInstance(false);
+
+ ThreadLocal<Looper> threadLocalLooper = (ThreadLocal<Looper>) THREAD_LOCAL_LOOPER_FIELD
+ .get(null);
+ threadLocalLooper.set(mLooper);
+ } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
+ throw new RuntimeException("Reflection error constructing or accessing looper", e);
+ }
+ }
+
+ public Looper getLooper() {
+ return mLooper;
+ }
+
+ private Message getMessageLinkedList() {
+ try {
+ MessageQueue queue = mLooper.getQueue();
+ return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException("Access failed in TestLooper: get - MessageQueue.mMessages",
+ e);
+ }
+ }
+
+ public void moveTimeForward(long milliSeconds) {
+ try {
+ Message msg = getMessageLinkedList();
+ while (msg != null) {
+ long updatedWhen = msg.getWhen() - milliSeconds;
+ if (updatedWhen < 0) {
+ updatedWhen = 0;
+ }
+ MESSAGE_WHEN_FIELD.set(msg, updatedWhen);
+ msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
+ }
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException("Access failed in TestLooper: set - Message.when", e);
+ }
+ }
+
+ private Message messageQueueNext() {
+ try {
+ long now = SystemClock.uptimeMillis();
+
+ Message prevMsg = null;
+ Message msg = getMessageLinkedList();
+ if (msg != null && msg.getTarget() == null) {
+ // Stalled by a barrier. Find the next asynchronous message in
+ // the queue.
+ do {
+ prevMsg = msg;
+ msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
+ } while (msg != null && !msg.isAsynchronous());
+ }
+ if (msg != null) {
+ if (now >= msg.getWhen()) {
+ // Got a message.
+ if (prevMsg != null) {
+ MESSAGE_NEXT_FIELD.set(prevMsg, MESSAGE_NEXT_FIELD.get(msg));
+ } else {
+ MESSAGE_QUEUE_MESSAGES_FIELD.set(mLooper.getQueue(),
+ MESSAGE_NEXT_FIELD.get(msg));
+ }
+ MESSAGE_NEXT_FIELD.set(msg, null);
+ MESSAGE_MARK_IN_USE_METHOD.invoke(msg);
+ return msg;
+ }
+ }
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ throw new RuntimeException("Access failed in TestLooper", e);
+ }
+
+ return null;
+ }
+
+ /**
+ * @return true if there are pending messages in the message queue
+ */
+ public synchronized boolean isIdle() {
+ Message messageList = getMessageLinkedList();
+
+ return messageList != null && SystemClock.uptimeMillis() >= messageList.getWhen();
+ }
+
+ /**
+ * @return the next message in the Looper's message queue or null if there is none
+ */
+ public synchronized Message nextMessage() {
+ if (isIdle()) {
+ return messageQueueNext();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Dispatch the next message in the queue
+ * Asserts that there is a message in the queue
+ */
+ public synchronized void dispatchNext() {
+ assertTrue(isIdle());
+ Message msg = messageQueueNext();
+ if (msg == null) {
+ return;
+ }
+ msg.getTarget().dispatchMessage(msg);
+ }
+
+ /**
+ * Dispatch all messages currently in the queue
+ * Will not fail if there are no messages pending
+ * @return the number of messages dispatched
+ */
+ public synchronized int dispatchAll() {
+ int count = 0;
+ while (isIdle()) {
+ dispatchNext();
+ ++count;
+ }
+ return count;
+ }
+
+ /**
+ * Thread used to dispatch messages when the main thread is blocked waiting for a response.
+ */
+ private class AutoDispatchThread extends Thread {
+ private static final int MAX_LOOPS = 100;
+ private static final int LOOP_SLEEP_TIME_MS = 10;
+
+ private RuntimeException mAutoDispatchException = null;
+
+ /**
+ * Run method for the auto dispatch thread.
+ * The thread loops a maximum of MAX_LOOPS times with a 10ms sleep between loops.
+ * The thread continues looping and attempting to dispatch all messages until at
+ * least one message has been dispatched.
+ */
+ @Override
+ public void run() {
+ int dispatchCount = 0;
+ for (int i = 0; i < MAX_LOOPS; i++) {
+ try {
+ dispatchCount = dispatchAll();
+ } catch (RuntimeException e) {
+ mAutoDispatchException = e;
+ }
+ Log.d(TAG, "dispatched " + dispatchCount + " messages");
+ if (dispatchCount > 0) {
+ return;
+ }
+ try {
+ Thread.sleep(LOOP_SLEEP_TIME_MS);
+ } catch (InterruptedException e) {
+ mAutoDispatchException = new IllegalStateException(
+ "stopAutoDispatch called before any messages were dispatched.");
+ return;
+ }
+ }
+ Log.e(TAG, "AutoDispatchThread did not dispatch any messages.");
+ mAutoDispatchException = new IllegalStateException(
+ "TestLooper did not dispatch any messages before exiting.");
+ }
+
+ /**
+ * Method allowing the TestLooper to pass any exceptions thrown by the thread to be passed
+ * to the main thread.
+ *
+ * @return RuntimeException Exception created by stopping without dispatching a message
+ */
+ public RuntimeException getException() {
+ return mAutoDispatchException;
+ }
+ }
+
+ /**
+ * Create and start a new AutoDispatchThread if one is not already running.
+ */
+ public void startAutoDispatch() {
+ if (mAutoDispatchThread != null) {
+ throw new IllegalStateException(
+ "startAutoDispatch called with the AutoDispatchThread already running.");
+ }
+ mAutoDispatchThread = new AutoDispatchThread();
+ mAutoDispatchThread.start();
+ }
+
+ /**
+ * If an AutoDispatchThread is currently running, stop and clean up.
+ */
+ public void stopAutoDispatch() {
+ if (mAutoDispatchThread != null) {
+ if (mAutoDispatchThread.isAlive()) {
+ mAutoDispatchThread.interrupt();
+ }
+ try {
+ mAutoDispatchThread.join();
+ } catch (InterruptedException e) {
+ // Catch exception from join.
+ }
+
+ RuntimeException e = mAutoDispatchThread.getException();
+ mAutoDispatchThread = null;
+ if (e != null) {
+ throw e;
+ }
+ } else {
+ // stopAutoDispatch was called when startAutoDispatch has not created a new thread.
+ throw new IllegalStateException(
+ "stopAutoDispatch called without startAutoDispatch.");
+ }
+ }
+}
diff --git a/tests/utils/testutils/java/android/os/test/TestLooperTest.java b/tests/utils/testutils/java/android/os/test/TestLooperTest.java
new file mode 100644
index 0000000..40d83b5
--- /dev/null
+++ b/tests/utils/testutils/java/android/os/test/TestLooperTest.java
@@ -0,0 +1,371 @@
+/*
+ * 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.os.test;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ErrorCollector;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Test TestLooperAbstractTime which provides control over "time". Note that
+ * real-time is being used as well. Therefore small time increments are NOT
+ * reliable. All tests are in "K" units (i.e. *1000).
+ */
+
+@SmallTest
+public class TestLooperTest {
+ private TestLooper mTestLooper;
+ private Handler mHandler;
+ private Handler mHandlerSpy;
+
+ @Rule
+ public ErrorCollector collector = new ErrorCollector();
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mTestLooper = new TestLooper();
+ mHandler = new Handler(mTestLooper.getLooper());
+ mHandlerSpy = spy(mHandler);
+ }
+
+ /**
+ * Basic test with no time stamps: dispatch 4 messages, check that all 4
+ * delivered (in correct order).
+ */
+ @Test
+ public void testNoTimeMovement() {
+ final int messageA = 1;
+ final int messageB = 2;
+ final int messageC = 3;
+
+ InOrder inOrder = inOrder(mHandlerSpy);
+ ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+
+ mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+ mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+ mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
+ mHandlerSpy.sendMessage(mHandler.obtainMessage(messageC));
+ mTestLooper.dispatchAll();
+
+ inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+ collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
+ inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+ collector.checkThat("2: messageA", messageA, equalTo(messageCaptor.getValue().what));
+ inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+ collector.checkThat("3: messageB", messageB, equalTo(messageCaptor.getValue().what));
+ inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+ collector.checkThat("4: messageC", messageC, equalTo(messageCaptor.getValue().what));
+
+ inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
+ }
+
+ /**
+ * Test message sequence: A, B, C@5K, A@10K. Don't move time.
+ * <p>
+ * Expected: only get A, B
+ */
+ @Test
+ public void testDelayedDispatchNoTimeMove() {
+ final int messageA = 1;
+ final int messageB = 2;
+ final int messageC = 3;
+
+ InOrder inOrder = inOrder(mHandlerSpy);
+ ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+
+ mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+ mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
+ mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
+ mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 10000);
+ mTestLooper.dispatchAll();
+
+ inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+ collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
+ inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+ collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
+
+ inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
+ }
+
+ /**
+ * Test message sequence: A, B, C@5K, A@10K, Advance time by 5K.
+ * <p>
+ * Expected: only get A, B, C
+ */
+ @Test
+ public void testDelayedDispatchAdvanceTimeOnce() {
+ final int messageA = 1;
+ final int messageB = 2;
+ final int messageC = 3;
+
+ InOrder inOrder = inOrder(mHandlerSpy);
+ ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+
+ mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+ mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
+ mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
+ mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 10000);
+ mTestLooper.moveTimeForward(5000);
+ mTestLooper.dispatchAll();
+
+ inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+ collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
+ inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+ collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
+ inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+ collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
+
+ inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
+ }
+
+ /**
+ * Test message sequence: A, B, C@5K, Advance time by 4K, A@1K, B@2K Advance
+ * time by 1K.
+ * <p>
+ * Expected: get A, B, C, A
+ */
+ @Test
+ public void testDelayedDispatchAdvanceTimeTwice() {
+ final int messageA = 1;
+ final int messageB = 2;
+ final int messageC = 3;
+
+ InOrder inOrder = inOrder(mHandlerSpy);
+ ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+
+ mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+ mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
+ mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
+ mTestLooper.moveTimeForward(4000);
+ mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 1000);
+ mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageB), 2000);
+ mTestLooper.moveTimeForward(1000);
+ mTestLooper.dispatchAll();
+
+ inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+ collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
+ inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+ collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
+ inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+ collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
+ inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+ collector.checkThat("4: messageA", messageA, equalTo(messageCaptor.getValue().what));
+
+ inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
+ }
+
+ /**
+ * Test message sequence: A, B, C@5K, Advance time by 4K, A@5K, B@2K Advance
+ * time by 3K.
+ * <p>
+ * Expected: get A, B, C, B
+ */
+ @Test
+ public void testDelayedDispatchReverseOrder() {
+ final int messageA = 1;
+ final int messageB = 2;
+ final int messageC = 3;
+
+ InOrder inOrder = inOrder(mHandlerSpy);
+ ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+
+ mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+ mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
+ mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
+ mTestLooper.moveTimeForward(4000);
+ mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 5000);
+ mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageB), 2000);
+ mTestLooper.moveTimeForward(3000);
+ mTestLooper.dispatchAll();
+
+ inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+ collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
+ inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+ collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
+ inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+ collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
+ inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+ collector.checkThat("4: messageB", messageB, equalTo(messageCaptor.getValue().what));
+
+ inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
+ }
+
+ /**
+ * Test message sequence: A, B, C@5K, Advance time by 4K, dispatch all,
+ * A@5K, B@2K Advance time by 3K, dispatch all.
+ * <p>
+ * Expected: get A, B after first dispatch; then C, B after second dispatch
+ */
+ @Test
+ public void testDelayedDispatchAllMultipleTimes() {
+ final int messageA = 1;
+ final int messageB = 2;
+ final int messageC = 3;
+
+ InOrder inOrder = inOrder(mHandlerSpy);
+ ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+
+ mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+ mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
+ mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
+ mTestLooper.moveTimeForward(4000);
+ mTestLooper.dispatchAll();
+
+ inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+ collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
+ inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+ collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
+
+ mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 5000);
+ mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageB), 2000);
+ mTestLooper.moveTimeForward(3000);
+ mTestLooper.dispatchAll();
+
+ inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+ collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
+ inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+ collector.checkThat("4: messageB", messageB, equalTo(messageCaptor.getValue().what));
+
+ inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
+ }
+
+ /**
+ * Test AutoDispatch for a single message.
+ * This test would ideally use the Channel sendMessageSynchronously. At this time, the setup to
+ * get a working test channel is cumbersome. Until this is fixed, we substitute with a
+ * sendMessage followed by a blocking call. The main test thread blocks until the test handler
+ * receives the test message (messageA) and sets a boolean true. Once the boolean is true, the
+ * main thread will exit the busy wait loop, stop autoDispatch and check the assert.
+ *
+ * Enable AutoDispatch, add message, block on message being handled and stop AutoDispatch.
+ * <p>
+ * Expected: handleMessage is called for messageA and stopAutoDispatch is called.
+ */
+ @Test
+ public void testAutoDispatchWithSingleMessage() {
+ final int mLoopSleepTimeMs = 5;
+
+ final int messageA = 1;
+
+ TestLooper mockLooper = new TestLooper();
+ class TestHandler extends Handler {
+ public volatile boolean handledMessage = false;
+ TestHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == messageA) {
+ handledMessage = true;
+ }
+ }
+ }
+
+ TestHandler testHandler = new TestHandler(mockLooper.getLooper());
+ mockLooper.startAutoDispatch();
+ testHandler.sendMessage(testHandler.obtainMessage(messageA));
+ while (!testHandler.handledMessage) {
+ // Block until message is handled
+ try {
+ Thread.sleep(mLoopSleepTimeMs);
+ } catch (InterruptedException e) {
+ // Interrupted while sleeping.
+ }
+ }
+ mockLooper.stopAutoDispatch();
+ assertTrue("TestHandler should have received messageA", testHandler.handledMessage);
+ }
+
+ /**
+ * Test starting AutoDispatch while already running throws IllegalStateException
+ * Enable AutoDispatch two times in a row.
+ * <p>
+ * Expected: catch IllegalStateException on second call.
+ */
+ @Test(expected = IllegalStateException.class)
+ public void testRepeatedStartAutoDispatchThrowsException() {
+ mTestLooper.startAutoDispatch();
+ mTestLooper.startAutoDispatch();
+ }
+
+ /**
+ * Test stopping AutoDispatch without previously starting throws IllegalStateException
+ * Stop AutoDispatch
+ * <p>
+ * Expected: catch IllegalStateException on second call.
+ */
+ @Test(expected = IllegalStateException.class)
+ public void testStopAutoDispatchWithoutStartThrowsException() {
+ mTestLooper.stopAutoDispatch();
+ }
+
+ /**
+ * Test AutoDispatch exits and does not dispatch a later message.
+ * Start and stop AutoDispatch then add a message.
+ * <p>
+ * Expected: After AutoDispatch is stopped, dispatchAll will return 1.
+ */
+ @Test
+ public void testAutoDispatchStopsCleanlyWithoutDispatchingAMessage() {
+ final int messageA = 1;
+
+ InOrder inOrder = inOrder(mHandlerSpy);
+ ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+
+ mTestLooper.startAutoDispatch();
+ try {
+ mTestLooper.stopAutoDispatch();
+ } catch (IllegalStateException e) {
+ // Stopping without a dispatch will throw an exception.
+ }
+
+ mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+ assertEquals("One message should be dispatched", 1, mTestLooper.dispatchAll());
+ }
+
+ /**
+ * Test AutoDispatch throws an exception when no messages are dispatched.
+ * Start and stop AutoDispatch
+ * <p>
+ * Expected: Exception is thrown with the stopAutoDispatch call.
+ */
+ @Test(expected = IllegalStateException.class)
+ public void testAutoDispatchThrowsExceptionWhenNoMessagesDispatched() {
+ mTestLooper.startAutoDispatch();
+ mTestLooper.stopAutoDispatch();
+ }
+}
diff --git a/tests/utils/testutils/java/com/android/internal/util/test/BidirectionalAsyncChannel.java b/tests/utils/testutils/java/com/android/internal/util/test/BidirectionalAsyncChannel.java
new file mode 100644
index 0000000..25cd5b9
--- /dev/null
+++ b/tests/utils/testutils/java/com/android/internal/util/test/BidirectionalAsyncChannel.java
@@ -0,0 +1,96 @@
+/*
+ * 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 com.android.internal.util.test;
+
+import static org.junit.Assert.assertEquals;
+
+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;
+
+
+/**
+ * Provides an AsyncChannel interface that implements the connection initiating half of a
+ * bidirectional channel as described in {@link com.android.internal.util.AsyncChannel}.
+ */
+public class BidirectionalAsyncChannel {
+ private static final String TAG = "BidirectionalAsyncChannel";
+
+ private AsyncChannel mChannel;
+ public enum ChannelState { DISCONNECTED, HALF_CONNECTED, CONNECTED, FAILURE };
+ private ChannelState mState = ChannelState.DISCONNECTED;
+
+ public void assertConnected() {
+ assertEquals("AsyncChannel was not fully connected", ChannelState.CONNECTED, mState);
+ }
+
+ public void connect(final Looper looper, final Messenger messenger,
+ final Handler incomingMessageHandler) {
+ assertEquals("AsyncChannel must be disconnected to connect",
+ ChannelState.DISCONNECTED, mState);
+ mChannel = new AsyncChannel();
+ Handler rawMessageHandler = new Handler(looper) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
+ if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+ Log.d(TAG, "Successfully half connected " + this);
+ mChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
+ mState = ChannelState.HALF_CONNECTED;
+ } else {
+ Log.d(TAG, "Failed to connect channel " + this);
+ mState = ChannelState.FAILURE;
+ mChannel = null;
+ }
+ break;
+ case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
+ mState = ChannelState.CONNECTED;
+ Log.d(TAG, "Channel fully connected" + this);
+ break;
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+ mState = ChannelState.DISCONNECTED;
+ mChannel = null;
+ Log.d(TAG, "Channel disconnected" + this);
+ break;
+ default:
+ incomingMessageHandler.handleMessage(msg);
+ break;
+ }
+ }
+ };
+ mChannel.connect(null, rawMessageHandler, messenger);
+ }
+
+ public void disconnect() {
+ assertEquals("AsyncChannel must be connected to disconnect",
+ ChannelState.CONNECTED, mState);
+ mChannel.sendMessage(AsyncChannel.CMD_CHANNEL_DISCONNECT);
+ mState = ChannelState.DISCONNECTED;
+ mChannel = null;
+ }
+
+ public void sendMessage(Message msg) {
+ assertEquals("AsyncChannel must be connected to send messages",
+ ChannelState.CONNECTED, mState);
+ mChannel.sendMessage(msg);
+ }
+}
diff --git a/tests/utils/testutils/java/com/android/internal/util/test/BidirectionalAsyncChannelServer.java b/tests/utils/testutils/java/com/android/internal/util/test/BidirectionalAsyncChannelServer.java
new file mode 100644
index 0000000..49c8332
--- /dev/null
+++ b/tests/utils/testutils/java/com/android/internal/util/test/BidirectionalAsyncChannelServer.java
@@ -0,0 +1,86 @@
+/*
+ * 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 com.android.internal.util.test;
+
+import android.content.Context;
+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 java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Provides an interface for the server side implementation of a bidirectional channel as described
+ * in {@link com.android.internal.util.AsyncChannel}.
+ */
+public class BidirectionalAsyncChannelServer {
+
+ private static final String TAG = "BidirectionalAsyncChannelServer";
+
+ // Keeps track of incoming clients, which are identifiable by their messengers.
+ private final Map<Messenger, AsyncChannel> mClients = new HashMap<>();
+
+ private Messenger mMessenger;
+
+ public BidirectionalAsyncChannelServer(final Context context, final Looper looper,
+ final Handler messageHandler) {
+ Handler handler = new Handler(looper) {
+ @Override
+ public void handleMessage(Message msg) {
+ AsyncChannel channel = mClients.get(msg.replyTo);
+ switch (msg.what) {
+ case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
+ if (channel != null) {
+ Log.d(TAG, "duplicate client connection: " + msg.sendingUid);
+ channel.replyToMessage(msg,
+ AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+ AsyncChannel.STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED);
+ } else {
+ channel = new AsyncChannel();
+ mClients.put(msg.replyTo, channel);
+ channel.connected(context, this, msg.replyTo);
+ channel.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+ AsyncChannel.STATUS_SUCCESSFUL);
+ }
+ break;
+ case AsyncChannel.CMD_CHANNEL_DISCONNECT:
+ channel.disconnect();
+ break;
+
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+ mClients.remove(msg.replyTo);
+ break;
+
+ default:
+ messageHandler.handleMessage(msg);
+ break;
+ }
+ }
+ };
+ mMessenger = new Messenger(handler);
+ }
+
+ public Messenger getMessenger() {
+ return mMessenger;
+ }
+
+}
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 9268a2b..3674f0f 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -169,5 +169,11 @@
void factoryReset();
Network getCurrentNetwork();
+
+ byte[] retrieveBackupData();
+
+ void restoreBackupData(in byte[] data);
+
+ void restoreSupplicantBackupData(in byte[] supplicantData, in byte[] ipConfigData);
}
diff --git a/wifi/java/android/net/wifi/RttManager.aidl b/wifi/java/android/net/wifi/RttManager.aidl
index 5c6d447..9479cf0 100644
--- a/wifi/java/android/net/wifi/RttManager.aidl
+++ b/wifi/java/android/net/wifi/RttManager.aidl
@@ -15,4 +15,7 @@
*/
package android.net.wifi;
-parcelable RttManager.RttCapabilities;
\ No newline at end of file
+
+parcelable RttManager.RttCapabilities;
+parcelable RttManager.ParcelableRttResults;
+parcelable RttManager.ParcelableRttParams;
diff --git a/wifi/java/android/net/wifi/RttManager.java b/wifi/java/android/net/wifi/RttManager.java
index 590ff1b..c6f1f68 100644
--- a/wifi/java/android/net/wifi/RttManager.java
+++ b/wifi/java/android/net/wifi/RttManager.java
@@ -472,6 +472,34 @@
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 */
@@ -727,6 +755,51 @@
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() {
@@ -1295,4 +1368,3 @@
}
}
-
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 9d0c20c..7c5276c 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -806,7 +806,7 @@
* Quality network selection status String (for debug purpose). Use Quality network
* selection status value as index to extec the corresponding debug string
*/
- private static final String[] QUALITY_NETWORK_SELECTION_STATUS = {
+ public static final String[] QUALITY_NETWORK_SELECTION_STATUS = {
"NETWORK_SELECTION_ENABLED",
"NETWORK_SELECTION_TEMPORARY_DISABLED",
"NETWORK_SELECTION_PERMANENTLY_DISABLED"};
@@ -817,6 +817,7 @@
*/
public static final int NETWORK_SELECTION_ENABLE = 0;
/**
+ * @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;
@@ -837,40 +838,51 @@
*/
public static final int DISABLED_DNS_FAILURE = 5;
/**
+ * This network is disabled because we started WPS
+ */
+ public static final int DISABLED_WPS_START = 6;
+ /**
* This network is disabled because EAP-TLS failure
*/
- public static final int DISABLED_TLS_VERSION_MISMATCH = 6;
+ public static final int DISABLED_TLS_VERSION_MISMATCH = 7;
/**
- * This network is disabled due to WifiManager disable it explicitly
+ * This network is disabled due to absence of user credentials
*/
- public static final int DISABLED_AUTHENTICATION_NO_CREDENTIALS = 7;
+ public static final int DISABLED_AUTHENTICATION_NO_CREDENTIALS = 8;
/**
* This network is disabled because no Internet connected and user do not want
*/
- public static final int DISABLED_NO_INTERNET = 8;
+ public static final int DISABLED_NO_INTERNET = 9;
/**
* This network is disabled due to WifiManager disable it explicitly
*/
- public static final int DISABLED_BY_WIFI_MANAGER = 9;
+ public static final int DISABLED_BY_WIFI_MANAGER = 10;
+ /**
+ * This network is disabled due to user switching
+ */
+ public static final int DISABLED_DUE_TO_USER_SWITCH = 11;
/**
* This Maximum disable reason value
*/
- public static final int NETWORK_SELECTION_DISABLED_MAX = 10;
+ public static final int NETWORK_SELECTION_DISABLED_MAX = 12;
/**
* Quality network selection disable reason String (for debug purpose)
*/
- private static final String[] QUALITY_NETWORK_SELECTION_DISABLE_REASON = {
+ public static final String[] QUALITY_NETWORK_SELECTION_DISABLE_REASON = {
"NETWORK_SELECTION_ENABLE",
- "NETWORK_SELECTION_DISABLED_BAD_LINK",
+ "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_WPS_START",
"NETWORK_SELECTION_DISABLED_TLS_VERSION",
"NETWORK_SELECTION_DISABLED_AUTHENTICATION_NO_CREDENTIALS",
"NETWORK_SELECTION_DISABLED_NO_INTERNET",
- "NETWORK_SELECTION_DISABLED_BY_WIFI_MANAGER"};
+ "NETWORK_SELECTION_DISABLED_BY_WIFI_MANAGER",
+ "NETWORK_SELECTION_DISABLED_BY_USER_SWITCH"
+ };
/**
* Invalid time stamp for network selection disable
@@ -1054,7 +1066,7 @@
return mHasEverConnected;
}
- private NetworkSelectionStatus() {
+ public NetworkSelectionStatus() {
// previously stored configs will not have this parameter, so we default to false.
mHasEverConnected = false;
};
@@ -1254,6 +1266,8 @@
}
mTemporarilyDisabledTimestamp = source.mTemporarilyDisabledTimestamp;
mNetworkSelectionBSSID = source.mNetworkSelectionBSSID;
+ setCandidate(source.getCandidate());
+ setCandidateScore(source.getCandidateScore());
setConnectChoice(source.getConnectChoice());
setConnectChoiceTimestamp(source.getConnectChoiceTimestamp());
setHasEverConnected(source.getHasEverConnected());
@@ -1302,7 +1316,7 @@
* @hide
* network selection related member
*/
- private final NetworkSelectionStatus mNetworkSelectionStatus = new NetworkSelectionStatus();
+ private NetworkSelectionStatus mNetworkSelectionStatus = new NetworkSelectionStatus();
/**
* @hide
@@ -1311,6 +1325,15 @@
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
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index b614a86..08ad35e 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -375,7 +375,15 @@
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;
}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 1cd32b6..19ecbdb 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -561,6 +561,12 @@
public static final String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
/**
+ * 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
@@ -1114,7 +1120,7 @@
/**
* @return true if this adapter supports Neighbour Awareness Network APIs
- * @hide PROPOSED_NAN_API
+ * @hide
*/
public boolean isNanSupported() {
return isFeatureSupported(WIFI_FEATURE_NAN);
@@ -2731,4 +2737,41 @@
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 & ipconfig.txt file.
+ * @hide
+ */
+ public void restoreSupplicantBackupData(byte[] supplicantData, byte[] ipConfigData) {
+ try {
+ mService.restoreSupplicantBackupData(supplicantData, ipConfigData);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 2636c3f..716f1d3 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -656,6 +656,40 @@
void onPnoNetworkFound(ScanResult[] results);
}
+ /**
+ * 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}
+ */
+ 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}
@@ -1129,6 +1163,10 @@
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;
private Context mContext;
private IWifiScanner mService;
diff --git a/wifi/java/android/net/wifi/nan/ConfigRequest.java b/wifi/java/android/net/wifi/nan/ConfigRequest.java
index 23e3754..44544de 100644
--- a/wifi/java/android/net/wifi/nan/ConfigRequest.java
+++ b/wifi/java/android/net/wifi/nan/ConfigRequest.java
@@ -16,15 +16,17 @@
package android.net.wifi.nan;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Defines a request object to configure a Wi-Fi NAN network. Built using
* {@link ConfigRequest.Builder}. Configuration is requested using
- * {@link WifiNanManager#requestConfig(ConfigRequest)}. Note that the actual
- * achieved configuration may be different from the requested configuration -
- * since multiple applications may request different configurations.
+ * {@link WifiNanManager#connect(android.os.Looper, ConfigRequest, WifiNanEventCallback)}.
+ * Note that the actual achieved configuration may be different from the
+ * requested configuration - since different applications may request different
+ * configurations.
*
* @hide PROPOSED_NAN_API
*/
@@ -73,19 +75,28 @@
*/
public final int mClusterHigh;
+ /**
+ * Indicates whether we want to get callbacks when our identity is changed.
+ *
+ * @hide
+ */
+ public final boolean mEnableIdentityChangeCallback;
+
private ConfigRequest(boolean support5gBand, int masterPreference, int clusterLow,
- int clusterHigh) {
+ int clusterHigh, boolean enableIdentityChangeCallback) {
mSupport5gBand = support5gBand;
mMasterPreference = masterPreference;
mClusterLow = clusterLow;
mClusterHigh = clusterHigh;
+ mEnableIdentityChangeCallback = enableIdentityChangeCallback;
}
@Override
public String toString() {
return "ConfigRequest [mSupport5gBand=" + mSupport5gBand + ", mMasterPreference="
+ mMasterPreference + ", mClusterLow=" + mClusterLow + ", mClusterHigh="
- + mClusterHigh + "]";
+ + mClusterHigh + ", mEnableIdentityChangeCallback=" + mEnableIdentityChangeCallback
+ + "]";
}
@Override
@@ -99,6 +110,7 @@
dest.writeInt(mMasterPreference);
dest.writeInt(mClusterLow);
dest.writeInt(mClusterHigh);
+ dest.writeInt(mEnableIdentityChangeCallback ? 1 : 0);
}
public static final Creator<ConfigRequest> CREATOR = new Creator<ConfigRequest>() {
@@ -113,7 +125,9 @@
int masterPreference = in.readInt();
int clusterLow = in.readInt();
int clusterHigh = in.readInt();
- return new ConfigRequest(support5gBand, masterPreference, clusterLow, clusterHigh);
+ boolean enableIdentityChangeCallback = in.readInt() != 0;
+ return new ConfigRequest(support5gBand, masterPreference, clusterLow, clusterHigh,
+ enableIdentityChangeCallback);
}
};
@@ -130,9 +144,47 @@
ConfigRequest lhs = (ConfigRequest) o;
return mSupport5gBand == lhs.mSupport5gBand && mMasterPreference == lhs.mMasterPreference
+ && mClusterLow == lhs.mClusterLow && mClusterHigh == lhs.mClusterHigh
+ && mEnableIdentityChangeCallback == lhs.mEnableIdentityChangeCallback;
+ }
+
+ /**
+ * Checks for equality of two configuration - but only considering their
+ * on-the-air NAN configuration impact.
+ *
+ * @param o Object to compare to.
+ * @return true if configuration objects have the same on-the-air
+ * configuration, false otherwise.
+ *
+ * @hide
+ */
+ public boolean equalsOnTheAir(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;
}
+ /**
+ * Checks whether the configuration's settings which impact on-air behavior are non-default.
+ *
+ * @return true if any of the on-air-impacting settings are non-default.
+ *
+ * @hide
+ */
+ public boolean isNonDefaultOnTheAir() {
+ return mSupport5gBand || mMasterPreference != 0 || mClusterLow != CLUSTER_ID_MIN
+ || mClusterHigh != CLUSTER_ID_MAX;
+ }
+
@Override
public int hashCode() {
int result = 17;
@@ -141,35 +193,63 @@
result = 31 * result + mMasterPreference;
result = 31 * result + mClusterLow;
result = 31 * result + mClusterHigh;
+ result = 31 * result + (mEnableIdentityChangeCallback ? 1 : 0);
return result;
}
/**
+ * Verifies that the contents of the ConfigRequest are valid. Otherwise
+ * throws an IllegalArgumentException.
+ *
+ * @hide
+ */
+ 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");
+ }
+ }
+
+ /**
* Builder used to build {@link ConfigRequest} objects.
*/
public static final class Builder {
- private boolean mSupport5gBand;
- private int mMasterPreference;
- private int mClusterLow;
- private int mClusterHigh;
+ private boolean mSupport5gBand = false;
+ private int mMasterPreference = 0;
+ private int mClusterLow = CLUSTER_ID_MIN;
+ private int mClusterHigh = CLUSTER_ID_MAX;
+ private boolean mEnableIdentityChangeCallback = false;
/**
- * Default constructor for the Builder.
- */
- public Builder() {
- mSupport5gBand = false;
- mMasterPreference = 0;
- mClusterLow = 0;
- mClusterHigh = CLUSTER_ID_MAX;
- }
-
- /**
- * Specify whether 5G band support is required in this request.
+ * 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(..)}.
+ *
+ * @hide PROPOSED_NAN_SYSTEM_API
*/
public Builder setSupport5gBand(boolean support5gBand) {
mSupport5gBand = support5gBand;
@@ -177,12 +257,15 @@
}
/**
- * Specify the Master Preference requested. The permitted range is 0 to
+ * 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(..)}.
+ *
+ * @hide PROPOSED_NAN_SYSTEM_API
*/
public Builder setMasterPreference(int masterPreference) {
if (masterPreference < 0) {
@@ -202,13 +285,16 @@
* The Cluster ID is generated randomly for new NAN 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 to the value specified by
- * {@link ConfigRequest.Builder#setClusterHigh(int)}. Equality is
+ * 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(..)}.
+ *
+ * @hide PROPOSED_NAN_SYSTEM_API
*/
public Builder setClusterLow(int clusterLow) {
if (clusterLow < CLUSTER_ID_MIN) {
@@ -227,12 +313,15 @@
* 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. Equality
- * is permitted which restricts the Cluster ID to the specified value.
+ * {@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(..)}.
+ *
+ * @hide PROPOSED_NAN_SYSTEM_API
*/
public Builder setClusterHigh(int clusterHigh) {
if (clusterHigh < CLUSTER_ID_MIN) {
@@ -247,6 +336,27 @@
}
/**
+ * Indicate whether or not we want to enable the
+ * {@link WifiNanEventCallback#onIdentityChanged(byte[])} callback. A
+ * device identity is its Discovery MAC address which is randomized at regular intervals.
+ * An application may need to know the MAC address, e.g. when using OOB (out-of-band)
+ * discovery together with NAN connections.
+ * <p>
+ * The callbacks are disabled by default since it may result in additional wake-ups
+ * of the host -
+ * increasing power.
+ *
+ * @param enableIdentityChangeCallback Enable the callback informing
+ * listener when identity is changed.
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setEnableIdentityChangeCallback(boolean enableIdentityChangeCallback) {
+ mEnableIdentityChangeCallback = enableIdentityChangeCallback;
+ return this;
+ }
+
+ /**
* Build {@link ConfigRequest} given the current requests made on the
* builder.
*/
@@ -256,7 +366,8 @@
"Invalid argument combination - must have Cluster Low <= Cluster High");
}
- return new ConfigRequest(mSupport5gBand, mMasterPreference, mClusterLow, mClusterHigh);
+ return new ConfigRequest(mSupport5gBand, mMasterPreference, mClusterLow, mClusterHigh,
+ mEnableIdentityChangeCallback);
}
}
}
diff --git a/wifi/java/android/net/wifi/nan/IWifiNanEventListener.aidl b/wifi/java/android/net/wifi/nan/IWifiNanEventCallback.aidl
similarity index 66%
rename from wifi/java/android/net/wifi/nan/IWifiNanEventListener.aidl
rename to wifi/java/android/net/wifi/nan/IWifiNanEventCallback.aidl
index fa666af..a4e590b 100644
--- a/wifi/java/android/net/wifi/nan/IWifiNanEventListener.aidl
+++ b/wifi/java/android/net/wifi/nan/IWifiNanEventCallback.aidl
@@ -17,16 +17,20 @@
package android.net.wifi.nan;
import android.net.wifi.nan.ConfigRequest;
+import android.net.wifi.RttManager;
/**
* Callback interface that WifiNanManager implements
*
* {@hide}
*/
-oneway interface IWifiNanEventListener
+oneway interface IWifiNanEventCallback
{
- void onConfigCompleted(in ConfigRequest completedConfig);
- void onConfigFailed(in ConfigRequest failedConfig, int reason);
- void onNanDown(int reason);
- void onIdentityChanged();
+ void onConnectSuccess();
+ void onConnectFail(int reason);
+ void onIdentityChanged(in byte[] mac);
+
+ void onRangingSuccess(int rangingId, in RttManager.ParcelableRttResults results);
+ void onRangingFailure(int rangingId, int reason, in String description);
+ void onRangingAborted(int rangingId);
}
diff --git a/wifi/java/android/net/wifi/nan/IWifiNanManager.aidl b/wifi/java/android/net/wifi/nan/IWifiNanManager.aidl
index f382d97..17ec1bc 100644
--- a/wifi/java/android/net/wifi/nan/IWifiNanManager.aidl
+++ b/wifi/java/android/net/wifi/nan/IWifiNanManager.aidl
@@ -19,12 +19,11 @@
import android.app.PendingIntent;
import android.net.wifi.nan.ConfigRequest;
-import android.net.wifi.nan.IWifiNanEventListener;
-import android.net.wifi.nan.IWifiNanSessionListener;
-import android.net.wifi.nan.PublishData;
-import android.net.wifi.nan.PublishSettings;
-import android.net.wifi.nan.SubscribeData;
-import android.net.wifi.nan.SubscribeSettings;
+import android.net.wifi.nan.IWifiNanEventCallback;
+import android.net.wifi.nan.IWifiNanSessionCallback;
+import android.net.wifi.nan.PublishConfig;
+import android.net.wifi.nan.SubscribeConfig;
+import android.net.wifi.RttManager;
/**
* Interface that WifiNanService implements
@@ -33,18 +32,25 @@
*/
interface IWifiNanManager
{
+ // NAN API
+ void enableUsage();
+ void disableUsage();
+ boolean isUsageEnabled();
+
// client API
- void connect(in IBinder binder, in IWifiNanEventListener listener, int events);
- void disconnect(in IBinder binder);
- void requestConfig(in ConfigRequest configRequest);
+ int connect(in IBinder binder, in String callingPackage, in IWifiNanEventCallback callback,
+ in ConfigRequest configRequest);
+ void disconnect(int clientId, in IBinder binder);
+
+ void publish(int clientId, in PublishConfig publishConfig, in IWifiNanSessionCallback callback);
+ void subscribe(int clientId, in SubscribeConfig subscribeConfig,
+ in IWifiNanSessionCallback callback);
// session API
- int createSession(in IWifiNanSessionListener listener, int events);
- void publish(int sessionId, in PublishData publishData, in PublishSettings publishSettings);
- void subscribe(int sessionId, in SubscribeData subscribeData,
- in SubscribeSettings subscribeSettings);
- void sendMessage(int sessionId, int peerId, in byte[] message, int messageLength,
- int messageId);
- void stopSession(int sessionId);
- void destroySession(int sessionId);
+ void updatePublish(int clientId, int sessionId, in PublishConfig publishConfig);
+ void updateSubscribe(int clientId, int sessionId, in SubscribeConfig subscribeConfig);
+ void sendMessage(int clientId, int sessionId, int peerId, in byte[] message, int messageId,
+ int retryCount);
+ void terminateSession(int clientId, int sessionId);
+ int startRanging(int clientId, int sessionId, in RttManager.ParcelableRttParams parms);
}
diff --git a/wifi/java/android/net/wifi/nan/IWifiNanSessionListener.aidl b/wifi/java/android/net/wifi/nan/IWifiNanSessionCallback.aidl
similarity index 65%
rename from wifi/java/android/net/wifi/nan/IWifiNanSessionListener.aidl
rename to wifi/java/android/net/wifi/nan/IWifiNanSessionCallback.aidl
index d60d8ca..ff2c409 100644
--- a/wifi/java/android/net/wifi/nan/IWifiNanSessionListener.aidl
+++ b/wifi/java/android/net/wifi/nan/IWifiNanSessionCallback.aidl
@@ -21,18 +21,16 @@
*
* {@hide}
*/
-oneway interface IWifiNanSessionListener
+oneway interface IWifiNanSessionCallback
{
- void onPublishFail(int reason);
- void onPublishTerminated(int reason);
+ void onSessionStarted(int sessionId);
+ void onSessionConfigSuccess();
+ void onSessionConfigFail(int reason);
+ void onSessionTerminated(int reason);
- void onSubscribeFail(int reason);
- void onSubscribeTerminated(int reason);
-
- void onMatch(int peerId, in byte[] serviceSpecificInfo,
- int serviceSpecificInfoLength, in byte[] matchFilter, int matchFilterLength);
+ void onMatch(int peerId, in byte[] serviceSpecificInfo, in byte[] matchFilter);
void onMessageSendSuccess(int messageId);
void onMessageSendFail(int messageId, int reason);
- void onMessageReceived(int peerId, in byte[] message, int messageLength);
+ void onMessageReceived(int peerId, in byte[] message);
}
diff --git a/wifi/java/android/net/wifi/nan/LvBufferUtils.java b/wifi/java/android/net/wifi/nan/LvBufferUtils.java
new file mode 100644
index 0000000..eb56070
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/LvBufferUtils.java
@@ -0,0 +1,340 @@
+/*
+ * 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.nan;
+
+import android.annotation.Nullable;
+
+import libcore.io.Memory;
+
+import java.nio.ByteOrder;
+import java.util.Iterator;
+
+/**
+ * Utility class to construct and parse byte arrays using the LV format -
+ * Length/Value format. The utilities accept a configuration of the size of
+ * the Length field.
+ *
+ * @hide PROPOSED_NAN_API
+ */
+public class LvBufferUtils {
+ private LvBufferUtils() {
+ // no reason to ever create this class
+ }
+
+ /**
+ * Utility class to construct byte arrays using the LV format - Length/Value.
+ * <p>
+ * A constructor is created specifying the size of the Length (L) field.
+ * <p>
+ * The byte array is either provided (using
+ * {@link LvBufferUtils.LvConstructor#wrap(byte[])}) or allocated (using
+ * {@link LvBufferUtils.LvConstructor#allocate(int)}).
+ * <p>
+ * Values are added to the structure using the {@code LvConstructor.put*()}
+ * methods.
+ * <p>
+ * The final byte array is obtained using {@link LvBufferUtils.LvConstructor#getArray()}.
+ */
+ public static class LvConstructor {
+ private TlvBufferUtils.TlvConstructor mTlvImpl;
+
+ /**
+ * Define a LV constructor with the specified size of the Length (L) field.
+ *
+ * @param lengthSize Number of bytes used for the Length (L) field.
+ * Values of 1 or 2 bytes are allowed.
+ */
+ public LvConstructor(int lengthSize) {
+ mTlvImpl = new TlvBufferUtils.TlvConstructor(0, lengthSize);
+ }
+
+ /**
+ * Set the byte array to be used to construct the LV.
+ *
+ * @param array Byte array to be formatted.
+ * @return The constructor to facilitate chaining
+ * {@code ctr.putXXX(..).putXXX(..)}.
+ */
+ public LvBufferUtils.LvConstructor wrap(@Nullable byte[] array) {
+ mTlvImpl.wrap(array);
+ return this;
+ }
+
+ /**
+ * Allocates a new byte array to be used ot construct a LV.
+ *
+ * @param capacity The size of the byte array to be allocated.
+ * @return The constructor to facilitate chaining
+ * {@code ctr.putXXX(..).putXXX(..)}.
+ */
+ public LvBufferUtils.LvConstructor allocate(int capacity) {
+ mTlvImpl.allocate(capacity);
+ return this;
+ }
+
+ /**
+ * Copies a byte into the LV array.
+ *
+ * @param b The byte to be inserted into the structure.
+ * @return The constructor to facilitate chaining
+ * {@code ctr.putXXX(..).putXXX(..)}.
+ */
+ public LvBufferUtils.LvConstructor putByte(byte b) {
+ mTlvImpl.putByte(0, b);
+ return this;
+ }
+
+ /**
+ * Copies a byte array into the LV.
+ *
+ * @param array The array to be copied into the LV 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 LvBufferUtils.LvConstructor putByteArray(@Nullable byte[] array, int offset,
+ int length) {
+ mTlvImpl.putByteArray(0, array, offset, length);
+ return this;
+ }
+
+ /**
+ * Copies a byte array into the LV.
+ *
+ * @param array The array to be copied (in full) into the LV structure.
+ * @return The constructor to facilitate chaining
+ * {@code ctr.putXXX(..).putXXX(..)}.
+ */
+ public LvBufferUtils.LvConstructor putByteArray(int type, @Nullable byte[] array) {
+ return putByteArray(array, 0, (array == null) ? 0 : array.length);
+ }
+
+ /**
+ * Places a zero length element (i.e. Length field = 0) into the LV.
+ *
+ * @return The constructor to facilitate chaining
+ * {@code ctr.putXXX(..).putXXX(..)}.
+ */
+ public LvBufferUtils.LvConstructor putZeroLengthElement() {
+ mTlvImpl.putZeroLengthElement(0);
+ return this;
+ }
+
+ /**
+ * Copies short into the LV.
+ *
+ * @param data The short to be inserted into the structure.
+ * @return The constructor to facilitate chaining
+ * {@code ctr.putXXX(..).putXXX(..)}.
+ */
+ public LvBufferUtils.LvConstructor putShort(short data) {
+ mTlvImpl.putShort(0, data);
+ return this;
+ }
+
+ /**
+ * Copies integer into the LV.
+ *
+ * @param data The integer to be inserted into the structure.
+ * @return The constructor to facilitate chaining
+ * {@code ctr.putXXX(..).putXXX(..)}.
+ */
+ public LvBufferUtils.LvConstructor putInt(int data) {
+ mTlvImpl.putInt(0, data);
+ return this;
+ }
+
+ /**
+ * Copies a String's byte representation into the LV.
+ *
+ * @param data The string whose bytes are to be inserted into the
+ * structure.
+ * @return The constructor to facilitate chaining
+ * {@code ctr.putXXX(..).putXXX(..)}.
+ */
+ public LvBufferUtils.LvConstructor putString(@Nullable String data) {
+ mTlvImpl.putString(0, data);
+ return this;
+ }
+
+ /**
+ * Returns the constructed LV 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 LV.
+ *
+ * @return The byte array containing the LV formatted structure.
+ */
+ public byte[] getArray() {
+ return mTlvImpl.getArray();
+ }
+ }
+
+ /**
+ * Utility class used when iterating over an LV formatted byte-array. Use
+ * {@link LvBufferUtils.LvIterable} to iterate over array. A {@link LvBufferUtils.LvElement}
+ * represents each entry in a LV formatted byte-array.
+ */
+ public static class LvElement {
+ /**
+ * The Length (L) field of the current LV element.
+ */
+ public int length;
+
+ /**
+ * The Value (V) field - a raw byte array representing the current LV
+ * element where the entry starts at {@link LvBufferUtils.LvElement#offset}.
+ */
+ public byte[] refArray;
+
+ /**
+ * The offset to be used into {@link LvBufferUtils.LvElement#refArray} to access the
+ * raw data representing the current LV element.
+ */
+ public int offset;
+
+ private LvElement(int length, @Nullable byte[] refArray, int offset) {
+ this.length = length;
+ this.refArray = refArray;
+ this.offset = offset;
+ }
+
+ /**
+ * Utility function to return a byte representation of a LV element of
+ * length 1. Note: an attempt to call this function on a LV item whose
+ * {@link LvBufferUtils.LvElement#length} is != 1 will result in an exception.
+ *
+ * @return byte representation of current LV element.
+ */
+ public byte getByte() {
+ if (length != 1) {
+ throw new IllegalArgumentException(
+ "Accesing a byte from a LV element of length " + length);
+ }
+ return refArray[offset];
+ }
+
+ /**
+ * Utility function to return a short representation of a LV element of
+ * length 2. Note: an attempt to call this function on a LV item whose
+ * {@link LvBufferUtils.LvElement#length} is != 2 will result in an exception.
+ *
+ * @return short representation of current LV element.
+ */
+ public short getShort() {
+ if (length != 2) {
+ throw new IllegalArgumentException(
+ "Accesing a short from a LV element of length " + length);
+ }
+ return Memory.peekShort(refArray, offset, ByteOrder.BIG_ENDIAN);
+ }
+
+ /**
+ * Utility function to return an integer representation of a LV element
+ * of length 4. Note: an attempt to call this function on a LV item
+ * whose {@link LvBufferUtils.LvElement#length} is != 4 will result in an exception.
+ *
+ * @return integer representation of current LV element.
+ */
+ public int getInt() {
+ if (length != 4) {
+ throw new IllegalArgumentException(
+ "Accesing an int from a LV element of length " + length);
+ }
+ return Memory.peekInt(refArray, offset, ByteOrder.BIG_ENDIAN);
+ }
+
+ /**
+ * Utility function to return a String representation of a LV element.
+ *
+ * @return String representation of the current LV element.
+ */
+ public String getString() {
+ return new String(refArray, offset, length);
+ }
+ }
+
+ /**
+ * Utility class to iterate over a LV formatted byte-array.
+ */
+ public static class LvIterable implements Iterable<LvBufferUtils.LvElement> {
+ private final TlvBufferUtils.TlvIterable mTlvIterable;
+
+ /**
+ * Constructs an LvIterable object - specifying the format of the LV
+ * (the size of the Length field), and the byte array whose data is to be parsed.
+ *
+ * @param lengthSize Number of bytes sued for the Length (L) field.
+ * Values values are 1 or 2 bytes.
+ * @param array The LV formatted byte-array to parse.
+ */
+ public LvIterable(int lengthSize, @Nullable byte[] array) {
+ mTlvIterable = new TlvBufferUtils.TlvIterable(0, lengthSize, array);
+ }
+
+ /**
+ * Prints out a parsed representation of the LV-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() {
+ return mTlvIterable.toString();
+ }
+
+ /**
+ * Returns an iterator to step through a LV formatted byte-array. The
+ * individual elements returned by the iterator are {@link LvBufferUtils.LvElement}.
+ */
+ @Override
+ public Iterator<LvBufferUtils.LvElement> iterator() {
+ return new Iterator<LvBufferUtils.LvElement>() {
+ private Iterator<TlvBufferUtils.TlvElement> mTlvIterator = mTlvIterable.iterator();
+
+ @Override
+ public boolean hasNext() {
+ return mTlvIterator.hasNext();
+ }
+
+ @Override
+ public LvBufferUtils.LvElement next() {
+ TlvBufferUtils.TlvElement tlvE = mTlvIterator.next();
+
+ return new LvElement(tlvE.length, tlvE.refArray, tlvE.offset);
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ }
+
+ /**
+ * Validates that a LV array is constructed correctly. I.e. that its specified Length
+ * fields correctly fill the specified length (and do not overshoot).
+ *
+ * @param array The LV array to verify.
+ * @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 lengthSize) {
+ return TlvBufferUtils.isValid(array, 0, lengthSize);
+ }
+}
diff --git a/wifi/java/android/net/wifi/nan/SubscribeSettings.aidl b/wifi/java/android/net/wifi/nan/PublishConfig.aidl
similarity index 95%
rename from wifi/java/android/net/wifi/nan/SubscribeSettings.aidl
rename to wifi/java/android/net/wifi/nan/PublishConfig.aidl
index 44849bc..5f66d16 100644
--- a/wifi/java/android/net/wifi/nan/SubscribeSettings.aidl
+++ b/wifi/java/android/net/wifi/nan/PublishConfig.aidl
@@ -16,4 +16,4 @@
package android.net.wifi.nan;
-parcelable SubscribeSettings;
+parcelable PublishConfig;
diff --git a/wifi/java/android/net/wifi/nan/PublishConfig.java b/wifi/java/android/net/wifi/nan/PublishConfig.java
new file mode 100644
index 0000000..71f99d9
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/PublishConfig.java
@@ -0,0 +1,391 @@
+/*
+ * 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.nan;
+
+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;
+
+/**
+ * Defines the configuration of a NAN publish session. Built using
+ * {@link PublishConfig.Builder}. A publish session is created using
+ * {@link WifiNanManager#publish(PublishConfig, WifiNanSessionCallback)} or updated using
+ * {@link WifiNanPublishSession#updatePublish(PublishConfig)}.
+ *
+ * @hide PROPOSED_NAN_API
+ */
+public 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 mPublishCount;
+
+ /** @hide */
+ public final int mTtlSec;
+
+ /** @hide */
+ public final boolean mEnableTerminateNotification;
+
+ private PublishConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter,
+ int publishType, int publichCount, int ttlSec, boolean enableTerminateNotification) {
+ mServiceName = serviceName;
+ mServiceSpecificInfo = serviceSpecificInfo;
+ mMatchFilter = matchFilter;
+ mPublishType = publishType;
+ mPublishCount = publichCount;
+ mTtlSec = ttlSec;
+ mEnableTerminateNotification = enableTerminateNotification;
+ }
+
+ @Override
+ public String toString() {
+ return "PublishConfig [mServiceName='" + mServiceName + ", mServiceSpecificInfo='" + (
+ (mServiceSpecificInfo == null) ? "null" : HexEncoding.encode(mServiceSpecificInfo))
+ + ", mTxFilter=" + (new LvBufferUtils.LvIterable(1, mMatchFilter)).toString()
+ + ", mPublishType=" + mPublishType + ", mPublishCount=" + mPublishCount
+ + ", mTtlSec=" + mTtlSec + ", mEnableTerminateNotification="
+ + mEnableTerminateNotification + "]";
+ }
+
+ @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(mPublishCount);
+ dest.writeInt(mTtlSec);
+ dest.writeInt(mEnableTerminateNotification ? 1 : 0);
+ }
+
+ public static final 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 publishCount = in.readInt();
+ int ttlSec = in.readInt();
+ boolean enableTerminateNotification = in.readInt() != 0;
+
+ return new PublishConfig(serviceName, ssi, matchFilter, publishType, publishCount,
+ ttlSec, enableTerminateNotification);
+ }
+ };
+
+ @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 && mPublishCount == lhs.mPublishCount
+ && mTtlSec == lhs.mTtlSec
+ && mEnableTerminateNotification == lhs.mEnableTerminateNotification;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+
+ result = 31 * result + Arrays.hashCode(mServiceName);
+ result = 31 * result + Arrays.hashCode(mServiceSpecificInfo);
+ result = 31 * result + Arrays.hashCode(mMatchFilter);
+ result = 31 * result + mPublishType;
+ result = 31 * result + mPublishCount;
+ result = 31 * result + mTtlSec;
+ result = 31 * result + (mEnableTerminateNotification ? 1 : 0);
+
+ return result;
+ }
+
+ /**
+ * Verifies that the contents of the PublishConfig are valid. Otherwise
+ * throws an IllegalArgumentException.
+ *
+ * @hide
+ */
+ public void validate() throws IllegalArgumentException {
+ WifiNanUtils.validateServiceName(mServiceName);
+
+ if (!LvBufferUtils.isValid(mMatchFilter, 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 (mPublishCount < 0) {
+ throw new IllegalArgumentException("Invalid publishCount - must be non-negative");
+ }
+ if (mTtlSec < 0) {
+ throw new IllegalArgumentException("Invalid ttlSec - must be non-negative");
+ }
+ }
+
+ /**
+ * 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 mPublishCount = 0;
+ private int mTtlSec = 0;
+ private boolean mEnableTerminateNotification = true;
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Specify service specific information for the publish session - a simple wrapper
+ * of {@link PublishConfig.Builder#setServiceSpecificInfo(byte[])}
+ * obtaining the data from a String.
+ * <p>
+ * Optional. Empty by default.
+ *
+ * @param serviceSpecificInfoStr The service specific information string
+ * to be included (as a byte array) in the publish
+ * information.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setServiceSpecificInfo(@NonNull String serviceSpecificInfoStr) {
+ mServiceSpecificInfo = serviceSpecificInfoStr.getBytes();
+ 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>
+ * Format is an LV byte array: a single byte Length field followed by L bytes (the value of
+ * the Length field) of a value blob.
+ * <p>
+ * Optional. Empty by default.
+ *
+ * @param matchFilter The byte-array containing the LV formatted match filter.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setMatchFilter(@Nullable byte[] matchFilter) {
+ mMatchFilter = matchFilter;
+ 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 number of times an unsolicited (configured using
+ * {@link PublishConfig.Builder#setPublishType(int)}) publish session
+ * will be broadcast. When the count is reached an event will be
+ * generated for {@link WifiNanSessionCallback#onSessionTerminated(int)}
+ * with {@link WifiNanSessionCallback#TERMINATE_REASON_DONE} [unless
+ * {@link #setEnableTerminateNotification(boolean)} disables the callback].
+ * <p>
+ * Optional. 0 by default - indicating the session doesn't terminate on its own.
+ * Session will be terminated when {@link WifiNanSession#terminate()} is called.
+ *
+ * @param publishCount Number of publish packets to broadcast.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setPublishCount(int publishCount) {
+ if (publishCount < 0) {
+ throw new IllegalArgumentException("Invalid publishCount - must be non-negative");
+ }
+ mPublishCount = publishCount;
+ 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 WifiNanSessionCallback#onSessionTerminated(int)} with
+ * {@link WifiNanSessionCallback#TERMINATE_REASON_DONE} [unless
+ * {@link #setEnableTerminateNotification(boolean)} disables the callback].
+ * <p>
+ * Optional. 0 by default - indicating the session doesn't terminate on its own.
+ * Session will be terminated when {@link WifiNanSession#terminate()} 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 WifiNanSessionCallback#onSessionTerminated(int)} 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 setEnableTerminateNotification(boolean enable) {
+ mEnableTerminateNotification = enable;
+ return this;
+ }
+
+ /**
+ * Build {@link PublishConfig} given the current requests made on the
+ * builder.
+ */
+ public PublishConfig build() {
+ return new PublishConfig(mServiceName, mServiceSpecificInfo, mMatchFilter, mPublishType,
+ mPublishCount, mTtlSec, mEnableTerminateNotification);
+ }
+ }
+}
diff --git a/wifi/java/android/net/wifi/nan/PublishData.aidl b/wifi/java/android/net/wifi/nan/PublishData.aidl
deleted file mode 100644
index 15e4ddf..0000000
--- a/wifi/java/android/net/wifi/nan/PublishData.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * 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.nan;
-
-parcelable PublishData;
diff --git a/wifi/java/android/net/wifi/nan/PublishData.java b/wifi/java/android/net/wifi/nan/PublishData.java
deleted file mode 100644
index 80119eb..0000000
--- a/wifi/java/android/net/wifi/nan/PublishData.java
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- * 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.nan;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.Arrays;
-
-/**
- * Defines the data for a NAN publish session. Built using
- * {@link PublishData.Builder}. Publish is done using
- * {@link WifiNanManager#publish(PublishData, PublishSettings, WifiNanSessionListener, int)}
- * or {@link WifiNanPublishSession#publish(PublishData, PublishSettings)}.
- * @hide PROPOSED_NAN_API
- */
-public class PublishData implements Parcelable {
- /**
- * @hide
- */
- public final String mServiceName;
-
- /**
- * @hide
- */
- public final int mServiceSpecificInfoLength;
-
- /**
- * @hide
- */
- public final byte[] mServiceSpecificInfo;
-
- /**
- * @hide
- */
- public final int mTxFilterLength;
-
- /**
- * @hide
- */
- public final byte[] mTxFilter;
-
- /**
- * @hide
- */
- public final int mRxFilterLength;
-
- /**
- * @hide
- */
- public final byte[] mRxFilter;
-
- private PublishData(String serviceName, byte[] serviceSpecificInfo,
- int serviceSpecificInfoLength, byte[] txFilter, int txFilterLength, byte[] rxFilter,
- int rxFilterLength) {
- mServiceName = serviceName;
- mServiceSpecificInfoLength = serviceSpecificInfoLength;
- mServiceSpecificInfo = serviceSpecificInfo;
- mTxFilterLength = txFilterLength;
- mTxFilter = txFilter;
- mRxFilterLength = rxFilterLength;
- mRxFilter = rxFilter;
- }
-
- @Override
- public String toString() {
- return "PublishData [mServiceName='" + mServiceName + "', mServiceSpecificInfo='"
- + (new String(mServiceSpecificInfo, 0, mServiceSpecificInfoLength))
- + "', mTxFilter="
- + (new TlvBufferUtils.TlvIterable(0, 1, mTxFilter, mTxFilterLength)).toString()
- + ", mRxFilter="
- + (new TlvBufferUtils.TlvIterable(0, 1, mRxFilter, mRxFilterLength)).toString()
- + "']";
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mServiceName);
- dest.writeInt(mServiceSpecificInfoLength);
- if (mServiceSpecificInfoLength != 0) {
- dest.writeByteArray(mServiceSpecificInfo, 0, mServiceSpecificInfoLength);
- }
- dest.writeInt(mTxFilterLength);
- if (mTxFilterLength != 0) {
- dest.writeByteArray(mTxFilter, 0, mTxFilterLength);
- }
- dest.writeInt(mRxFilterLength);
- if (mRxFilterLength != 0) {
- dest.writeByteArray(mRxFilter, 0, mRxFilterLength);
- }
- }
-
- public static final Creator<PublishData> CREATOR = new Creator<PublishData>() {
- @Override
- public PublishData[] newArray(int size) {
- return new PublishData[size];
- }
-
- @Override
- public PublishData createFromParcel(Parcel in) {
- String serviceName = in.readString();
- int ssiLength = in.readInt();
- byte[] ssi = new byte[ssiLength];
- if (ssiLength != 0) {
- in.readByteArray(ssi);
- }
- int txFilterLength = in.readInt();
- byte[] txFilter = new byte[txFilterLength];
- if (txFilterLength != 0) {
- in.readByteArray(txFilter);
- }
- int rxFilterLength = in.readInt();
- byte[] rxFilter = new byte[rxFilterLength];
- if (rxFilterLength != 0) {
- in.readByteArray(rxFilter);
- }
-
- return new PublishData(serviceName, ssi, ssiLength, txFilter, txFilterLength, rxFilter,
- rxFilterLength);
- }
- };
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
-
- if (!(o instanceof PublishData)) {
- return false;
- }
-
- PublishData lhs = (PublishData) o;
-
- if (!mServiceName.equals(lhs.mServiceName)
- || mServiceSpecificInfoLength != lhs.mServiceSpecificInfoLength
- || mTxFilterLength != lhs.mTxFilterLength
- || mRxFilterLength != lhs.mRxFilterLength) {
- return false;
- }
-
- if (mServiceSpecificInfo != null && lhs.mServiceSpecificInfo != null) {
- for (int i = 0; i < mServiceSpecificInfoLength; ++i) {
- if (mServiceSpecificInfo[i] != lhs.mServiceSpecificInfo[i]) {
- return false;
- }
- }
- } else if (mServiceSpecificInfoLength != 0) {
- return false; // invalid != invalid
- }
-
- if (mTxFilter != null && lhs.mTxFilter != null) {
- for (int i = 0; i < mTxFilterLength; ++i) {
- if (mTxFilter[i] != lhs.mTxFilter[i]) {
- return false;
- }
- }
- } else if (mTxFilterLength != 0) {
- return false; // invalid != invalid
- }
-
- if (mRxFilter != null && lhs.mRxFilter != null) {
- for (int i = 0; i < mRxFilterLength; ++i) {
- if (mRxFilter[i] != lhs.mRxFilter[i]) {
- return false;
- }
- }
- } else if (mRxFilterLength != 0) {
- return false; // invalid != invalid
- }
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = 17;
-
- result = 31 * result + mServiceName.hashCode();
- result = 31 * result + mServiceSpecificInfoLength;
- result = 31 * result + Arrays.hashCode(mServiceSpecificInfo);
- result = 31 * result + mTxFilterLength;
- result = 31 * result + Arrays.hashCode(mTxFilter);
- result = 31 * result + mRxFilterLength;
- result = 31 * result + Arrays.hashCode(mRxFilter);
-
- return result;
- }
-
- /**
- * Builder used to build {@link PublishData} objects.
- */
- public static final class Builder {
- private String mServiceName;
- private int mServiceSpecificInfoLength;
- private byte[] mServiceSpecificInfo = new byte[0];
- private int mTxFilterLength;
- private byte[] mTxFilter = new byte[0];
- private int mRxFilterLength;
- private byte[] mRxFilter = new byte[0];
-
- /**
- * Specify the service name of the publish session. The actual on-air
- * value is a 6 byte hashed representation of this string.
- *
- * @param serviceName The service name for the publish session.
- * @return The builder to facilitate chaining
- * {@code builder.setXXX(..).setXXX(..)}.
- */
- public Builder setServiceName(String serviceName) {
- mServiceName = serviceName;
- 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 - i.e. it
- * will not be used to determine whether a publish/subscribe match
- * occurs.
- *
- * @param serviceSpecificInfo A byte-array for the service-specific
- * information field.
- * @param serviceSpecificInfoLength The length of the byte-array to be
- * used.
- * @return The builder to facilitate chaining
- * {@code builder.setXXX(..).setXXX(..)}.
- */
- public Builder setServiceSpecificInfo(byte[] serviceSpecificInfo,
- int serviceSpecificInfoLength) {
- if (serviceSpecificInfoLength != 0 && (serviceSpecificInfo == null
- || serviceSpecificInfo.length < serviceSpecificInfoLength)) {
- throw new IllegalArgumentException("Non-matching combination of "
- + "serviceSpecificInfo and serviceSpecificInfoLength");
- }
- mServiceSpecificInfoLength = serviceSpecificInfoLength;
- mServiceSpecificInfo = serviceSpecificInfo;
- return this;
- }
-
- /**
- * Specify service specific information for the publish session - same
- * as {@link PublishData.Builder#setServiceSpecificInfo(byte[], int)}
- * but obtaining the data from a String.
- *
- * @param serviceSpecificInfoStr The service specific information string
- * to be included (as a byte array) in the publish
- * information.
- * @return The builder to facilitate chaining
- * {@code builder.setXXX(..).setXXX(..)}.
- */
- public Builder setServiceSpecificInfo(String serviceSpecificInfoStr) {
- mServiceSpecificInfoLength = serviceSpecificInfoStr.length();
- mServiceSpecificInfo = serviceSpecificInfoStr.getBytes();
- return this;
- }
-
- /**
- * The transmit filter for an active publish session
- * {@link PublishSettings.Builder#setPublishType(int)} and
- * {@link PublishSettings#PUBLISH_TYPE_UNSOLICITED}. Included in
- * transmitted publish packets and used by receivers (subscribers) to
- * determine whether they match - in addition to just relying on the
- * service name.
- * <p>
- * Format is an LV byte array - the {@link TlvBufferUtils} utility class
- * is available to form and parse.
- *
- * @param txFilter The byte-array containing the LV formatted transmit
- * filter.
- * @param txFilterLength The number of bytes in the transmit filter
- * argument.
- * @return The builder to facilitate chaining
- * {@code builder.setXXX(..).setXXX(..)}.
- */
- public Builder setTxFilter(byte[] txFilter, int txFilterLength) {
- if (txFilterLength != 0 && (txFilter == null || txFilter.length < txFilterLength)) {
- throw new IllegalArgumentException(
- "Non-matching combination of txFilter and txFilterLength");
- }
- mTxFilter = txFilter;
- mTxFilterLength = txFilterLength;
- return this;
- }
-
- /**
- * The transmit filter for a passive publish session
- * {@link PublishSettings.Builder#setPublishType(int)} and
- * {@link PublishSettings#PUBLISH_TYPE_SOLICITED}. Used by the publisher
- * to determine whether they match transmitted subscriber packets
- * (active subscribers) - in addition to just relying on the service
- * name.
- * <p>
- * Format is an LV byte array - the {@link TlvBufferUtils} utility class
- * is available to form and parse.
- *
- * @param rxFilter The byte-array containing the LV formatted receive
- * filter.
- * @param rxFilterLength The number of bytes in the receive filter
- * argument.
- * @return The builder to facilitate chaining
- * {@code builder.setXXX(..).setXXX(..)}.
- */
- public Builder setRxFilter(byte[] rxFilter, int rxFilterLength) {
- if (rxFilterLength != 0 && (rxFilter == null || rxFilter.length < rxFilterLength)) {
- throw new IllegalArgumentException(
- "Non-matching combination of rxFilter and rxFilterLength");
- }
- mRxFilter = rxFilter;
- mRxFilterLength = rxFilterLength;
- return this;
- }
-
- /**
- * Build {@link PublishData} given the current requests made on the
- * builder.
- */
- public PublishData build() {
- return new PublishData(mServiceName, mServiceSpecificInfo, mServiceSpecificInfoLength,
- mTxFilter, mTxFilterLength, mRxFilter, mRxFilterLength);
- }
- }
-}
diff --git a/wifi/java/android/net/wifi/nan/PublishSettings.aidl b/wifi/java/android/net/wifi/nan/PublishSettings.aidl
deleted file mode 100644
index ff69293..0000000
--- a/wifi/java/android/net/wifi/nan/PublishSettings.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * 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.nan;
-
-parcelable PublishSettings;
diff --git a/wifi/java/android/net/wifi/nan/PublishSettings.java b/wifi/java/android/net/wifi/nan/PublishSettings.java
deleted file mode 100644
index bbc5340..0000000
--- a/wifi/java/android/net/wifi/nan/PublishSettings.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * 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.nan;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Defines the settings (configuration) for a NAN publish session. Built using
- * {@link PublishSettings.Builder}. Publish is done using
- * {@link WifiNanManager#publish(PublishData, PublishSettings, WifiNanSessionListener, int)}
- * or {@link WifiNanPublishSession#publish(PublishData, PublishSettings)}.
- *
- * @hide PROPOSED_NAN_API
- */
-public class PublishSettings implements Parcelable {
-
- /**
- * Defines an unsolicited publish session - i.e. a publish session where
- * publish packets are transmitted over-the-air. Configuration is done using
- * {@link PublishSettings.Builder#setPublishType(int)}.
- */
- public static final int PUBLISH_TYPE_UNSOLICITED = 0;
-
- /**
- * Defines a solicited publish session - i.e. a publish session where
- * publish packets are not transmitted over-the-air and the device listens
- * and matches to transmitted subscribe packets. Configuration is done using
- * {@link PublishSettings.Builder#setPublishType(int)}.
- */
- public static final int PUBLISH_TYPE_SOLICITED = 1;
-
- /**
- * @hide
- */
- public final int mPublishType;
-
- /**
- * @hide
- */
- public final int mPublishCount;
-
- /**
- * @hide
- */
- public final int mTtlSec;
-
- private PublishSettings(int publishType, int publichCount, int ttlSec) {
- mPublishType = publishType;
- mPublishCount = publichCount;
- mTtlSec = ttlSec;
- }
-
- @Override
- public String toString() {
- return "PublishSettings [mPublishType=" + mPublishType + ", mPublishCount=" + mPublishCount
- + ", mTtlSec=" + mTtlSec + "]";
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mPublishType);
- dest.writeInt(mPublishCount);
- dest.writeInt(mTtlSec);
- }
-
- public static final Creator<PublishSettings> CREATOR = new Creator<PublishSettings>() {
- @Override
- public PublishSettings[] newArray(int size) {
- return new PublishSettings[size];
- }
-
- @Override
- public PublishSettings createFromParcel(Parcel in) {
- int publishType = in.readInt();
- int publishCount = in.readInt();
- int ttlSec = in.readInt();
- return new PublishSettings(publishType, publishCount, ttlSec);
- }
- };
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
-
- if (!(o instanceof PublishSettings)) {
- return false;
- }
-
- PublishSettings lhs = (PublishSettings) o;
-
- return mPublishType == lhs.mPublishType && mPublishCount == lhs.mPublishCount
- && mTtlSec == lhs.mTtlSec;
- }
-
- @Override
- public int hashCode() {
- int result = 17;
-
- result = 31 * result + mPublishType;
- result = 31 * result + mPublishCount;
- result = 31 * result + mTtlSec;
-
- return result;
- }
-
- /**
- * Builder used to build {@link PublishSettings} objects.
- */
- public static final class Builder {
- int mPublishType;
- int mPublishCount;
- int mTtlSec;
-
- /**
- * Sets 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: solicited (
- * {@link PublishSettings#PUBLISH_TYPE_SOLICITED}) or
- * unsolicited (
- * {@link PublishSettings#PUBLISH_TYPE_UNSOLICITED}).
- * @return The builder to facilitate chaining
- * {@code builder.setXXX(..).setXXX(..)}.
- */
- public Builder setPublishType(int publishType) {
- if (publishType < PUBLISH_TYPE_UNSOLICITED || publishType > PUBLISH_TYPE_SOLICITED) {
- throw new IllegalArgumentException("Invalid publishType - " + publishType);
- }
- mPublishType = publishType;
- return this;
- }
-
- /**
- * Sets the number of times a solicited (
- * {@link PublishSettings.Builder#setPublishType(int)}) publish session
- * will transmit a packet. When the count is reached an event will be
- * generated for {@link WifiNanSessionListener#onPublishTerminated(int)}
- * with reason={@link WifiNanSessionListener#TERMINATE_REASON_DONE}.
- *
- * @param publishCount Number of publish packets to transmit.
- * @return The builder to facilitate chaining
- * {@code builder.setXXX(..).setXXX(..)}.
- */
- public Builder setPublishCount(int publishCount) {
- if (publishCount < 0) {
- throw new IllegalArgumentException("Invalid publishCount - must be non-negative");
- }
- mPublishCount = publishCount;
- return this;
- }
-
- /**
- * Sets the time interval (in seconds) a solicited (
- * {@link PublishSettings.Builder#setPublishCount(int)}) publish session
- * will be alive - i.e. transmitting a packet. When the TTL is reached
- * an event will be generated for
- * {@link WifiNanSessionListener#onPublishTerminated(int)} with reason=
- * {@link WifiNanSessionListener#TERMINATE_REASON_DONE}.
- *
- * @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;
- }
-
- /**
- * Build {@link PublishSettings} given the current requests made on the
- * builder.
- */
- public PublishSettings build() {
- return new PublishSettings(mPublishType, mPublishCount, mTtlSec);
- }
- }
-}
diff --git a/wifi/java/android/net/wifi/nan/SubscribeSettings.aidl b/wifi/java/android/net/wifi/nan/SubscribeConfig.aidl
similarity index 95%
copy from wifi/java/android/net/wifi/nan/SubscribeSettings.aidl
copy to wifi/java/android/net/wifi/nan/SubscribeConfig.aidl
index 44849bc..92344a4 100644
--- a/wifi/java/android/net/wifi/nan/SubscribeSettings.aidl
+++ b/wifi/java/android/net/wifi/nan/SubscribeConfig.aidl
@@ -16,4 +16,4 @@
package android.net.wifi.nan;
-parcelable SubscribeSettings;
+parcelable SubscribeConfig;
diff --git a/wifi/java/android/net/wifi/nan/SubscribeConfig.java b/wifi/java/android/net/wifi/nan/SubscribeConfig.java
new file mode 100644
index 0000000..7904875
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/SubscribeConfig.java
@@ -0,0 +1,444 @@
+/*
+ * 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.nan;
+
+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;
+
+/**
+ * Defines the configuration of a NAN subscribe session. Built using
+ * {@link SubscribeConfig.Builder}. Subscribe is done using
+ * {@link WifiNanManager#subscribe(SubscribeConfig, WifiNanSessionCallback)} or
+ * {@link WifiNanSubscribeSession#updateSubscribe(SubscribeConfig)}.
+ *
+ * @hide PROPOSED_NAN_API
+ */
+public 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 */
+ @IntDef({
+ MATCH_STYLE_FIRST_ONLY, MATCH_STYLE_ALL })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MatchStyles {
+ }
+
+ /**
+ * Specifies that only the first match of a set of identical matches (same
+ * publish) will be reported to the subscriber. Configuration is done using
+ * {@link SubscribeConfig.Builder#setMatchStyle(int)}.
+ */
+ public static final int MATCH_STYLE_FIRST_ONLY = 0;
+
+ /**
+ * Specifies that all matches of a set of identical matches (same publish)
+ * will be reported to the subscriber. Configuration is done using
+ * {@link SubscribeConfig.Builder#setMatchStyle(int)}.
+ */
+ public static final int MATCH_STYLE_ALL = 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 mSubscribeCount;
+
+ /** @hide */
+ public final int mTtlSec;
+
+ /** @hide */
+ public final int mMatchStyle;
+
+ /** @hide */
+ public final boolean mEnableTerminateNotification;
+
+ private SubscribeConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter,
+ int subscribeType, int publichCount, int ttlSec, int matchStyle,
+ boolean enableTerminateNotification) {
+ mServiceName = serviceName;
+ mServiceSpecificInfo = serviceSpecificInfo;
+ mMatchFilter = matchFilter;
+ mSubscribeType = subscribeType;
+ mSubscribeCount = publichCount;
+ mTtlSec = ttlSec;
+ mMatchStyle = matchStyle;
+ mEnableTerminateNotification = enableTerminateNotification;
+ }
+
+ @Override
+ public String toString() {
+ return "SubscribeConfig [mServiceName='" + mServiceName + ", mServiceSpecificInfo='" + (
+ (mServiceSpecificInfo == null) ? "null" : HexEncoding.encode(mServiceSpecificInfo))
+ + ", mMatchFilter=" + (new LvBufferUtils.LvIterable(1, mMatchFilter)).toString()
+ + ", mSubscribeType=" + mSubscribeType + ", mSubscribeCount=" + mSubscribeCount
+ + ", mTtlSec=" + mTtlSec + ", mMatchType=" + mMatchStyle
+ + ", mEnableTerminateNotification=" + mEnableTerminateNotification + "]";
+ }
+
+ @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(mSubscribeCount);
+ dest.writeInt(mTtlSec);
+ dest.writeInt(mMatchStyle);
+ dest.writeInt(mEnableTerminateNotification ? 1 : 0);
+ }
+
+ public static final 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 subscribeCount = in.readInt();
+ int ttlSec = in.readInt();
+ int matchStyle = in.readInt();
+ boolean enableTerminateNotification = in.readInt() != 0;
+
+ return new SubscribeConfig(serviceName, ssi, matchFilter, subscribeType, subscribeCount,
+ ttlSec, matchStyle, enableTerminateNotification);
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof SubscribeConfig)) {
+ return false;
+ }
+
+ SubscribeConfig lhs = (SubscribeConfig) o;
+
+ return Arrays.equals(mServiceName, lhs.mServiceName) && Arrays.equals(mServiceSpecificInfo,
+ lhs.mServiceSpecificInfo) && Arrays.equals(mMatchFilter, lhs.mMatchFilter)
+ && mSubscribeType == lhs.mSubscribeType && mSubscribeCount == lhs.mSubscribeCount
+ && mTtlSec == lhs.mTtlSec && mMatchStyle == lhs.mMatchStyle
+ && mEnableTerminateNotification == lhs.mEnableTerminateNotification;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+
+ result = 31 * result + Arrays.hashCode(mServiceName);
+ result = 31 * result + Arrays.hashCode(mServiceSpecificInfo);
+ result = 31 * result + Arrays.hashCode(mMatchFilter);
+ result = 31 * result + mSubscribeType;
+ result = 31 * result + mSubscribeCount;
+ result = 31 * result + mTtlSec;
+ result = 31 * result + mMatchStyle;
+ result = 31 * result + (mEnableTerminateNotification ? 1 : 0);
+
+ return result;
+ }
+
+ /**
+ * Verifies that the contents of the SubscribeConfig are valid. Otherwise
+ * throws an IllegalArgumentException.
+ *
+ * @hide
+ */
+ public void validate() throws IllegalArgumentException {
+ WifiNanUtils.validateServiceName(mServiceName);
+
+ if (!LvBufferUtils.isValid(mMatchFilter, 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 (mSubscribeCount < 0) {
+ throw new IllegalArgumentException("Invalid subscribeCount - must be non-negative");
+ }
+ if (mTtlSec < 0) {
+ throw new IllegalArgumentException("Invalid ttlSec - must be non-negative");
+ }
+ if (mMatchStyle != MATCH_STYLE_FIRST_ONLY && mMatchStyle != MATCH_STYLE_ALL) {
+ throw new IllegalArgumentException(
+ "Invalid matchType - must be MATCH_FIRST_ONLY or MATCH_ALL");
+ }
+ }
+
+ /**
+ * 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 mSubscribeCount = 0;
+ private int mTtlSec = 0;
+ private int mMatchStyle = MATCH_STYLE_ALL;
+ private boolean mEnableTerminateNotification = true;
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Specify service specific information for the subscribe session - a simple wrapper
+ * of {@link SubscribeConfig.Builder#setServiceSpecificInfo(byte[])}
+ * obtaining the data from a String.
+ * <p>
+ * Optional. Empty by default.
+ *
+ * @param serviceSpecificInfoStr The service specific information string
+ * to be included (as a byte array) in the subscribe
+ * information.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setServiceSpecificInfo(@NonNull String serviceSpecificInfoStr) {
+ mServiceSpecificInfo = serviceSpecificInfoStr.getBytes();
+ 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>
+ * Format is an LV byte array: a single byte Length field followed by L bytes (the value of
+ * the Length field) of a value blob.
+ * <p>
+ * Optional. Empty by default.
+ *
+ * @param matchFilter The byte-array containing the LV formatted match filter.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setMatchFilter(@Nullable byte[] matchFilter) {
+ mMatchFilter = matchFilter;
+ 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 number of times an active (
+ * {@link SubscribeConfig.Builder#setSubscribeType(int)}) subscribe session
+ * will broadcast. When the count is reached an event will be
+ * generated for {@link WifiNanSessionCallback#onSessionTerminated(int)}
+ * with {@link WifiNanSessionCallback#TERMINATE_REASON_DONE}.
+ * <p>
+ * Optional. 0 by default - indicating the session doesn't terminate on its own.
+ * Session will be terminated when {@link WifiNanSession#terminate()} is called.
+ *
+ * @param subscribeCount Number of subscribe packets to broadcast.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setSubscribeCount(int subscribeCount) {
+ if (subscribeCount < 0) {
+ throw new IllegalArgumentException("Invalid subscribeCount - must be non-negative");
+ }
+ mSubscribeCount = subscribeCount;
+ 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 WifiNanSessionCallback#onSessionTerminated(int)} with
+ * {@link WifiNanSessionCallback#TERMINATE_REASON_DONE}.
+ * <p>
+ * Optional. 0 by default - indicating the session doesn't terminate on its own.
+ * Session will be terminated when {@link WifiNanSession#terminate()} 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;
+ }
+
+ /**
+ * Sets the match style of the subscription - how are matches from a
+ * single match session (corresponding to the same publish action on the
+ * peer) reported to the host (using the
+ * {@link WifiNanSessionCallback#onMatch(int, byte[], byte[])}
+ * ). The options are: only report the first match and ignore the rest
+ * {@link SubscribeConfig#MATCH_STYLE_FIRST_ONLY} or report every single
+ * match {@link SubscribeConfig#MATCH_STYLE_ALL} (the default).
+ *
+ * @param matchStyle The reporting style for the discovery match.
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setMatchStyle(@MatchStyles int matchStyle) {
+ if (matchStyle != MATCH_STYLE_FIRST_ONLY && matchStyle != MATCH_STYLE_ALL) {
+ throw new IllegalArgumentException(
+ "Invalid matchType - must be MATCH_FIRST_ONLY or MATCH_ALL");
+ }
+ mMatchStyle = matchStyle;
+ return this;
+ }
+
+ /**
+ * Configure whether a subscribe terminate notification
+ * {@link WifiNanSessionCallback#onSessionTerminated(int)} 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 setEnableTerminateNotification(boolean enable) {
+ mEnableTerminateNotification = enable;
+ return this;
+ }
+
+ /**
+ * Build {@link SubscribeConfig} given the current requests made on the
+ * builder.
+ */
+ public SubscribeConfig build() {
+ return new SubscribeConfig(mServiceName, mServiceSpecificInfo, mMatchFilter,
+ mSubscribeType, mSubscribeCount, mTtlSec, mMatchStyle,
+ mEnableTerminateNotification);
+ }
+ }
+}
diff --git a/wifi/java/android/net/wifi/nan/SubscribeData.aidl b/wifi/java/android/net/wifi/nan/SubscribeData.aidl
deleted file mode 100644
index 662fdb8..0000000
--- a/wifi/java/android/net/wifi/nan/SubscribeData.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * 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.nan;
-
-parcelable SubscribeData;
diff --git a/wifi/java/android/net/wifi/nan/SubscribeData.java b/wifi/java/android/net/wifi/nan/SubscribeData.java
deleted file mode 100644
index cd6e918..0000000
--- a/wifi/java/android/net/wifi/nan/SubscribeData.java
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * 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.nan;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.Arrays;
-
-/**
- * Defines the data for a NAN subscribe session. Built using
- * {@link SubscribeData.Builder}. Subscribe is done using
- * {@link WifiNanManager#subscribe(SubscribeData, SubscribeSettings, WifiNanSessionListener, int)}
- * or
- * {@link WifiNanSubscribeSession#subscribe(SubscribeData, SubscribeSettings)}.
- * @hide PROPOSED_NAN_API
- */
-public class SubscribeData implements Parcelable {
- /**
- * @hide
- */
- public final String mServiceName;
-
- /**
- * @hide
- */
- public final int mServiceSpecificInfoLength;
-
- /**
- * @hide
- */
- public final byte[] mServiceSpecificInfo;
-
- /**
- * @hide
- */
- public final int mTxFilterLength;
-
- /**
- * @hide
- */
- public final byte[] mTxFilter;
-
- /**
- * @hide
- */
- public final int mRxFilterLength;
-
- /**
- * @hide
- */
- public final byte[] mRxFilter;
-
- private SubscribeData(String serviceName, byte[] serviceSpecificInfo,
- int serviceSpecificInfoLength, byte[] txFilter, int txFilterLength, byte[] rxFilter,
- int rxFilterLength) {
- mServiceName = serviceName;
- mServiceSpecificInfoLength = serviceSpecificInfoLength;
- mServiceSpecificInfo = serviceSpecificInfo;
- mTxFilterLength = txFilterLength;
- mTxFilter = txFilter;
- mRxFilterLength = rxFilterLength;
- mRxFilter = rxFilter;
- }
-
- @Override
- public String toString() {
- return "SubscribeData [mServiceName='" + mServiceName + "', mServiceSpecificInfo='"
- + (new String(mServiceSpecificInfo, 0, mServiceSpecificInfoLength))
- + "', mTxFilter="
- + (new TlvBufferUtils.TlvIterable(0, 1, mTxFilter, mTxFilterLength)).toString()
- + ", mRxFilter="
- + (new TlvBufferUtils.TlvIterable(0, 1, mRxFilter, mRxFilterLength)).toString()
- + "']";
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mServiceName);
- dest.writeInt(mServiceSpecificInfoLength);
- if (mServiceSpecificInfoLength != 0) {
- dest.writeByteArray(mServiceSpecificInfo, 0, mServiceSpecificInfoLength);
- }
- dest.writeInt(mTxFilterLength);
- if (mTxFilterLength != 0) {
- dest.writeByteArray(mTxFilter, 0, mTxFilterLength);
- }
- dest.writeInt(mRxFilterLength);
- if (mRxFilterLength != 0) {
- dest.writeByteArray(mRxFilter, 0, mRxFilterLength);
- }
- }
-
- public static final Creator<SubscribeData> CREATOR = new Creator<SubscribeData>() {
- @Override
- public SubscribeData[] newArray(int size) {
- return new SubscribeData[size];
- }
-
- @Override
- public SubscribeData createFromParcel(Parcel in) {
- String serviceName = in.readString();
- int ssiLength = in.readInt();
- byte[] ssi = new byte[ssiLength];
- if (ssiLength != 0) {
- in.readByteArray(ssi);
- }
- int txFilterLength = in.readInt();
- byte[] txFilter = new byte[txFilterLength];
- if (txFilterLength != 0) {
- in.readByteArray(txFilter);
- }
- int rxFilterLength = in.readInt();
- byte[] rxFilter = new byte[rxFilterLength];
- if (rxFilterLength != 0) {
- in.readByteArray(rxFilter);
- }
-
- return new SubscribeData(serviceName, ssi, ssiLength, txFilter, txFilterLength,
- rxFilter, rxFilterLength);
- }
- };
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
-
- if (!(o instanceof SubscribeData)) {
- return false;
- }
-
- SubscribeData lhs = (SubscribeData) o;
-
- if (!mServiceName.equals(lhs.mServiceName)
- || mServiceSpecificInfoLength != lhs.mServiceSpecificInfoLength
- || mTxFilterLength != lhs.mTxFilterLength
- || mRxFilterLength != lhs.mRxFilterLength) {
- return false;
- }
-
- if (mServiceSpecificInfo != null && lhs.mServiceSpecificInfo != null) {
- for (int i = 0; i < mServiceSpecificInfoLength; ++i) {
- if (mServiceSpecificInfo[i] != lhs.mServiceSpecificInfo[i]) {
- return false;
- }
- }
- } else if (mServiceSpecificInfoLength != 0) {
- return false; // invalid != invalid
- }
-
- if (mTxFilter != null && lhs.mTxFilter != null) {
- for (int i = 0; i < mTxFilterLength; ++i) {
- if (mTxFilter[i] != lhs.mTxFilter[i]) {
- return false;
- }
- }
- } else if (mTxFilterLength != 0) {
- return false; // invalid != invalid
- }
-
- if (mRxFilter != null && lhs.mRxFilter != null) {
- for (int i = 0; i < mRxFilterLength; ++i) {
- if (mRxFilter[i] != lhs.mRxFilter[i]) {
- return false;
- }
- }
- } else if (mRxFilterLength != 0) {
- return false; // invalid != invalid
- }
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = 17;
-
- result = 31 * result + mServiceName.hashCode();
- result = 31 * result + mServiceSpecificInfoLength;
- result = 31 * result + Arrays.hashCode(mServiceSpecificInfo);
- result = 31 * result + mTxFilterLength;
- result = 31 * result + Arrays.hashCode(mTxFilter);
- result = 31 * result + mRxFilterLength;
- result = 31 * result + Arrays.hashCode(mRxFilter);
-
- return result;
- }
-
- /**
- * Builder used to build {@link SubscribeData} objects.
- */
- public static final class Builder {
- private String mServiceName;
- private int mServiceSpecificInfoLength;
- private byte[] mServiceSpecificInfo = new byte[0];
- private int mTxFilterLength;
- private byte[] mTxFilter = new byte[0];
- private int mRxFilterLength;
- private byte[] mRxFilter = new byte[0];
-
- /**
- * Specify the service name of the subscribe session. The actual on-air
- * value is a 6 byte hashed representation of this string.
- *
- * @param serviceName The service name for the subscribe session.
- * @return The builder to facilitate chaining
- * {@code builder.setXXX(..).setXXX(..)}.
- */
- public Builder setServiceName(String serviceName) {
- mServiceName = serviceName;
- 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.
- *
- * @param serviceSpecificInfo A byte-array for the service-specific
- * information field.
- * @param serviceSpecificInfoLength The length of the byte-array to be
- * used.
- * @return The builder to facilitate chaining
- * {@code builder.setXXX(..).setXXX(..)}.
- */
- public Builder setServiceSpecificInfo(byte[] serviceSpecificInfo,
- int serviceSpecificInfoLength) {
- mServiceSpecificInfoLength = serviceSpecificInfoLength;
- mServiceSpecificInfo = serviceSpecificInfo;
- return this;
- }
-
- /**
- * Specify service specific information for the subscribe session - same
- * as {@link SubscribeData.Builder#setServiceSpecificInfo(byte[], int)}
- * but obtaining the data from a String.
- *
- * @param serviceSpecificInfoStr The service specific information string
- * to be included (as a byte array) in the subscribe
- * information.
- * @return The builder to facilitate chaining
- * {@code builder.setXXX(..).setXXX(..)}.
- */
- public Builder setServiceSpecificInfo(String serviceSpecificInfoStr) {
- mServiceSpecificInfoLength = serviceSpecificInfoStr.length();
- mServiceSpecificInfo = serviceSpecificInfoStr.getBytes();
- return this;
- }
-
- /**
- * The transmit filter for an active subscribe session
- * {@link SubscribeSettings.Builder#setSubscribeType(int)} and
- * {@link SubscribeSettings#SUBSCRIBE_TYPE_ACTIVE}. Included in
- * transmitted subscribe packets and used by receivers (passive
- * publishers) to determine whether they match - in addition to just
- * relying on the service name.
- * <p>
- * Format is an LV byte array - the {@link TlvBufferUtils} utility class
- * is available to form and parse.
- *
- * @param txFilter The byte-array containing the LV formatted transmit
- * filter.
- * @param txFilterLength The number of bytes in the transmit filter
- * argument.
- * @return The builder to facilitate chaining
- * {@code builder.setXXX(..).setXXX(..)}.
- */
- public Builder setTxFilter(byte[] txFilter, int txFilterLength) {
- mTxFilter = txFilter;
- mTxFilterLength = txFilterLength;
- return this;
- }
-
- /**
- * The transmit filter for a passive subsribe session
- * {@link SubscribeSettings.Builder#setSubscribeType(int)} and
- * {@link SubscribeSettings#SUBSCRIBE_TYPE_PASSIVE}. Used by the
- * subscriber to determine whether they match transmitted publish
- * packets - in addition to just relying on the service name.
- * <p>
- * Format is an LV byte array - the {@link TlvBufferUtils} utility class
- * is available to form and parse.
- *
- * @param rxFilter The byte-array containing the LV formatted receive
- * filter.
- * @param rxFilterLength The number of bytes in the receive filter
- * argument.
- * @return The builder to facilitate chaining
- * {@code builder.setXXX(..).setXXX(..)}.
- */
- public Builder setRxFilter(byte[] rxFilter, int rxFilterLength) {
- mRxFilter = rxFilter;
- mRxFilterLength = rxFilterLength;
- return this;
- }
-
- /**
- * Build {@link SubscribeData} given the current requests made on the
- * builder.
- */
- public SubscribeData build() {
- return new SubscribeData(mServiceName, mServiceSpecificInfo, mServiceSpecificInfoLength,
- mTxFilter, mTxFilterLength, mRxFilter, mRxFilterLength);
- }
- }
-}
diff --git a/wifi/java/android/net/wifi/nan/SubscribeSettings.java b/wifi/java/android/net/wifi/nan/SubscribeSettings.java
deleted file mode 100644
index 5c4f8fb..0000000
--- a/wifi/java/android/net/wifi/nan/SubscribeSettings.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * 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.nan;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Defines the settings (configuration) for a NAN subscribe session. Built using
- * {@link SubscribeSettings.Builder}. Subscribe is done using
- * {@link WifiNanManager#subscribe(SubscribeData, SubscribeSettings, WifiNanSessionListener, int)}
- * or {@link WifiNanSubscribeSession#subscribe(SubscribeData, SubscribeSettings)}.
- *
- * @hide PROPOSED_NAN_API
- */
-public class SubscribeSettings implements Parcelable {
-
- /**
- * Defines a passive subscribe session - i.e. 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 SubscribeSettings.Builder#setSubscribeType(int)}.
- */
- public static final int SUBSCRIBE_TYPE_PASSIVE = 0;
-
- /**
- * Defines an active subscribe session - i.e. a subscribe session where
- * subscribe packets are transmitted over-the-air. Configuration is done
- * using {@link SubscribeSettings.Builder#setSubscribeType(int)}.
- */
- public static final int SUBSCRIBE_TYPE_ACTIVE = 1;
-
- /**
- * @hide
- */
- public final int mSubscribeType;
-
- /**
- * @hide
- */
- public final int mSubscribeCount;
-
- /**
- * @hide
- */
- public final int mTtlSec;
-
- private SubscribeSettings(int subscribeType, int publichCount, int ttlSec) {
- mSubscribeType = subscribeType;
- mSubscribeCount = publichCount;
- mTtlSec = ttlSec;
- }
-
- @Override
- public String toString() {
- return "SubscribeSettings [mSubscribeType=" + mSubscribeType + ", mSubscribeCount="
- + mSubscribeCount + ", mTtlSec=" + mTtlSec + "]";
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mSubscribeType);
- dest.writeInt(mSubscribeCount);
- dest.writeInt(mTtlSec);
- }
-
- public static final Creator<SubscribeSettings> CREATOR = new Creator<SubscribeSettings>() {
- @Override
- public SubscribeSettings[] newArray(int size) {
- return new SubscribeSettings[size];
- }
-
- @Override
- public SubscribeSettings createFromParcel(Parcel in) {
- int subscribeType = in.readInt();
- int subscribeCount = in.readInt();
- int ttlSec = in.readInt();
- return new SubscribeSettings(subscribeType, subscribeCount, ttlSec);
- }
- };
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
-
- if (!(o instanceof SubscribeSettings)) {
- return false;
- }
-
- SubscribeSettings lhs = (SubscribeSettings) o;
-
- return mSubscribeType == lhs.mSubscribeType && mSubscribeCount == lhs.mSubscribeCount
- && mTtlSec == lhs.mTtlSec;
- }
-
- @Override
- public int hashCode() {
- int result = 17;
-
- result = 31 * result + mSubscribeType;
- result = 31 * result + mSubscribeCount;
- result = 31 * result + mTtlSec;
-
- return result;
- }
-
- /**
- * Builder used to build {@link SubscribeSettings} objects.
- */
- public static final class Builder {
- int mSubscribeType;
- int mSubscribeCount;
- int mTtlSec;
-
- /**
- * 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: active (
- * {@link SubscribeSettings#SUBSCRIBE_TYPE_ACTIVE}) or
- * passive ( {@link SubscribeSettings#SUBSCRIBE_TYPE_PASSIVE}
- * ).
- * @return The builder to facilitate chaining
- * {@code builder.setXXX(..).setXXX(..)}.
- */
- public Builder setSubscribeType(int subscribeType) {
- if (subscribeType < SUBSCRIBE_TYPE_PASSIVE || subscribeType > SUBSCRIBE_TYPE_ACTIVE) {
- throw new IllegalArgumentException("Invalid subscribeType - " + subscribeType);
- }
- mSubscribeType = subscribeType;
- return this;
- }
-
- /**
- * Sets the number of times an active (
- * {@link SubscribeSettings.Builder#setSubscribeType(int)}) subscribe
- * session will transmit a packet. When the count is reached an event
- * will be generated for
- * {@link WifiNanSessionListener#onSubscribeTerminated(int)} with reason=
- * {@link WifiNanSessionListener#TERMINATE_REASON_DONE}.
- *
- * @param subscribeCount Number of subscribe packets to transmit.
- * @return The builder to facilitate chaining
- * {@code builder.setXXX(..).setXXX(..)}.
- */
- public Builder setSubscribeCount(int subscribeCount) {
- if (subscribeCount < 0) {
- throw new IllegalArgumentException("Invalid subscribeCount - must be non-negative");
- }
- mSubscribeCount = subscribeCount;
- return this;
- }
-
- /**
- * Sets the time interval (in seconds) an active (
- * {@link SubscribeSettings.Builder#setSubscribeType(int)}) subscribe
- * session will be alive - i.e. transmitting a packet. When the TTL is
- * reached an event will be generated for
- * {@link WifiNanSessionListener#onSubscribeTerminated(int)} with reason=
- * {@link WifiNanSessionListener#TERMINATE_REASON_DONE}.
- *
- * @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;
- }
-
- /**
- * Build {@link SubscribeSettings} given the current requests made on
- * the builder.
- */
- public SubscribeSettings build() {
- return new SubscribeSettings(mSubscribeType, mSubscribeCount, mTtlSec);
- }
- }
-}
diff --git a/wifi/java/android/net/wifi/nan/TlvBufferUtils.java b/wifi/java/android/net/wifi/nan/TlvBufferUtils.java
index ea8785a..2c5aab4 100644
--- a/wifi/java/android/net/wifi/nan/TlvBufferUtils.java
+++ b/wifi/java/android/net/wifi/nan/TlvBufferUtils.java
@@ -16,11 +16,15 @@
package android.net.wifi.nan;
+import android.annotation.Nullable;
+
import libcore.io.Memory;
import java.nio.BufferOverflowException;
import java.nio.ByteOrder;
+import java.util.Arrays;
import java.util.Iterator;
+import java.util.NoSuchElementException;
/**
* Utility class to construct and parse byte arrays using the TLV format -
@@ -50,8 +54,7 @@
* Values are added to the structure using the {@code TlvConstructor.put*()}
* methods.
* <p>
- * The final byte array is obtained using {@link TlvConstructor#getArray()}
- * and {@link TlvConstructor#getActualLength()} methods.
+ * The final byte array is obtained using {@link TlvConstructor#getArray()}.
*/
public static class TlvConstructor {
private int mTypeSize;
@@ -88,9 +91,9 @@
* @return The constructor to facilitate chaining
* {@code ctr.putXXX(..).putXXX(..)}.
*/
- public TlvConstructor wrap(byte[] array) {
+ public TlvConstructor wrap(@Nullable byte[] array) {
mArray = array;
- mArrayLength = array.length;
+ mArrayLength = (array == null) ? 0 : array.length;
return this;
}
@@ -137,10 +140,13 @@
* @return The constructor to facilitate chaining
* {@code ctr.putXXX(..).putXXX(..)}.
*/
- public TlvConstructor putByteArray(int type, byte[] array, int offset, int length) {
+ public TlvConstructor putByteArray(int type, @Nullable byte[] array, int offset,
+ int length) {
checkLength(length);
addHeader(type, length);
- System.arraycopy(array, offset, mArray, mPosition, length);
+ if (length != 0) {
+ System.arraycopy(array, offset, mArray, mPosition, length);
+ }
mPosition += length;
return this;
}
@@ -155,8 +161,8 @@
* @return The constructor to facilitate chaining
* {@code ctr.putXXX(..).putXXX(..)}.
*/
- public TlvConstructor putByteArray(int type, byte[] array) {
- return putByteArray(type, array, 0, array.length);
+ public TlvConstructor putByteArray(int type, @Nullable byte[] array) {
+ return putByteArray(type, array, 0, (array == null) ? 0 : array.length);
}
/**
@@ -223,23 +229,25 @@
* @return The constructor to facilitate chaining
* {@code ctr.putXXX(..).putXXX(..)}.
*/
- public TlvConstructor putString(int type, String data) {
- return putByteArray(type, data.getBytes(), 0, data.length());
+ 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. Note that the
- * returned array is the fully wrapped (
- * {@link TlvConstructor#wrap(byte[])}) or allocated (
- * {@link TlvConstructor#allocate(int)}) array - which isn't necessarily
- * the actual size of the formatted data. Use
- * {@link TlvConstructor#getActualLength()} to obtain the size of the
- * formatted data.
+ * 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 mArray;
+ return Arrays.copyOf(mArray, getActualLength());
}
/**
@@ -249,7 +257,7 @@
*
* @return The size of the TLV formatted portion of the byte array.
*/
- public int getActualLength() {
+ private int getActualLength() {
return mPosition;
}
@@ -287,75 +295,75 @@
* formatted byte-arrays (i.e. TLV whose Type/T size is 0) the value of
* this field is undefined.
*/
- public int mType;
+ public int type;
/**
* The Length (L) field of the current TLV element.
*/
- public int mLength;
+ public int length;
/**
* The Value (V) field - a raw byte array representing the current TLV
- * element where the entry starts at {@link TlvElement#mOffset}.
+ * element where the entry starts at {@link TlvElement#offset}.
*/
- public byte[] mRefArray;
+ public byte[] refArray;
/**
- * The offset to be used into {@link TlvElement#mRefArray} to access the
+ * The offset to be used into {@link TlvElement#refArray} to access the
* raw data representing the current TLV element.
*/
- public int mOffset;
+ public int offset;
- private TlvElement(int type, int length, byte[] refArray, int offset) {
- mType = type;
- mLength = length;
- mRefArray = refArray;
- mOffset = offset;
+ private TlvElement(int type, int length, @Nullable byte[] refArray, int offset) {
+ this.type = type;
+ this.length = length;
+ this.refArray = refArray;
+ this.offset = offset;
}
/**
* 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#mLength} is != 1 will result in an exception.
+ * {@link TlvElement#length} is != 1 will result in an exception.
*
* @return byte representation of current TLV element.
*/
public byte getByte() {
- if (mLength != 1) {
+ if (length != 1) {
throw new IllegalArgumentException(
- "Accesing a byte from a TLV element of length " + mLength);
+ "Accesing a byte from a TLV element of length " + length);
}
- return mRefArray[mOffset];
+ return refArray[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#mLength} is != 2 will result in an exception.
+ * {@link TlvElement#length} is != 2 will result in an exception.
*
* @return short representation of current TLV element.
*/
public short getShort() {
- if (mLength != 2) {
+ if (length != 2) {
throw new IllegalArgumentException(
- "Accesing a short from a TLV element of length " + mLength);
+ "Accesing a short from a TLV element of length " + length);
}
- return Memory.peekShort(mRefArray, mOffset, ByteOrder.BIG_ENDIAN);
+ return Memory.peekShort(refArray, offset, ByteOrder.BIG_ENDIAN);
}
/**
* 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#mLength} is != 4 will result in an exception.
+ * whose {@link TlvElement#length} is != 4 will result in an exception.
*
* @return integer representation of current TLV element.
*/
public int getInt() {
- if (mLength != 4) {
+ if (length != 4) {
throw new IllegalArgumentException(
- "Accesing an int from a TLV element of length " + mLength);
+ "Accesing an int from a TLV element of length " + length);
}
- return Memory.peekInt(mRefArray, mOffset, ByteOrder.BIG_ENDIAN);
+ return Memory.peekInt(refArray, offset, ByteOrder.BIG_ENDIAN);
}
/**
@@ -364,7 +372,7 @@
* @return String repersentation of the current TLV element.
*/
public String getString() {
- return new String(mRefArray, mOffset, mLength);
+ return new String(refArray, offset, length);
}
}
@@ -388,10 +396,8 @@
* @param lengthSize Number of bytes sued for the Length (L) field.
* Values values are 1 or 2 bytes.
* @param array The TLV formatted byte-array to parse.
- * @param length The number of bytes of the array to be used in the
- * parsing.
*/
- public TlvIterable(int typeSize, int lengthSize, byte[] array, int length) {
+ 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);
@@ -399,7 +405,7 @@
mTypeSize = typeSize;
mLengthSize = lengthSize;
mArray = array;
- mArrayLength = length;
+ mArrayLength = (array == null) ? 0 : array.length;
}
/**
@@ -420,21 +426,21 @@
first = false;
builder.append(" (");
if (mTypeSize != 0) {
- builder.append("T=" + tlv.mType + ",");
+ builder.append("T=" + tlv.type + ",");
}
- builder.append("L=" + tlv.mLength + ") ");
- if (tlv.mLength == 0) {
+ builder.append("L=" + tlv.length + ") ");
+ if (tlv.length == 0) {
builder.append("<null>");
- } else if (tlv.mLength == 1) {
+ } else if (tlv.length == 1) {
builder.append(tlv.getByte());
- } else if (tlv.mLength == 2) {
+ } else if (tlv.length == 2) {
builder.append(tlv.getShort());
- } else if (tlv.mLength == 4) {
+ } else if (tlv.length == 4) {
builder.append(tlv.getInt());
} else {
builder.append("<bytes>");
}
- if (tlv.mLength != 0) {
+ if (tlv.length != 0) {
builder.append(" (S='" + tlv.getString() + "')");
}
}
@@ -459,6 +465,10 @@
@Override
public TlvElement next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+
int type = 0;
if (mTypeSize == 1) {
type = mArray[mOffset];
@@ -487,4 +497,40 @@
};
}
}
+
+ /**
+ * 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.
+ * @return A boolean indicating whether the array is valid (true) or invalid (false).
+ */
+ public static boolean isValid(@Nullable byte[] array, int typeSize, int lengthSize) {
+ 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.BIG_ENDIAN);
+ }
+ }
+
+ return nextTlvIndex == array.length;
+ }
}
diff --git a/wifi/java/android/net/wifi/nan/WifiNanEventCallback.java b/wifi/java/android/net/wifi/nan/WifiNanEventCallback.java
new file mode 100644
index 0000000..6e714f1
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/WifiNanEventCallback.java
@@ -0,0 +1,105 @@
+/*
+ * 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.nan;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Base class for NAN events callbacks. Should be extended by applications and set when calling
+ * {@link WifiNanManager#connect(android.os.Looper, WifiNanEventCallback)}. These are callbacks
+ * applying to the NAN connection as a whole - not to specific publish or subscribe sessions -
+ * for that see {@link WifiNanSessionCallback}.
+ *
+ * @hide PROPOSED_NAN_API
+ */
+public class WifiNanEventCallback {
+ /** @hide */
+ @IntDef({
+ REASON_INVALID_ARGS, REASON_ALREADY_CONNECTED_INCOMPAT_CONFIG, REASON_OTHER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EventReasonCodes {
+ }
+
+ /**
+ * Indicates invalid argument in the requested operation. Failure reason flag for
+ * {@link WifiNanEventCallback#onConnectFail(int)}.
+ */
+ public static final int REASON_INVALID_ARGS = 1000;
+
+ /**
+ * Indicates that a {@link ConfigRequest} passed in
+ * {@link WifiNanManager#connect(android.os.Looper, ConfigRequest, WifiNanEventCallback)}
+ * couldn't be applied since other connections already exist with an incompatible
+ * configurations. Failure reason flag for {@link WifiNanEventCallback#onConnectFail(int)}.
+ */
+ public static final int REASON_ALREADY_CONNECTED_INCOMPAT_CONFIG = 1001;
+
+ /**
+ * Indicates an unspecified error occurred during the operation. Failure reason flag for
+ * {@link WifiNanEventCallback#onConnectFail(int)}.
+ */
+ public static final int REASON_OTHER = 1002;
+
+ /**
+ * Called when NAN connect operation
+ * {@link WifiNanManager#connect(android.os.Looper, WifiNanEventCallback)}
+ * is completed and that we can now start discovery sessions or connections.
+ */
+ public void onConnectSuccess() {
+ /* empty */
+ }
+
+ /**
+ * Called when NAN connect operation
+ * {@link WifiNanManager#connect(android.os.Looper, WifiNanEventCallback)} failed.
+ *
+ * @param reason Failure reason code, see
+ * {@code WifiNanEventCallback.REASON_*}.
+ */
+ public void onConnectFail(@EventReasonCodes int reason) {
+ /* empty */
+ }
+
+ /**
+ * Called when NAN identity (the MAC address representing our NAN discovery interface) has
+ * 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-NAN)
+ * discovery but needs to set up a NAN connection. The provided NAN discovery interface MAC
+ * address can then be used in
+ * {@link WifiNanManager#createNetworkSpecifier(int, byte[], byte[])}.
+ * <p>
+ * This callback is only called if the NAN connection enables it using
+ * {@link ConfigRequest.Builder#setEnableIdentityChangeCallback(boolean)} in
+ * {@link WifiNanManager#connect(android.os.Looper, ConfigRequest, WifiNanEventCallback)}
+ * . It is disabled by default since it may result in additional wake-ups of the host -
+ * increasing power.
+ *
+ * @param mac The MAC address of the NAN discovery interface. The application must have the
+ * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} to get the actual MAC address,
+ * otherwise all 0's will be provided.
+ */
+ public void onIdentityChanged(byte[] mac) {
+ /* empty */
+ }
+}
diff --git a/wifi/java/android/net/wifi/nan/WifiNanEventListener.java b/wifi/java/android/net/wifi/nan/WifiNanEventListener.java
deleted file mode 100644
index 9e6ed4e..0000000
--- a/wifi/java/android/net/wifi/nan/WifiNanEventListener.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * 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.nan;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
-
-/**
- * Base class for NAN events callbacks. Should be extended by applications
- * wanting notifications. These are callbacks applying to the NAN connection as
- * a whole - not to specific publish or subscribe sessions - for that see
- * {@link WifiNanSessionListener}.
- * <p>
- * During registration specify which specific events are desired using a set of
- * {@code NanEventListener.LISTEN_*} flags OR'd together. Only those events will
- * be delivered to the registered listener. Override those callbacks
- * {@code NanEventListener.on*} for the registered events.
- *
- * @hide PROPOSED_NAN_API
- */
-public class WifiNanEventListener {
- private static final String TAG = "WifiNanEventListener";
- private static final boolean DBG = false;
- private static final boolean VDBG = false; // STOPSHIP if true
-
- /**
- * Configuration completion callback event registration flag. Corresponding
- * callback is {@link WifiNanEventListener#onConfigCompleted(ConfigRequest)}.
- */
- public static final int LISTEN_CONFIG_COMPLETED = 0x1 << 0;
-
- /**
- * Configuration failed callback event registration flag. Corresponding
- * callback is
- * {@link WifiNanEventListener#onConfigFailed(ConfigRequest, int)}.
- */
- public static final int LISTEN_CONFIG_FAILED = 0x1 << 1;
-
- /**
- * NAN cluster is down callback event registration flag. Corresponding
- * callback is {@link WifiNanEventListener#onNanDown(int)}.
- */
- public static final int LISTEN_NAN_DOWN = 0x1 << 2;
-
- /**
- * NAN identity has changed event registration flag. This may be due to
- * joining a cluster, starting a cluster, or discovery interface change. The
- * implication is that peers you've been communicating with may no longer
- * recognize you and you need to re-establish your identity. Corresponding
- * callback is {@link WifiNanEventListener#onIdentityChanged()}.
- */
- public static final int LISTEN_IDENTITY_CHANGED = 0x1 << 3;
-
- private final Handler mHandler;
-
- /**
- * Constructs a {@link WifiNanEventListener} using the looper of the current
- * thread. I.e. all callbacks will be delivered on the current thread.
- */
- public WifiNanEventListener() {
- this(Looper.myLooper());
- }
-
- /**
- * Constructs a {@link WifiNanEventListener} using the specified looper. I.e.
- * all callbacks will delivered on the thread of the specified looper.
- *
- * @param looper The looper on which to execute the callbacks.
- */
- public WifiNanEventListener(Looper looper) {
- if (VDBG) Log.v(TAG, "ctor: looper=" + looper);
- mHandler = new Handler(looper) {
- @Override
- public void handleMessage(Message msg) {
- if (DBG) Log.d(TAG, "What=" + msg.what + ", msg=" + msg);
- switch (msg.what) {
- case LISTEN_CONFIG_COMPLETED:
- WifiNanEventListener.this.onConfigCompleted((ConfigRequest) msg.obj);
- break;
- case LISTEN_CONFIG_FAILED:
- WifiNanEventListener.this.onConfigFailed((ConfigRequest) msg.obj, msg.arg1);
- break;
- case LISTEN_NAN_DOWN:
- WifiNanEventListener.this.onNanDown(msg.arg1);
- break;
- case LISTEN_IDENTITY_CHANGED:
- WifiNanEventListener.this.onIdentityChanged();
- break;
- }
- }
- };
- }
-
- /**
- * Called when NAN configuration is completed. Event will only be delivered
- * if registered using {@link WifiNanEventListener#LISTEN_CONFIG_COMPLETED}. A
- * dummy (empty implementation printing out a warning). Make sure to
- * override if registered.
- *
- * @param completedConfig The actual configuration request which was
- * completed. Note that it may be different from that requested
- * by the application. The service combines configuration
- * requests from all applications.
- */
- public void onConfigCompleted(ConfigRequest completedConfig) {
- Log.w(TAG, "onConfigCompleted: called in stub - override if interested or disable");
- }
-
- /**
- * Called when NAN configuration failed. Event will only be delivered if
- * registered using {@link WifiNanEventListener#LISTEN_CONFIG_FAILED}. A dummy
- * (empty implementation printing out a warning). Make sure to override if
- * registered.
- *
- * @param reason Failure reason code, see {@code NanSessionListener.FAIL_*}.
- */
- public void onConfigFailed(ConfigRequest failedConfig, int reason) {
- Log.w(TAG, "onConfigFailed: called in stub - override if interested or disable");
- }
-
- /**
- * Called when NAN cluster is down. Event will only be delivered if
- * registered using {@link WifiNanEventListener#LISTEN_NAN_DOWN}. A dummy (empty
- * implementation printing out a warning). Make sure to override if
- * registered.
- *
- * @param reason Reason code for event, see {@code NanSessionListener.FAIL_*}.
- */
- public void onNanDown(int reason) {
- Log.w(TAG, "onNanDown: called in stub - override if interested or disable");
- }
-
- /**
- * Called when NAN identity has changed. This may be due to joining a
- * cluster, starting a cluster, or discovery interface change. The
- * implication is that peers you've been communicating with may no longer
- * recognize you and you need to re-establish your identity. Event will only
- * be delivered if registered using
- * {@link WifiNanEventListener#LISTEN_IDENTITY_CHANGED}. A dummy (empty
- * implementation printing out a warning). Make sure to override if
- * registered.
- */
- public void onIdentityChanged() {
- if (VDBG) Log.v(TAG, "onIdentityChanged: called in stub - override if interested");
- }
-
- /**
- * {@hide}
- */
- public IWifiNanEventListener callback = new IWifiNanEventListener.Stub() {
- @Override
- public void onConfigCompleted(ConfigRequest completedConfig) {
- if (VDBG) Log.v(TAG, "onConfigCompleted: configRequest=" + completedConfig);
-
- Message msg = mHandler.obtainMessage(LISTEN_CONFIG_COMPLETED);
- msg.obj = completedConfig;
- mHandler.sendMessage(msg);
- }
-
- @Override
- public void onConfigFailed(ConfigRequest failedConfig, int reason) {
- if (VDBG) {
- Log.v(TAG, "onConfigFailed: failedConfig=" + failedConfig + ", reason=" + reason);
- }
-
- Message msg = mHandler.obtainMessage(LISTEN_CONFIG_FAILED);
- msg.arg1 = reason;
- msg.obj = failedConfig;
- mHandler.sendMessage(msg);
- }
-
- @Override
- public void onNanDown(int reason) {
- if (VDBG) Log.v(TAG, "onNanDown: reason=" + reason);
-
- Message msg = mHandler.obtainMessage(LISTEN_NAN_DOWN);
- msg.arg1 = reason;
- mHandler.sendMessage(msg);
- }
-
- @Override
- public void onIdentityChanged() {
- if (VDBG) Log.v(TAG, "onIdentityChanged");
-
- Message msg = mHandler.obtainMessage(LISTEN_IDENTITY_CHANGED);
- mHandler.sendMessage(msg);
- }
- };
-}
diff --git a/wifi/java/android/net/wifi/nan/WifiNanManager.java b/wifi/java/android/net/wifi/nan/WifiNanManager.java
index 1b78beb..82d22bc 100644
--- a/wifi/java/android/net/wifi/nan/WifiNanManager.java
+++ b/wifi/java/android/net/wifi/nan/WifiNanManager.java
@@ -16,23 +16,103 @@
package android.net.wifi.nan;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkRequest;
+import android.net.wifi.RttManager;
import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
+import android.util.Base64;
import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import dalvik.system.CloseGuard;
+
+import libcore.util.HexEncoding;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
/**
- * This class provides the primary API for managing Wi-Fi NAN operation:
- * including discovery and data-links. Get an instance of this class by calling
+ * This class provides the primary API for managing Wi-Fi NAN operations:
+ * discovery and peer-to-peer data connections. Get an instance of this class by calling
* {@link android.content.Context#getSystemService(String)
* Context.getSystemService(Context.WIFI_NAN_SERVICE)}.
* <p>
* The class provides access to:
* <ul>
- * <li>Configure a NAN connection and register for events.
- * <li>Create publish and subscribe sessions.
- * <li>Create NAN network specifier to be used to create a NAN network.
+ * <li>Initialize a NAN cluster (peer-to-peer synchronization). Refer to
+ * {@link #connect(Looper, WifiNanEventCallback)}.
+ * <li>Create discovery sessions (publish or subscribe sessions).
+ * Refer to {@link #publish(PublishConfig, WifiNanSessionCallback)} and
+ * {@link #subscribe(SubscribeConfig, WifiNanSessionCallback)}.
+ * <li>Create a NAN network specifier to be used with
+ * {@link ConnectivityManager#requestNetwork(NetworkRequest, ConnectivityManager.NetworkCallback)}
+ * to set-up a NAN connection with a peer. Refer to
+ * {@link WifiNanSession#createNetworkSpecifier(int, int, byte[])} and
+ * {@link #createNetworkSpecifier(int, byte[], byte[])}.
* </ul>
+ * <p>
+ * NAN may not be usable when Wi-Fi is disabled (and other conditions). To validate that
+ * the functionality is available use the {@link #isUsageEnabled()} function. To track
+ * changes in NAN usability register for the {@link #ACTION_WIFI_NAN_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 #connect(Looper, WifiNanEventCallback)} to initialize a NAN
+ * cluster - before making any other NAN operation. NAN cluster membership is a device-wide
+ * operation - the API guarantees that the device is in a cluster or joins a NAN cluster (or
+ * starts one if none can be found). Information about connection success (or failure) are
+ * returned in callbacks of {@link WifiNanEventCallback}. Proceed with NAN discovery or
+ * connection setup only after receiving confirmation that NAN connection succeeded -
+ * {@link WifiNanEventCallback#onConnectSuccess()}.
+ * When an application is finished using NAN it <b>must</b> use the {@link #disconnect()} API
+ * to indicate to the NAN service that the device may disconnect from the NAN cluster. The
+ * device will actually disconnect from the NAN cluster once the last application disconnects.
+ * <p>
+ * Once a NAN connection is confirmed use the
+ * {@link #publish(PublishConfig, WifiNanSessionCallback)} or
+ * {@link #subscribe(SubscribeConfig, WifiNanSessionCallback)} to create publish or subscribe
+ * NAN discovery sessions. Events are called on the provided callback object
+ * {@link WifiNanSessionCallback}. Specifically, the
+ * {@link WifiNanSessionCallback#onPublishStarted(WifiNanPublishSession)} and
+ * {@link WifiNanSessionCallback#onSubscribeStarted(WifiNanSubscribeSession)} return
+ * {@link WifiNanPublishSession} and {@link WifiNanSubscribeSession} objects respectively on
+ * which additional session operations can be performed, e.g. updating the session
+ * {@link WifiNanPublishSession#updatePublish(PublishConfig)} and
+ * {@link WifiNanSubscribeSession#updateSubscribe(SubscribeConfig)}. Sessions can also be
+ * used to send messages using the {@link WifiNanSession#sendMessage(int, byte[], int)} APIs.
+ * When an application is finished with a discovery session it <b>must</b> terminate it using
+ * the {@link WifiNanSession#terminate()} API.
+ * <p>
+ * Creating connections between NAN 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_NAN}.
+ * <li>{@link NetworkRequest.Builder#setNetworkSpecifier(String)} using
+ * {@link #createNetworkSpecifier(int, byte[], byte[])} or
+ * {@link WifiNanSession#createNetworkSpecifier(int, int, byte[])}.
+ * </ul>
*
* @hide PROPOSED_NAN_API
*/
@@ -41,292 +121,1080 @@
private static final boolean DBG = false;
private static final boolean VDBG = false; // STOPSHIP if true
- private IBinder mBinder;
-
- private IWifiNanManager mService;
+ private static final int INVALID_CLIENT_ID = 0;
/**
- * {@hide}
+ * Keys used to generate a Network Specifier for the NAN network request. The network specifier
+ * is formatted as a JSON string.
*/
- public WifiNanManager(IWifiNanManager service) {
+
+ /**
+ * TYPE_1A: role, client_id, session_id, peer_id, token
+ * @hide
+ */
+ public static final int NETWORK_SPECIFIER_TYPE_1A = 0;
+
+ /**
+ * TYPE_1B: role, client_id, session_id, peer_id [only permitted for RESPONDER]
+ * @hide
+ */
+ public static final int NETWORK_SPECIFIER_TYPE_1B = 1;
+
+ /**
+ * TYPE_1C: role, client_id, session_id, token [only permitted for RESPONDER]
+ * @hide
+ */
+ public static final int NETWORK_SPECIFIER_TYPE_1C = 2;
+
+ /**
+ * TYPE_1C: role, client_id, session_id [only permitted for RESPONDER]
+ * @hide
+ */
+ public static final int NETWORK_SPECIFIER_TYPE_1D = 3;
+
+ /**
+ * TYPE_2A: role, client_id, peer_mac, token
+ * @hide
+ */
+ public static final int NETWORK_SPECIFIER_TYPE_2A = 4;
+
+ /**
+ * TYPE_2B: role, client_id, peer_mac [only permitted for RESPONDER]
+ * @hide
+ */
+ public static final int NETWORK_SPECIFIER_TYPE_2B = 5;
+
+ /**
+ * TYPE_2C: role, client_id, token [only permitted for RESPONDER]
+ * @hide
+ */
+ public static final int NETWORK_SPECIFIER_TYPE_2C = 6;
+
+ /**
+ * TYPE_2D: role, client_id [only permitted for RESPONDER]
+ * @hide
+ */
+ public static final int NETWORK_SPECIFIER_TYPE_2D = 7;
+
+ /** @hide */
+ public static final int NETWORK_SPECIFIER_TYPE_MAX_VALID = NETWORK_SPECIFIER_TYPE_2D;
+
+ /** @hide */
+ public static final String NETWORK_SPECIFIER_KEY_TYPE = "type";
+
+ /** @hide */
+ public static final String NETWORK_SPECIFIER_KEY_ROLE = "role";
+
+ /** @hide */
+ public static final String NETWORK_SPECIFIER_KEY_CLIENT_ID = "client_id";
+
+ /** @hide */
+ public static final String NETWORK_SPECIFIER_KEY_SESSION_ID = "session_id";
+
+ /** @hide */
+ public static final String NETWORK_SPECIFIER_KEY_PEER_ID = "peer_id";
+
+ /** @hide */
+ public static final String NETWORK_SPECIFIER_KEY_PEER_MAC = "peer_mac";
+
+ /** @hide */
+ public static final String NETWORK_SPECIFIER_KEY_TOKEN = "token";
+
+ /**
+ * Broadcast intent action to indicate whether Wi-Fi NAN is enabled or
+ * disabled. An extra {@link #EXTRA_WIFI_STATE} provides the state
+ * information as int using {@link #WIFI_NAN_STATE_DISABLED} and
+ * {@link #WIFI_NAN_STATE_ENABLED} constants. This broadcast is <b>not</b> sticky,
+ * use the {@link #isUsageEnabled()} API after registering the broadcast to check the current
+ * state of Wi-Fi NAN.
+ *
+ * @see #EXTRA_WIFI_STATE
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_WIFI_NAN_STATE_CHANGED =
+ "android.net.wifi.nan.action.WIFI_NAN_STATE_CHANGED";
+
+ /**
+ * The lookup key for an int value indicating whether Wi-Fi NAN is enabled or
+ * disabled. Retrieve it with
+ * {@link android.content.Intent#getIntExtra(String,int)}.
+ *
+ * @see #WIFI_NAN_STATE_DISABLED
+ * @see #WIFI_NAN_STATE_ENABLED
+ */
+ public static final String EXTRA_WIFI_STATE = "android.net.wifi.nan.extra.WIFI_STATE";
+
+ /**
+ * Wi-Fi NAN is disabled.
+ *
+ * @see #ACTION_WIFI_NAN_STATE_CHANGED
+ */
+ public static final int WIFI_NAN_STATE_DISABLED = 1;
+
+ /**
+ * Wi-Fi NAN is enabled.
+ *
+ * @see #ACTION_WIFI_NAN_STATE_CHANGED
+ */
+ public static final int WIFI_NAN_STATE_ENABLED = 2;
+
+ /** @hide */
+ @IntDef({
+ WIFI_NAN_DATA_PATH_ROLE_INITIATOR, WIFI_NAN_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 NAN network.
+ *
+ * @see WifiNanSession#createNetworkSpecifier(int, int, byte[])
+ * @see #createNetworkSpecifier(int, byte[], byte[])
+ */
+ public static final int WIFI_NAN_DATA_PATH_ROLE_INITIATOR = 0;
+
+ /**
+ * Connection creation role is that of RESPONDER. Used to create a network specifier string
+ * when requesting a NAN network.
+ *
+ * @see WifiNanSession#createNetworkSpecifier(int, int, byte[])
+ * @see #createNetworkSpecifier(int, byte[], byte[])
+ */
+ public static final int WIFI_NAN_DATA_PATH_ROLE_RESPONDER = 1;
+
+ private final Context mContext;
+ private final IWifiNanManager mService;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ private final Object mLock = new Object(); // lock access to the following vars
+
+ @GuardedBy("mLock")
+ private final IBinder mBinder = new Binder();
+
+ @GuardedBy("mLock")
+ private int mClientId = INVALID_CLIENT_ID;
+
+ @GuardedBy("mLock")
+ private Looper mLooper;
+
+ @GuardedBy("mLock")
+ private SparseArray<RttManager.RttListener> mRangingListeners = new SparseArray<>();
+
+ /** @hide */
+ public WifiNanManager(Context context, IWifiNanManager service) {
+ mContext = context;
mService = service;
}
/**
- * Re-connect to the Wi-Fi NAN service - enabling the application to execute
- * {@link WifiNanManager} APIs. Application don't normally need to call this
- * API since it is executed in the constructor. However, applications which
- * have explicitly {@link WifiNanManager#disconnect()} need to call this
- * function to re-connect.
+ * Enable the usage of the NAN API. Doesn't actually turn on NAN cluster formation - that only
+ * happens when a connection is made. {@link #ACTION_WIFI_NAN_STATE_CHANGED} broadcast will be
+ * triggered.
*
- * @param listener A listener extended from {@link WifiNanEventListener}.
- * @param events The set of events to be delivered to the {@code listener}.
- * OR'd event flags from {@link WifiNanEventListener
- * NanEventListener.LISTEN*}.
+ * @hide
*/
- public void connect(WifiNanEventListener listener, int events) {
+ public void enableUsage() {
try {
- if (VDBG) Log.v(TAG, "connect()");
- if (listener == null) {
- throw new IllegalArgumentException("Invalid listener - must not be null");
- }
- if (mBinder == null) {
- mBinder = new Binder();
- }
- mService.connect(mBinder, listener.callback, events);
+ mService.enableUsage();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Disconnect from the Wi-Fi NAN service and destroy all outstanding
- * operations - i.e. all publish and subscribes are terminated, any
- * outstanding data-link is shut-down, and all requested NAN configurations
- * are cancelled.
+ * Disable the usage of the NAN API. All attempts to connect() will be rejected. All open
+ * connections and sessions will be terminated. {@link #ACTION_WIFI_NAN_STATE_CHANGED} broadcast
+ * will be triggered.
+ *
+ * @hide
+ */
+ public void disableUsage() {
+ try {
+ mService.disableUsage();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the current status of NAN API: whether or not usage is enabled. To track changes
+ * in the state of NAN API register for the {@link #ACTION_WIFI_NAN_STATE_CHANGED} broadcast.
+ *
+ * @return A boolean indicating whether the app can use the NAN API (true)
+ * or not (false).
+ */
+ public boolean isUsageEnabled() {
+ try {
+ return mService.isUsageEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Connect to the Wi-Fi NAN service - enabling the application to create discovery session or
+ * create connection to peers. The device will connect to an existing cluster if it can find
+ * one or create a new cluster (if it is the first to enable NAN in its vicinity). Results
+ * (e.g. successful connection to a cluster) are provided to the {@code callback} object.
+ * An application <b>must</b> call {@link #disconnect()} when done with the Wi-Fi NAN
+ * connection.
* <p>
- * An application may then re-connect using
- * {@link WifiNanManager#connect(WifiNanEventListener, int)} .
+ * Note: a NAN cluster is a shared resource - if the device is already connected to a cluster
+ * than this function will simply indicate success immediately.
+ *
+ * @param looper The Looper on which to execute all callbacks related to the
+ * connection - including all sessions opened as part of this
+ * connection.
+ * @param callback A callback extended from {@link WifiNanEventCallback}.
+ */
+ public void connect(@NonNull Looper looper, @NonNull WifiNanEventCallback callback) {
+ connect(looper, null, callback);
+ }
+
+ /**
+ * Connect to the Wi-Fi NAN service - enabling the application to create discovery session or
+ * create connection to peers. The device will connect to an existing cluster if it can find
+ * one or create a new cluster (if it is the first to enable NAN in its vicinity). Results
+ * (e.g. successful connection to a cluster) are provided to the {@code callback} object.
+ * An application <b>must</b> call {@link #disconnect()} when done with the Wi-Fi NAN
+ * connection. Allows requesting a specific configuration using {@link ConfigRequest}. If not
+ * necessary (default configuration should usually work) use the
+ * {@link #connect(Looper, WifiNanEventCallback)} method instead.
+ * <p>
+ * Note: a NAN cluster is a shared resource - if the device is already connected to a cluster
+ * than this function will simply indicate success immediately.
+ *
+ * @param looper The Looper on which to execute all callbacks related to the
+ * connection - including all sessions opened as part of this
+ * connection.
+ * @param configRequest The requested NAN configuration.
+ * @param callback A callback extended from {@link WifiNanEventCallback}.
+ */
+ public void connect(@NonNull Looper looper, @Nullable ConfigRequest configRequest,
+ @NonNull WifiNanEventCallback callback) {
+ if (VDBG) {
+ Log.v(TAG, "connect(): looper=" + looper + ", callback=" + callback + ", configRequest="
+ + configRequest);
+ }
+
+ synchronized (mLock) {
+ mLooper = looper;
+
+ try {
+ mClientId = mService.connect(mBinder, mContext.getOpPackageName(),
+ new WifiNanEventCallbackProxy(this, looper, callback), configRequest);
+ } catch (RemoteException e) {
+ mClientId = INVALID_CLIENT_ID;
+ mLooper = null;
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ mCloseGuard.open("disconnect");
+ }
+
+ /**
+ * Disconnect from the Wi-Fi NAN service and, if no other applications are connected to NAN,
+ * also disconnect from the NAN cluster. 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 terminate these discovery sessions and
+ * connections explicitly before a disconnect.
+ * <p>
+ * An application may re-connect after a disconnect using
+ * {@link WifiNanManager#connect(Looper, WifiNanEventCallback)} .
*/
public void disconnect() {
- try {
- if (VDBG) Log.v(TAG, "disconnect()");
- mService.disconnect(mBinder);
- mBinder = null;
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
+ if (VDBG) Log.v(TAG, "disconnect()");
- /**
- * Requests a NAN configuration, specified by {@link ConfigRequest}. Note
- * that NAN is a shared resource and the device can only be a member of a
- * single cluster. Thus the service may merge configuration requests from
- * multiple applications and configure NAN differently from individual
- * requests.
- * <p>
- * The {@link WifiNanEventListener#onConfigCompleted(ConfigRequest)} will be
- * called when configuration is completed (if a listener is registered for
- * this specific event).
- *
- * @param configRequest The requested NAN configuration.
- */
- public void requestConfig(ConfigRequest configRequest) {
- if (VDBG) Log.v(TAG, "requestConfig(): configRequest=" + configRequest);
- try {
- mService.requestConfig(configRequest);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Request a NAN publish session. The results of the publish session
- * operation will result in callbacks to the indicated listener:
- * {@link WifiNanSessionListener NanSessionListener.on*}.
- *
- * @param publishData The {@link PublishData} specifying the contents of the
- * publish session.
- * @param publishSettings The {@link PublishSettings} specifying the
- * settings for the publish session.
- * @param listener The {@link WifiNanSessionListener} derived objects to be used
- * for the event callbacks specified by {@code events}.
- * @param events The list of events to be delivered to the {@code listener}
- * object. An OR'd value of {@link WifiNanSessionListener
- * NanSessionListener.LISTEN_*}.
- * @return The {@link WifiNanPublishSession} which can be used to further
- * control the publish session.
- */
- public WifiNanPublishSession publish(PublishData publishData, PublishSettings publishSettings,
- WifiNanSessionListener listener, int events) {
- return publishRaw(publishData, publishSettings, listener,
- events | WifiNanSessionListener.LISTEN_HIDDEN_FLAGS);
- }
-
- /**
- * Same as publish(*) but does not modify the event flag
- *
- * @hide
- */
- public WifiNanPublishSession publishRaw(PublishData publishData,
- PublishSettings publishSettings, WifiNanSessionListener listener, int events) {
- if (VDBG) Log.v(TAG, "publish(): data='" + publishData + "', settings=" + publishSettings);
-
- if (publishSettings.mPublishType == PublishSettings.PUBLISH_TYPE_UNSOLICITED
- && publishData.mRxFilterLength != 0) {
- throw new IllegalArgumentException("Invalid publish data & settings: UNSOLICITED "
- + "publishes (active) can't have an Rx filter");
- }
- if (publishSettings.mPublishType == PublishSettings.PUBLISH_TYPE_SOLICITED
- && publishData.mTxFilterLength != 0) {
- throw new IllegalArgumentException("Invalid publish data & settings: SOLICITED "
- + "publishes (passive) can't have a Tx filter");
- }
- if (listener == null) {
- throw new IllegalArgumentException("Invalid listener - must not be null");
- }
-
- int sessionId;
-
- try {
- sessionId = mService.createSession(listener.callback, events);
- if (DBG) Log.d(TAG, "publish: session created - sessionId=" + sessionId);
- mService.publish(sessionId, publishData, publishSettings);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
-
- return new WifiNanPublishSession(this, sessionId);
- }
-
- /**
- * {@hide}
- */
- public void publish(int sessionId, PublishData publishData, PublishSettings publishSettings) {
- if (VDBG) Log.v(TAG, "publish(): data='" + publishData + "', settings=" + publishSettings);
-
- if (publishSettings.mPublishType == PublishSettings.PUBLISH_TYPE_UNSOLICITED
- && publishData.mRxFilterLength != 0) {
- throw new IllegalArgumentException("Invalid publish data & settings: UNSOLICITED "
- + "publishes (active) can't have an Rx filter");
- }
- if (publishSettings.mPublishType == PublishSettings.PUBLISH_TYPE_SOLICITED
- && publishData.mTxFilterLength != 0) {
- throw new IllegalArgumentException("Invalid publish data & settings: SOLICITED "
- + "publishes (passive) can't have a Tx filter");
- }
-
- try {
- mService.publish(sessionId, publishData, publishSettings);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- /**
- * Request a NAN subscribe session. The results of the subscribe session
- * operation will result in callbacks to the indicated listener:
- * {@link WifiNanSessionListener NanSessionListener.on*}.
- *
- * @param subscribeData The {@link SubscribeData} specifying the contents of
- * the subscribe session.
- * @param subscribeSettings The {@link SubscribeSettings} specifying the
- * settings for the subscribe session.
- * @param listener The {@link WifiNanSessionListener} derived objects to be used
- * for the event callbacks specified by {@code events}.
- * @param events The list of events to be delivered to the {@code listener}
- * object. An OR'd value of {@link WifiNanSessionListener
- * NanSessionListener.LISTEN_*}.
- * @return The {@link WifiNanSubscribeSession} which can be used to further
- * control the subscribe session.
- */
- public WifiNanSubscribeSession subscribe(SubscribeData subscribeData,
- SubscribeSettings subscribeSettings,
- WifiNanSessionListener listener, int events) {
- return subscribeRaw(subscribeData, subscribeSettings, listener,
- events | WifiNanSessionListener.LISTEN_HIDDEN_FLAGS);
- }
-
- /**
- * Same as subscribe(*) but does not modify the event flag
- *
- * @hide
- */
- public WifiNanSubscribeSession subscribeRaw(SubscribeData subscribeData,
- SubscribeSettings subscribeSettings, WifiNanSessionListener listener, int events) {
- if (VDBG) {
- Log.v(TAG, "subscribe(): data='" + subscribeData + "', settings=" + subscribeSettings);
- }
-
- if (subscribeSettings.mSubscribeType == SubscribeSettings.SUBSCRIBE_TYPE_ACTIVE
- && subscribeData.mRxFilterLength != 0) {
- throw new IllegalArgumentException(
- "Invalid subscribe data & settings: ACTIVE subscribes can't have an Rx filter");
- }
- if (subscribeSettings.mSubscribeType == SubscribeSettings.SUBSCRIBE_TYPE_PASSIVE
- && subscribeData.mTxFilterLength != 0) {
- throw new IllegalArgumentException(
- "Invalid subscribe data & settings: PASSIVE subscribes can't have a Tx filter");
- }
-
- int sessionId;
-
- try {
- sessionId = mService.createSession(listener.callback, events);
- if (DBG) Log.d(TAG, "subscribe: session created - sessionId=" + sessionId);
- mService.subscribe(sessionId, subscribeData, subscribeSettings);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
-
- return new WifiNanSubscribeSession(this, sessionId);
- }
-
- /**
- * {@hide}
- */
- public void subscribe(int sessionId, SubscribeData subscribeData,
- SubscribeSettings subscribeSettings) {
- if (VDBG) {
- Log.v(TAG, "subscribe(): data='" + subscribeData + "', settings=" + subscribeSettings);
- }
-
- if (subscribeSettings.mSubscribeType == SubscribeSettings.SUBSCRIBE_TYPE_ACTIVE
- && subscribeData.mRxFilterLength != 0) {
- throw new IllegalArgumentException(
- "Invalid subscribe data & settings: ACTIVE subscribes can't have an Rx filter");
- }
- if (subscribeSettings.mSubscribeType == SubscribeSettings.SUBSCRIBE_TYPE_PASSIVE
- && subscribeData.mTxFilterLength != 0) {
- throw new IllegalArgumentException(
- "Invalid subscribe data & settings: PASSIVE subscribes can't have a Tx filter");
- }
-
- try {
- mService.subscribe(sessionId, subscribeData, subscribeSettings);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * {@hide}
- */
- public void stopSession(int sessionId) {
- if (DBG) Log.d(TAG, "Stop NAN session #" + sessionId);
-
- try {
- mService.stopSession(sessionId);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * {@hide}
- */
- public void destroySession(int sessionId) {
- if (DBG) Log.d(TAG, "Destroy NAN session #" + sessionId);
-
- try {
- mService.destroySession(sessionId);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * {@hide}
- */
- public void sendMessage(int sessionId, int peerId, byte[] message, int messageLength,
- int messageId) {
- try {
- if (VDBG) {
- Log.v(TAG, "sendMessage(): sessionId=" + sessionId + ", peerId=" + peerId
- + ", messageLength=" + messageLength + ", messageId=" + messageId);
+ IBinder binder;
+ int clientId;
+ synchronized (mLock) {
+ if (mClientId == INVALID_CLIENT_ID) {
+ Log.w(TAG, "disconnect(): called with invalid client ID - not connected first?");
+ return;
}
- mService.sendMessage(sessionId, peerId, message, messageLength, messageId);
+
+ binder = mBinder;
+ clientId = mClientId;
+
+ mLooper = null;
+ mClientId = INVALID_CLIENT_ID;
+ }
+
+ mCloseGuard.close();
+ try {
+ mService.disconnect(clientId, binder);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
+
+ /** @hide */
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ mCloseGuard.warnIfOpen();
+ disconnect();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Issue a request to the NAN service to create a new NAN publish discovery session, using
+ * the specified {@code publishConfig} configuration. The results of the publish operation
+ * are routed to the callbacks of {@link WifiNanSessionCallback}:
+ * <ul>
+ * <li>{@link WifiNanSessionCallback#onPublishStarted(WifiNanPublishSession)} 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 WifiNanSessionCallback#onSessionConfigFail(int)} 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 WifiNanPublishSession#updatePublish(PublishConfig)}.
+ * <p>
+ * An application must use the {@link WifiNanSession#terminate()} to terminate the publish
+ * discovery session once it isn't needed. This will free resources as well terminate
+ * any on-air transmissions.
+ *
+ * @param publishConfig The {@link PublishConfig} specifying the
+ * configuration of the requested publish session.
+ * @param callback A {@link WifiNanSessionCallback} derived object to be used for session
+ * event callbacks.
+ */
+ public void publish(@NonNull PublishConfig publishConfig,
+ @NonNull WifiNanSessionCallback callback) {
+ if (VDBG) Log.v(TAG, "publish(): config=" + publishConfig);
+
+ int clientId;
+ Looper looper;
+ synchronized (mLock) {
+ if (mLooper == null || mClientId == INVALID_CLIENT_ID) {
+ Log.e(TAG, "publish(): called with null looper or invalid client ID - "
+ + "not connected first?");
+ return;
+ }
+
+ clientId = mClientId;
+ looper = mLooper;
+ }
+ try {
+ mService.publish(clientId, publishConfig,
+ new WifiNanSessionCallbackProxy(this, looper, true, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void updatePublish(int sessionId, PublishConfig publishConfig) {
+ if (VDBG) Log.v(TAG, "updatePublish(): config=" + publishConfig);
+
+ int clientId;
+ synchronized (mLock) {
+ if (mClientId == INVALID_CLIENT_ID) {
+ Log.e(TAG, "updatePublish(): called with invalid client ID - not connected first?");
+ return;
+ }
+
+ clientId = mClientId;
+ }
+ try {
+ mService.updatePublish(clientId, sessionId, publishConfig);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Issue a request to the NAN service to create a new NAN subscribe discovery session, using
+ * the specified {@code subscribeConfig} configuration. The results of the subscribe
+ * operation are routed to the callbacks of {@link WifiNanSessionCallback}:
+ * <ul>
+ * <li>{@link WifiNanSessionCallback#onSubscribeStarted(WifiNanSubscribeSession)} 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 WifiNanSessionCallback#onSessionConfigFail(int)} 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 WifiNanSubscribeSession#updateSubscribe(SubscribeConfig)}.
+ * <p>
+ * An application must use the {@link WifiNanSession#terminate()} to terminate the
+ * subscribe discovery session once it isn't needed. This will free resources as well
+ * terminate any on-air transmissions.
+ *
+ * @param subscribeConfig The {@link SubscribeConfig} specifying the
+ * configuration of the requested subscribe session.
+ * @param callback A {@link WifiNanSessionCallback} derived object to be used for session
+ * event callbacks.
+ */
+ public void subscribe(@NonNull SubscribeConfig subscribeConfig,
+ @NonNull WifiNanSessionCallback callback) {
+ if (VDBG) {
+ Log.v(TAG, "subscribe(): config=" + subscribeConfig);
+ }
+
+ int clientId;
+ Looper looper;
+ synchronized (mLock) {
+ if (mLooper == null || mClientId == INVALID_CLIENT_ID) {
+ Log.e(TAG, "subscribe(): called with null looper or invalid client ID - "
+ + "not connected first?");
+ return;
+ }
+
+ clientId = mClientId;
+ looper = mLooper;
+ }
+
+ try {
+ mService.subscribe(clientId, subscribeConfig,
+ new WifiNanSessionCallbackProxy(this, looper, false, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void updateSubscribe(int sessionId, SubscribeConfig subscribeConfig) {
+ if (VDBG) {
+ Log.v(TAG, "subscribe(): config=" + subscribeConfig);
+ }
+
+ int clientId;
+ synchronized (mLock) {
+ if (mClientId == INVALID_CLIENT_ID) {
+ Log.e(TAG,
+ "updateSubscribe(): called with invalid client ID - not connected first?");
+ return;
+ }
+
+ clientId = mClientId;
+ }
+
+ try {
+ mService.updateSubscribe(clientId, sessionId, subscribeConfig);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void terminateSession(int sessionId) {
+ if (DBG) Log.d(TAG, "Terminate NAN session #" + sessionId);
+
+ int clientId;
+ synchronized (mLock) {
+ if (mClientId == INVALID_CLIENT_ID) {
+ Log.e(TAG,
+ "terminateSession(): called with invalid client ID - not connected first?");
+ return;
+ }
+
+ clientId = mClientId;
+ }
+
+ try {
+ mService.terminateSession(clientId, sessionId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void sendMessage(int sessionId, int peerId, byte[] message, int messageId,
+ int retryCount) {
+ if (VDBG) {
+ Log.v(TAG, "sendMessage(): sessionId=" + sessionId + ", peerId=" + peerId
+ + ", messageId=" + messageId + ", retryCount=" + retryCount);
+ }
+
+ int clientId;
+ synchronized (mLock) {
+ if (mClientId == INVALID_CLIENT_ID) {
+ Log.e(TAG, "sendMessage(): called with invalid client ID - not connected first?");
+ return;
+ }
+
+ clientId = mClientId;
+ }
+
+ try {
+ mService.sendMessage(clientId, sessionId, peerId, message, messageId, retryCount);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void startRanging(int sessionId, RttManager.RttParams[] params,
+ RttManager.RttListener listener) {
+ if (VDBG) {
+ Log.v(TAG, "startRanging: sessionId=" + sessionId + ", " + "params="
+ + Arrays.toString(params) + ", listener=" + listener);
+ }
+
+ int clientId;
+ synchronized (mLock) {
+ if (mClientId == INVALID_CLIENT_ID) {
+ Log.e(TAG, "startRanging(): called with invalid client ID - not connected first?");
+ return;
+ }
+
+ clientId = mClientId;
+ }
+
+ int rangingKey = 0;
+ try {
+ rangingKey = mService.startRanging(clientId, sessionId,
+ new RttManager.ParcelableRttParams(params));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ synchronized (mLock) {
+ mRangingListeners.put(rangingKey, listener);
+ }
+ }
+
+ /** @hide */
+ public String createNetworkSpecifier(@DataPathRole int role, int sessionId, int peerId,
+ byte[] token) {
+ if (VDBG) {
+ Log.v(TAG, "createNetworkSpecifier: role=" + role + ", sessionId=" + sessionId
+ + ", peerId=" + peerId + ", token=" + token);
+ }
+
+ int type;
+ if (token != null && peerId != 0) {
+ type = NETWORK_SPECIFIER_TYPE_1A;
+ } else if (token == null && peerId != 0) {
+ type = NETWORK_SPECIFIER_TYPE_1B;
+ } else if (token != null && peerId == 0) {
+ type = NETWORK_SPECIFIER_TYPE_1C;
+ } else {
+ type = NETWORK_SPECIFIER_TYPE_1D;
+ }
+
+ if (role != WIFI_NAN_DATA_PATH_ROLE_INITIATOR
+ && role != WIFI_NAN_DATA_PATH_ROLE_RESPONDER) {
+ throw new IllegalArgumentException(
+ "createNetworkSpecifier: Invalid 'role' argument when creating a network "
+ + "specifier");
+ }
+ if (role == WIFI_NAN_DATA_PATH_ROLE_INITIATOR) {
+ if (token == null) {
+ throw new IllegalArgumentException(
+ "createNetworkSpecifier: Invalid null token - not permitted on INITIATOR");
+ }
+ if (peerId == 0) {
+ throw new IllegalArgumentException(
+ "createNetworkSpecifier: Invalid peer ID (value of 0) - not permitted on "
+ + "INITIATOR");
+ }
+ }
+
+ int clientId;
+ synchronized (mLock) {
+ if (mClientId == INVALID_CLIENT_ID) {
+ Log.e(TAG,
+ "createNetworkSpecifier: called with invalid client ID - not connected "
+ + "first?");
+ return null;
+ }
+
+ clientId = mClientId;
+ }
+
+ JSONObject json;
+ try {
+ json = new JSONObject();
+ json.put(NETWORK_SPECIFIER_KEY_TYPE, type);
+ json.put(NETWORK_SPECIFIER_KEY_ROLE, role);
+ json.put(NETWORK_SPECIFIER_KEY_CLIENT_ID, clientId);
+ json.put(NETWORK_SPECIFIER_KEY_SESSION_ID, sessionId);
+ if (peerId != 0) {
+ json.put(NETWORK_SPECIFIER_KEY_PEER_ID, peerId);
+ }
+ if (token != null) {
+ json.put(NETWORK_SPECIFIER_KEY_TOKEN,
+ Base64.encodeToString(token, 0, token.length, Base64.DEFAULT));
+ }
+ } catch (JSONException e) {
+ return "";
+ }
+
+ return json.toString();
+ }
+
+ /**
+ * Create a {@link NetworkRequest.Builder#setNetworkSpecifier(String)} for a
+ * WiFi NAN connection to the specified peer. The
+ * {@link NetworkRequest.Builder#addTransportType(int)} should be set to
+ * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_NAN}.
+ * <p>
+ * This API is targeted for applications which can obtain the peer MAC address using OOB
+ * (out-of-band) discovery. NAN discovery does not provide the MAC address of the peer -
+ * when using NAN discovery use the alternative network specifier method -
+ * {@link WifiNanSession#createNetworkSpecifier(int, int, byte[])}.
+ *
+ * @param role The role of this device:
+ * {@link WifiNanManager#WIFI_NAN_DATA_PATH_ROLE_INITIATOR} or
+ * {@link WifiNanManager#WIFI_NAN_DATA_PATH_ROLE_RESPONDER}
+ * @param peer The MAC address of the peer's NAN discovery interface. On a RESPONDER this
+ * value is used to gate the acceptance of a connection request from only that
+ * peer. A RESPONDER may specified a null - indicating that it will accept
+ * connection requests from any device.
+ * @param token An arbitrary token (message) to be used to match connection initiation request
+ * to a responder setup. A RESPONDER is set up with a {@code token} which must
+ * be matched by the token provided by the INITIATOR. A null token is permitted
+ * on the RESPONDER and matches any peer token. An empty ({@code ""}) token is
+ * not the same as a null token and requires the peer token to be empty as well.
+ *
+ * @return A string to be used to construct
+ * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to pass to {@link
+ * android.net.ConnectivityManager#requestNetwork(NetworkRequest, ConnectivityManager.NetworkCallback)}
+ * [or other varieties of that API].
+ */
+ public String createNetworkSpecifier(@DataPathRole int role, @Nullable byte[] peer,
+ @Nullable byte[] token) {
+ if (VDBG) {
+ Log.v(TAG, "createNetworkSpecifier: role=" + role + ", token=" + token);
+ }
+
+ int type;
+ if (token != null && peer != null) {
+ type = NETWORK_SPECIFIER_TYPE_2A;
+ } else if (token == null && peer != null) {
+ type = NETWORK_SPECIFIER_TYPE_2B;
+ } else if (token != null && peer == null) {
+ type = NETWORK_SPECIFIER_TYPE_2C;
+ } else { // both are null
+ type = NETWORK_SPECIFIER_TYPE_2D;
+ }
+
+ if (role != WIFI_NAN_DATA_PATH_ROLE_INITIATOR
+ && role != WIFI_NAN_DATA_PATH_ROLE_RESPONDER) {
+ throw new IllegalArgumentException(
+ "createNetworkSpecifier: Invalid 'role' argument when creating a network "
+ + "specifier");
+ }
+ if (role == WIFI_NAN_DATA_PATH_ROLE_INITIATOR) {
+ if (peer == null || peer.length != 6) {
+ throw new IllegalArgumentException(
+ "createNetworkSpecifier: Invalid peer MAC address");
+ }
+ if (token == null) {
+ throw new IllegalArgumentException(
+ "createNetworkSpecifier: Invalid null token - not permitted on INITIATOR");
+ }
+ } else {
+ if (peer != null && peer.length != 6) {
+ throw new IllegalArgumentException(
+ "createNetworkSpecifier: Invalid peer MAC address");
+ }
+ }
+
+ int clientId;
+ synchronized (mLock) {
+ if (mClientId == INVALID_CLIENT_ID) {
+ Log.e(TAG,
+ "createNetworkSpecifier: called with invalid client ID - not connected "
+ + "first?");
+ return null;
+ }
+
+ clientId = mClientId;
+ }
+
+ JSONObject json;
+ try {
+ json = new JSONObject();
+ json.put(NETWORK_SPECIFIER_KEY_TYPE, type);
+ json.put(NETWORK_SPECIFIER_KEY_ROLE, role);
+ json.put(NETWORK_SPECIFIER_KEY_CLIENT_ID, clientId);
+ if (peer != null) {
+ json.put(NETWORK_SPECIFIER_KEY_PEER_MAC, new String(HexEncoding.encode(peer)));
+ }
+ if (token != null) {
+ json.put(NETWORK_SPECIFIER_KEY_TOKEN,
+ Base64.encodeToString(token, 0, token.length, Base64.DEFAULT));
+ }
+ } catch (JSONException e) {
+ return "";
+ }
+
+ return json.toString();
+ }
+
+ private static class WifiNanEventCallbackProxy extends IWifiNanEventCallback.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 static final int CALLBACK_RANGING_SUCCESS = 3;
+ private static final int CALLBACK_RANGING_FAILURE = 4;
+ private static final int CALLBACK_RANGING_ABORTED = 5;
+
+ private final Handler mHandler;
+ private final WeakReference<WifiNanManager> mNanManager;
+
+ RttManager.RttListener getAndRemoveRangingListener(int rangingId) {
+ WifiNanManager mgr = mNanManager.get();
+ if (mgr == null) {
+ Log.w(TAG, "getAndRemoveRangingListener: called post GC");
+ return null;
+ }
+
+ synchronized (mgr.mLock) {
+ RttManager.RttListener listener = mgr.mRangingListeners.get(rangingId);
+ mgr.mRangingListeners.delete(rangingId);
+ return listener;
+ }
+ }
+
+ /**
+ * Constructs a {@link WifiNanEventCallback} 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.
+ */
+ WifiNanEventCallbackProxy(WifiNanManager mgr, Looper looper,
+ final WifiNanEventCallback originalCallback) {
+ mNanManager = new WeakReference<>(mgr);
+
+ if (VDBG) Log.v(TAG, "WifiNanEventCallbackProxy ctor: looper=" + looper);
+ mHandler = new Handler(looper) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (DBG) {
+ Log.d(TAG, "WifiNanEventCallbackProxy: What=" + msg.what + ", msg=" + msg);
+ }
+
+ WifiNanManager mgr = mNanManager.get();
+ if (mgr == null) {
+ Log.w(TAG, "WifiNanEventCallbackProxy: handleMessage post GC");
+ return;
+ }
+
+ switch (msg.what) {
+ case CALLBACK_CONNECT_SUCCESS:
+ originalCallback.onConnectSuccess();
+ break;
+ case CALLBACK_CONNECT_FAIL:
+ synchronized (mgr.mLock) {
+ mgr.mLooper = null;
+ mgr.mClientId = INVALID_CLIENT_ID;
+ }
+ mNanManager.clear();
+ originalCallback.onConnectFail(msg.arg1);
+ break;
+ case CALLBACK_IDENTITY_CHANGED:
+ originalCallback.onIdentityChanged((byte[]) msg.obj);
+ break;
+ case CALLBACK_RANGING_SUCCESS: {
+ RttManager.RttListener listener = getAndRemoveRangingListener(msg.arg1);
+ if (listener == null) {
+ Log.e(TAG, "CALLBACK_RANGING_SUCCESS rangingId=" + msg.arg1
+ + ": no listener registered (anymore)");
+ } else {
+ listener.onSuccess(
+ ((RttManager.ParcelableRttResults) msg.obj).mResults);
+ }
+ break;
+ }
+ case CALLBACK_RANGING_FAILURE: {
+ RttManager.RttListener listener = getAndRemoveRangingListener(msg.arg1);
+ if (listener == null) {
+ Log.e(TAG, "CALLBACK_RANGING_SUCCESS rangingId=" + msg.arg1
+ + ": no listener registered (anymore)");
+ } else {
+ listener.onFailure(msg.arg2, (String) msg.obj);
+ }
+ break;
+ }
+ case CALLBACK_RANGING_ABORTED: {
+ RttManager.RttListener listener = getAndRemoveRangingListener(msg.arg1);
+ if (listener == null) {
+ Log.e(TAG, "CALLBACK_RANGING_SUCCESS rangingId=" + msg.arg1
+ + ": no listener registered (anymore)");
+ } else {
+ listener.onAborted();
+ }
+ break;
+ }
+ }
+ }
+ };
+ }
+
+ @Override
+ public void onConnectSuccess() {
+ if (VDBG) Log.v(TAG, "onConnectSuccess");
+
+ Message msg = mHandler.obtainMessage(CALLBACK_CONNECT_SUCCESS);
+ mHandler.sendMessage(msg);
+ }
+
+ @Override
+ public void onConnectFail(int reason) {
+ if (VDBG) Log.v(TAG, "onConfigFailed: 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);
+ }
+
+ @Override
+ public void onRangingSuccess(int rangingId, RttManager.ParcelableRttResults results) {
+ if (VDBG) {
+ Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", results=" + results);
+ }
+
+ Message msg = mHandler.obtainMessage(CALLBACK_RANGING_SUCCESS);
+ msg.arg1 = rangingId;
+ msg.obj = results;
+ mHandler.sendMessage(msg);
+ }
+
+ @Override
+ public void onRangingFailure(int rangingId, int reason, String description) {
+ if (VDBG) {
+ Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", reason=" + reason
+ + ", description=" + description);
+ }
+
+ Message msg = mHandler.obtainMessage(CALLBACK_RANGING_FAILURE);
+ msg.arg1 = rangingId;
+ msg.arg2 = reason;
+ msg.obj = description;
+ mHandler.sendMessage(msg);
+
+ }
+
+ @Override
+ public void onRangingAborted(int rangingId) {
+ if (VDBG) Log.v(TAG, "onRangingAborted: rangingId=" + rangingId);
+
+ Message msg = mHandler.obtainMessage(CALLBACK_RANGING_ABORTED);
+ msg.arg1 = rangingId;
+ mHandler.sendMessage(msg);
+
+ }
+ }
+
+ private static class WifiNanSessionCallbackProxy extends IWifiNanSessionCallback.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 String MESSAGE_BUNDLE_KEY_MESSAGE = "message";
+ private static final String MESSAGE_BUNDLE_KEY_MESSAGE2 = "message2";
+
+ private final WeakReference<WifiNanManager> mNanManager;
+ private final boolean mIsPublish;
+ private final WifiNanSessionCallback mOriginalCallback;
+
+ private final Handler mHandler;
+ private WifiNanSession mSession;
+
+ WifiNanSessionCallbackProxy(WifiNanManager mgr, Looper looper, boolean isPublish,
+ WifiNanSessionCallback originalCallback) {
+ mNanManager = new WeakReference<>(mgr);
+ mIsPublish = isPublish;
+ mOriginalCallback = originalCallback;
+
+ if (VDBG) Log.v(TAG, "WifiNanSessionCallbackProxy ctor: isPublish=" + isPublish);
+
+ mHandler = new Handler(looper) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (DBG) Log.d(TAG, "What=" + msg.what + ", msg=" + msg);
+
+ if (mNanManager.get() == null) {
+ Log.w(TAG, "WifiNanSessionCallbackProxy: handleMessage post GC");
+ return;
+ }
+
+ switch (msg.what) {
+ case CALLBACK_SESSION_STARTED:
+ onProxySessionStarted(msg.arg1);
+ break;
+ case CALLBACK_SESSION_CONFIG_SUCCESS:
+ mOriginalCallback.onSessionConfigSuccess();
+ break;
+ case CALLBACK_SESSION_CONFIG_FAIL:
+ mOriginalCallback.onSessionConfigFail(msg.arg1);
+ if (mSession == null) {
+ /*
+ * creation failed (as opposed to update
+ * failing)
+ */
+ mNanManager.clear();
+ }
+ break;
+ case CALLBACK_SESSION_TERMINATED:
+ onProxySessionTerminated(msg.arg1);
+ break;
+ case CALLBACK_MATCH:
+ mOriginalCallback.onMatch(
+ msg.arg1,
+ msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE),
+ msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE2));
+ break;
+ case CALLBACK_MESSAGE_SEND_SUCCESS:
+ mOriginalCallback.onMessageSendSuccess(msg.arg1);
+ break;
+ case CALLBACK_MESSAGE_SEND_FAIL:
+ mOriginalCallback.onMessageSendFail(msg.arg1, msg.arg2);
+ break;
+ case CALLBACK_MESSAGE_RECEIVED:
+ mOriginalCallback.onMessageReceived(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);
+ }
+
+ @Override
+ public void onMatch(int peerId, byte[] serviceSpecificInfo, byte[] matchFilter) {
+ if (VDBG) Log.v(TAG, "onMatch: peerId=" + peerId);
+
+ Bundle data = new Bundle();
+ data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE, serviceSpecificInfo);
+ data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE2, matchFilter);
+
+ Message msg = mHandler.obtainMessage(CALLBACK_MATCH);
+ msg.arg1 = peerId;
+ msg.setData(data);
+ mHandler.sendMessage(msg);
+ }
+
+ @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!?");
+ }
+
+ WifiNanManager mgr = mNanManager.get();
+ if (mgr == null) {
+ Log.w(TAG, "onProxySessionStarted: mgr GC'd");
+ return;
+ }
+
+ if (mIsPublish) {
+ WifiNanPublishSession session = new WifiNanPublishSession(mgr, sessionId);
+ mSession = session;
+ mOriginalCallback.onPublishStarted(session);
+ } else {
+ WifiNanSubscribeSession session = new WifiNanSubscribeSession(mgr, 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!?");
+ }
+ mNanManager.clear();
+ mOriginalCallback.onSessionTerminated(reason);
+ }
+ }
}
diff --git a/wifi/java/android/net/wifi/nan/WifiNanPublishSession.java b/wifi/java/android/net/wifi/nan/WifiNanPublishSession.java
index 81b38f4..ccd7fae 100644
--- a/wifi/java/android/net/wifi/nan/WifiNanPublishSession.java
+++ b/wifi/java/android/net/wifi/nan/WifiNanPublishSession.java
@@ -16,32 +16,54 @@
package android.net.wifi.nan;
+import android.annotation.NonNull;
+import android.util.Log;
+
/**
- * A representation of a NAN publish session. Created when
- * {@link WifiNanManager#publish(PublishData, PublishSettings, WifiNanSessionListener, int)}
- * is executed. The object can be used to stop and re-start (re-configure) the
- * publish session.
+ * A class representing a NAN publish session. Created when
+ * {@link WifiNanManager#publish(PublishConfig, WifiNanSessionCallback)} is called and a
+ * discovery session is created and returned in
+ * {@link WifiNanSessionCallback#onPublishStarted(WifiNanPublishSession)}. See baseline
+ * functionality of all discovery sessions in {@link WifiNanSession}. This object allows updating
+ * an existing/running publish discovery session using {@link #updatePublish(PublishConfig)}.
*
* @hide PROPOSED_NAN_API
*/
public class WifiNanPublishSession extends WifiNanSession {
- /**
- * {@hide}
- */
+ private static final String TAG = "WifiNanPublishSession";
+
+ /** @hide */
public WifiNanPublishSession(WifiNanManager manager, int sessionId) {
super(manager, sessionId);
}
/**
- * Restart/re-configure the publish session. Note that the
- * {@link WifiNanSessionListener} is not replaced - the same listener used at
- * creation is still used.
+ * Re-configure the currently active publish session. The
+ * {@link WifiNanSessionCallback} is not replaced - the same listener used
+ * at creation is still used. The results of the configuration are returned using
+ * {@link WifiNanSessionCallback}:
+ * <ul>
+ * <li>{@link WifiNanSessionCallback#onSessionConfigSuccess()}: configuration update
+ * succeeded.
+ * <li>{@link WifiNanSessionCallback#onSessionConfigFail(int)}: 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 publishData The data ({@link PublishData}) to publish.
- * @param publishSettings The settings ({@link PublishSettings}) of the
- * publish session.
+ * @param publishConfig The new discovery publish session configuration ({@link PublishConfig}).
*/
- public void publish(PublishData publishData, PublishSettings publishSettings) {
- mManager.publish(mSessionId, publishData, publishSettings);
+ public void updatePublish(@NonNull PublishConfig publishConfig) {
+ if (mTerminated) {
+ Log.w(TAG, "updatePublish: called on terminated session");
+ return;
+ } else {
+ WifiNanManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.w(TAG, "updatePublish: called post GC on WifiNanManager");
+ return;
+ }
+
+ mgr.updatePublish(mSessionId, publishConfig);
+ }
}
}
diff --git a/wifi/java/android/net/wifi/nan/WifiNanSession.java b/wifi/java/android/net/wifi/nan/WifiNanSession.java
index bc1787f..005ca29 100644
--- a/wifi/java/android/net/wifi/nan/WifiNanSession.java
+++ b/wifi/java/android/net/wifi/nan/WifiNanSession.java
@@ -16,12 +16,28 @@
package android.net.wifi.nan;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.net.wifi.RttManager;
import android.util.Log;
+import dalvik.system.CloseGuard;
+
+import java.lang.ref.WeakReference;
+
/**
- * A representation of a single publish or subscribe NAN session. This object
+ * A class representing a single publish or subscribe NAN session. This object
* will not be created directly - only its child classes are available:
- * {@link WifiNanPublishSession} and {@link WifiNanSubscribeSession}.
+ * {@link WifiNanPublishSession} and {@link WifiNanSubscribeSession}. This class provides
+ * functionality common to both publish and subscribe discovery sessions:
+ * <ul>
+ * <li>Sending messages: {@link #sendMessage(int, byte[], int)} or
+ * {@link #sendMessage(int, byte[], int, int)} methods.
+ * <li>Creating a network-specifier when requesting a NAN connection:
+ * {@link #createNetworkSpecifier(int, int, byte[])}.
+ * </ul>
+ * The {@link #terminate()} method must be called to terminate discovery sessions once they are
+ * no longer needed.
*
* @hide PROPOSED_NAN_API
*/
@@ -30,84 +46,234 @@
private static final boolean DBG = false;
private static final boolean VDBG = false; // STOPSHIP if true
- /**
- * {@hide}
- */
- protected WifiNanManager mManager;
+ private static final int MAX_SEND_RETRY_COUNT = 5;
+
+ /** @hide */
+ protected WeakReference<WifiNanManager> mMgr;
+ /** @hide */
+ protected final int mSessionId;
+ /** @hide */
+ protected boolean mTerminated = false;
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
/**
- * {@hide}
+ * Return the maximum permitted retry count when sending messages using
+ * {@link #sendMessage(int, byte[], int, int)}.
+ *
+ * @return Maximum retry count when sending messages.
*/
- protected int mSessionId;
+ public static int getMaxSendRetryCount() {
+ return MAX_SEND_RETRY_COUNT;
+ }
- /**
- * {@hide}
- */
- private boolean mDestroyed;
-
- /**
- * {@hide}
- */
+ /** @hide */
public WifiNanSession(WifiNanManager manager, int sessionId) {
if (VDBG) Log.v(TAG, "New client created: manager=" + manager + ", sessionId=" + sessionId);
- mManager = manager;
+ mMgr = new WeakReference<>(manager);
mSessionId = sessionId;
- mDestroyed = false;
+
+ mCloseGuard.open("terminate");
}
/**
- * Terminate the current publish or subscribe session - i.e. stop
- * transmitting packet on-air (for an active session) or listening for
- * matches (for a passive session). Note that the session may still receive
- * incoming messages and may be re-configured/re-started at a later time.
+ * Terminate 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 termination.
+ * <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 terminates. The only
+ * exception is a session for which we received a termination callback,
+ * {@link WifiNanSessionCallback#onSessionTerminated(int)}.
*/
- public void stop() {
- mManager.stopSession(mSessionId);
+ public void terminate() {
+ WifiNanManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.w(TAG, "terminate: called post GC on WifiNanManager");
+ return;
+ }
+ mgr.terminateSession(mSessionId);
+ mTerminated = true;
+ mMgr.clear();
+ mCloseGuard.close();
}
/**
- * Destroy the current publish or subscribe session. Performs a
- * {@link WifiNanSession#stop()} function but in addition destroys the session -
- * it will not be able to receive any messages or to be restarted at a later
- * time.
+ * Sets the status of the session to terminated - i.e. an indication that
+ * already terminated rather than executing a termination.
+ *
+ * @hide
*/
- public void destroy() {
- mManager.destroySession(mSessionId);
- mDestroyed = true;
+ public void setTerminated() {
+ if (mTerminated) {
+ Log.w(TAG, "terminate: already terminated.");
+ return;
+ }
+ mTerminated = true;
+ mMgr.clear();
+ mCloseGuard.close();
}
- /**
- * {@hide}
- */
+ /** @hide */
@Override
protected void finalize() throws Throwable {
- if (!mDestroyed) {
- Log.w(TAG, "WifiNanSession mSessionId=" + mSessionId
- + " was not explicitly destroyed. The session may use resources until "
- + "destroyed so step should be done explicitly");
+ try {
+ if (!mTerminated) {
+ mCloseGuard.warnIfOpen();
+ terminate();
+ }
+ } finally {
+ super.finalize();
}
- destroy();
}
/**
- * Sends a message to the specified destination. Message transmission is
- * part of the current discovery session - i.e. executed subsequent to a
- * publish/subscribe
- * {@link WifiNanSessionListener#onMatch(int, byte[], int, byte[], int)}
- * event.
+ * Sends a message to the specified destination. NAN messages are transmitted in the context
+ * of a discovery session - executed subsequent to a publish/subscribe
+ * {@link WifiNanSessionCallback#onMatch(int, byte[], byte[])} event.
+ * <p>
+ * NAN messages are not guaranteed delivery. Callbacks on {@link WifiNanSessionCallback}
+ * indicate message was transmitted successfully,
+ * {@link WifiNanSessionCallback#onMessageSendSuccess(int)}, or transmission failed
+ * (possibly after several retries) -
+ * {@link WifiNanSessionCallback#onMessageSendFail(int, int)}.
+ * <p>
+ * The peer will get a callback indicating a message was received using
+ * {@link WifiNanSessionCallback#onMessageReceived(int, byte[])}.
*
* @param peerId The peer's ID for the message. Must be a result of an
- * {@link WifiNanSessionListener#onMatch(int, byte[], int, byte[], int)}
- * event.
+ * {@link WifiNanSessionCallback#onMatch(int, byte[], byte[])} or
+ * {@link WifiNanSessionCallback#onMessageReceived(int, byte[])} events.
* @param message The message to be transmitted.
- * @param messageLength The number of bytes from the {@code message} to be
- * transmitted.
- * @param messageId An arbitrary integer used by the caller to identify the
- * message. The same integer ID will be returned in the callbacks
- * indicated message send success or failure.
+ * @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 NAN service - it
+ * can be arbitrary and non-unique.
+ * @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()}.
*/
- public void sendMessage(int peerId, byte[] message, int messageLength, int messageId) {
- mManager.sendMessage(mSessionId, peerId, message, messageLength, messageId);
+ public void sendMessage(int peerId, @Nullable byte[] message, int messageId, int retryCount) {
+ if (mTerminated) {
+ Log.w(TAG, "sendMessage: called on terminated session");
+ return;
+ } else {
+ WifiNanManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.w(TAG, "sendMessage: called post GC on WifiNanManager");
+ return;
+ }
+
+ mgr.sendMessage(mSessionId, peerId, message, messageId, retryCount);
+ }
+ }
+
+ /**
+ * Sends a message to the specified destination. NAN messages are transmitted in the context
+ * of a discovery session - executed subsequent to a publish/subscribe
+ * {@link WifiNanSessionCallback#onMatch(int, byte[], byte[])} event.
+ * <p>
+ * NAN messages are not guaranteed delivery. Callbacks on {@link WifiNanSessionCallback}
+ * indicate message was transmitted successfully,
+ * {@link WifiNanSessionCallback#onMessageSendSuccess(int)}, or transmission failed
+ * (possibly after several retries) -
+ * {@link WifiNanSessionCallback#onMessageSendFail(int, int)}.
+ * <p>
+ * The peer will get a callback indicating a message was received using
+ * {@link WifiNanSessionCallback#onMessageReceived(int, byte[])}.
+ * Equivalent to {@link #sendMessage(int, byte[], int, int)} with a {@code retryCount} of
+ * 0.
+ *
+ * @param peerId The peer's ID for the message. Must be a result of an
+ * {@link WifiNanSessionCallback#onMatch(int, byte[], byte[])} or
+ * {@link WifiNanSessionCallback#onMessageReceived(int, byte[])} events.
+ * @param message The message to be transmitted.
+ * @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 NAN service - it
+ * can be arbitrary and non-unique.
+ */
+ public void sendMessage(int peerId, @Nullable byte[] message, int messageId) {
+ sendMessage(peerId, message, messageId, 0);
+ }
+
+ /**
+ * Start a ranging operation with the specified peers. The peer IDs are obtained from an
+ * {@link WifiNanSessionCallback#onMatch(int, byte[], byte[])} or
+ * {@link WifiNanSessionCallback#onMessageReceived(int, byte[])} operation - can only
+ * range devices which are part of an ongoing discovery session.
+ *
+ * @param params RTT parameters - each corresponding to a specific peer ID (the array sizes
+ * must be identical). The
+ * {@link android.net.wifi.RttManager.RttParams#bssid} member must be set to
+ * a peer ID - not to a MAC address.
+ * @param listener The listener to receive the results of the ranging session.
+ * @hide PROPOSED_NAN_SYSTEM_API [TODO: b/28847998 - track RTT API & visilibity]
+ */
+ public void startRanging(RttManager.RttParams[] params, RttManager.RttListener listener) {
+ if (mTerminated) {
+ Log.w(TAG, "startRanging: called on terminated session");
+ return;
+ } else {
+ WifiNanManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.w(TAG, "startRanging: called post GC on WifiNanManager");
+ return;
+ }
+
+ mgr.startRanging(mSessionId, params, listener);
+ }
+ }
+
+ /**
+ * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} for a
+ * WiFi NAN connection to the specified peer. The
+ * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
+ * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_NAN}.
+ * <p>
+ * This method should be used when setting up a connection with a peer discovered through NAN
+ * discovery or communication (in such scenarios the MAC address of the peer is shielded by
+ * an opaque peer ID handle). If a NAN connection is needed to a peer discovered using other
+ * OOB (out-of-band) mechanism then use the alternative
+ * {@link WifiNanManager#createNetworkSpecifier(int, byte[], byte[])} method - which uses the
+ * peer's MAC address.
+ *
+ * @param role The role of this device:
+ * {@link WifiNanManager#WIFI_NAN_DATA_PATH_ROLE_INITIATOR} or
+ * {@link WifiNanManager#WIFI_NAN_DATA_PATH_ROLE_RESPONDER}
+ * @param peerId The peer ID obtained through
+ * {@link WifiNanSessionCallback#onMatch(int, byte[], byte[])} or
+ * {@link WifiNanSessionCallback#onMessageReceived(int, byte[])}. On a RESPONDER this
+ * value is used to gate the acceptance of a connection request from only that
+ * peer. A RESPONDER may specified a 0 - indicating that it will accept
+ * connection requests from any device.
+ * @param token An arbitrary token (message) to be used to match connection initiation request
+ * to a responder setup. A RESPONDER is set up with a {@code token} which must
+ * be matched by the token provided by the INITIATOR. A null token is permitted
+ * on the RESPONDER and matches any peer token. An empty ({@code ""}) token is
+ * not the same as a null token and requires the peer token to be empty as well.
+ *
+ * @return A string to be used to construct
+ * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to pass to
+ * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,android.net.ConnectivityManager.NetworkCallback)}
+ * [or other varieties of that API].
+ */
+ public String createNetworkSpecifier(@WifiNanManager.DataPathRole int role, int peerId,
+ @Nullable byte[] token) {
+ if (mTerminated) {
+ Log.w(TAG, "createNetworkSpecifier: called on terminated session");
+ return null;
+ } else {
+ WifiNanManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.w(TAG, "createNetworkSpecifier: called post GC on WifiNanManager");
+ return null;
+ }
+
+ return mgr.createNetworkSpecifier(role, mSessionId, peerId, token);
+ }
}
}
diff --git a/wifi/java/android/net/wifi/nan/WifiNanSessionCallback.java b/wifi/java/android/net/wifi/nan/WifiNanSessionCallback.java
new file mode 100644
index 0000000..8433b99
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/WifiNanSessionCallback.java
@@ -0,0 +1,223 @@
+/*
+ * 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.nan;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Base class for NAN session events callbacks. Should be extended by
+ * applications wanting notifications. The callbacks are set when a
+ * publish or subscribe session is created using
+ * {@link WifiNanManager#publish(PublishConfig, WifiNanSessionCallback)} or
+ * {@link WifiNanManager#subscribe(SubscribeConfig, WifiNanSessionCallback)} .
+ * <p>
+ * A single callback is set at session creation - it cannot be replaced.
+ *
+ * @hide PROPOSED_NAN_API
+ */
+public class WifiNanSessionCallback {
+ /** @hide */
+ @IntDef({
+ REASON_NO_RESOURCES, REASON_INVALID_ARGS, REASON_NO_MATCH_SESSION,
+ REASON_TX_FAIL, REASON_OTHER })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SessionReasonCodes {
+ }
+
+ /** @hide */
+ @IntDef({
+ TERMINATE_REASON_DONE, TERMINATE_REASON_FAIL })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SessionTerminateCodes {
+ }
+
+ /**
+ * Indicates no resources to execute the requested operation.
+ * Failure reason flag for {@link WifiNanSessionCallback} callbacks.
+ */
+ public static final int REASON_NO_RESOURCES = 0;
+
+ /**
+ * Indicates invalid argument in the requested operation.
+ * Failure reason flag for {@link WifiNanSessionCallback} callbacks.
+ */
+ public static final int REASON_INVALID_ARGS = 1;
+
+ /**
+ * Indicates a message is transmitted without a match (a discovery) or received message
+ * from peer occurring first.
+ * Failure reason flag for {@link WifiNanSessionCallback} callbacks.
+ */
+ public static final int REASON_NO_MATCH_SESSION = 2;
+
+ /**
+ * Indicates transmission failure: this may be due to local transmission
+ * failure or to no ACK received - remote device didn't receive the
+ * sent message. Failure reason flag for
+ * {@link WifiNanSessionCallback#onMessageSendFail(int, int)} callback.
+ */
+ public static final int REASON_TX_FAIL = 3;
+
+ /**
+ * Indicates an unspecified error occurred during the operation.
+ * Failure reason flag for {@link WifiNanSessionCallback} callbacks.
+ */
+ public static final int REASON_OTHER = 4;
+
+ /**
+ * Indicates that publish or subscribe session is done - all the
+ * requested operations (per {@link PublishConfig} or
+ * {@link SubscribeConfig}) have been executed. Failure reason flag for
+ * {@link WifiNanSessionCallback#onSessionTerminated(int)} callback.
+ */
+ public static final int TERMINATE_REASON_DONE = 100;
+
+ /**
+ * Indicates that publish or subscribe session is terminated due to a
+ * failure.
+ * Failure reason flag for
+ * {@link WifiNanSessionCallback#onSessionTerminated(int)} callback.
+ */
+ public static final int TERMINATE_REASON_FAIL = 101;
+
+ /**
+ * Called when a publish operation is started successfully in response to a
+ * {@link WifiNanManager#publish(PublishConfig, WifiNanSessionCallback)} operation.
+ *
+ * @param session The {@link WifiNanPublishSession} used to control the
+ * discovery session.
+ */
+ public void onPublishStarted(@NonNull WifiNanPublishSession session) {
+ /* empty */
+ }
+
+ /**
+ * Called when a subscribe operation is started successfully in response to a
+ * {@link WifiNanManager#subscribe(SubscribeConfig, WifiNanSessionCallback)} operation.
+ *
+ * @param session The {@link WifiNanSubscribeSession} used to control the
+ * discovery session.
+ */
+ public void onSubscribeStarted(@NonNull WifiNanSubscribeSession session) {
+ /* empty */
+ }
+
+ /**
+ * Called when a publish or subscribe discovery session configuration is update request
+ * succeeds. Called in response to {@link WifiNanPublishSession#updatePublish(PublishConfig)}
+ * or {@link WifiNanSubscribeSession#updateSubscribe(SubscribeConfig)}.
+ */
+ public void onSessionConfigSuccess() {
+ /* empty */
+ }
+
+ /**
+ * Called when a publish or subscribe discovery session cannot be created:
+ * {@link WifiNanManager#publish(PublishConfig, WifiNanSessionCallback)} or
+ * {@link WifiNanManager#subscribe(SubscribeConfig, WifiNanSessionCallback)},
+ * or when a configuration update fails:
+ * {@link WifiNanPublishSession#updatePublish(PublishConfig)} or
+ * {@link WifiNanSubscribeSession#updateSubscribe(SubscribeConfig)}.
+ * <p>
+ * For discovery session updates failure leaves the session running with its previous
+ * configuration - the discovery session is not terminated.
+ *
+ * @param reason The failure reason using
+ * {@code WifiNanSessionCallback.REASON_*} codes.
+ */
+ public void onSessionConfigFail(@SessionReasonCodes int reason) {
+ /* empty */
+ }
+
+ /**
+ * Called when a discovery session (publish or subscribe) terminates. Termination may be due
+ * to user-request (either directly through {@link WifiNanSession#terminate()} or
+ * application-specified expiration, e.g. {@link PublishConfig.Builder#setPublishCount(int)}
+ * or {@link SubscribeConfig.Builder#setTtlSec(int)}) or due to a failure.
+ *
+ * @param reason The termination reason using
+ * {@code WifiNanSessionCallback.TERMINATE_*} codes.
+ */
+ public void onSessionTerminated(@SessionTerminateCodes int reason) {
+ /* empty */
+ }
+
+ /**
+ * Called when a discovery (publish or subscribe) operation results in a
+ * match - when a peer is discovered.
+ *
+ * @param peerId The ID of 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 (Tx on advertiser and Rx on listener) which
+ * resulted in this match.
+ */
+ public void onMatch(int peerId, byte[] serviceSpecificInfo, byte[] matchFilter) {
+ /* empty */
+ }
+
+ /**
+ * Called in response to {@link WifiNanSession#sendMessage(int, byte[], int)} when a message
+ * is transmitted successfully - when it was received successfully by the peer
+ * (corresponds to an ACK being received).
+ * <p>
+ * Note that either this callback or
+ * {@link WifiNanSessionCallback#onMessageSendFail(int, int)} will be
+ * received - never both.
+ *
+ * @param messageId The arbitrary message ID specified when sending the message.
+ */
+ public void onMessageSendSuccess(@SuppressWarnings("unused") int messageId) {
+ /* empty */
+ }
+
+ /**
+ * Called when message transmission fails - when no ACK is received from the peer.
+ * Retries when ACKs are not received are done by hardware, MAC, and in the NAN stack (using
+ * the {@link WifiNanSession#sendMessage(int, byte[], int, int)} method) - this
+ * event is received after all retries are exhausted.
+ * <p>
+ * Note that either this callback or
+ * {@link WifiNanSessionCallback#onMessageSendSuccess(int)} will be received
+ * - never both.
+ *
+ * @param messageId The arbitrary message ID specified when sending the message.
+ * @param reason The failure reason using
+ * {@code WifiNanSessionCallback.REASON_*} codes.
+ */
+ public void onMessageSendFail(@SuppressWarnings("unused") int messageId,
+ @SessionReasonCodes int reason) {
+ /* empty */
+ }
+
+ /**
+ * Called when a message is received from a discovery session peer - in response to the
+ * peer's {@link WifiNanSession#sendMessage(int, byte[], int)} or
+ * {@link WifiNanSession#sendMessage(int, byte[], int, int)}.
+ *
+ * @param peerId The ID of the peer sending the message.
+ * @param message A byte array containing the message.
+ */
+ public void onMessageReceived(int peerId, byte[] message) {
+ /* empty */
+ }
+}
diff --git a/wifi/java/android/net/wifi/nan/WifiNanSessionListener.java b/wifi/java/android/net/wifi/nan/WifiNanSessionListener.java
deleted file mode 100644
index b9af7def..0000000
--- a/wifi/java/android/net/wifi/nan/WifiNanSessionListener.java
+++ /dev/null
@@ -1,439 +0,0 @@
-/*
- * 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.nan;
-
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
-
-/**
- * Base class for NAN session events callbacks. Should be extended by
- * applications wanting notifications. The callbacks are registered when a
- * publish or subscribe session is created using
- * {@link WifiNanManager#publish(PublishData, PublishSettings, WifiNanSessionListener, int)}
- * or
- * {@link WifiNanManager#subscribe(SubscribeData, SubscribeSettings, WifiNanSessionListener, int)}
- * . These are callbacks applying to a specific NAN session. Events
- * corresponding to the NAN link are delivered using {@link WifiNanEventListener}.
- * <p>
- * A single listener is registered at session creation - it cannot be replaced.
- * <p>
- * During registration specify which specific events are desired using a set of
- * {@code NanSessionListener.LISTEN_*} flags OR'd together. Only those events
- * will be delivered to the registered listener. Override those callbacks
- * {@code NanSessionListener.on*} for the registered events.
- *
- * @hide PROPOSED_NAN_API
- */
-public class WifiNanSessionListener {
- private static final String TAG = "WifiNanSessionListener";
- private static final boolean DBG = false;
- private static final boolean VDBG = false; // STOPSHIP if true
-
- /**
- * Publish fail callback event registration flag. Corresponding callback is
- * {@link WifiNanSessionListener#onPublishFail(int)}.
- *
- * @hide
- */
- public static final int LISTEN_PUBLISH_FAIL = 0x1 << 0;
-
- /**
- * Publish terminated callback event registration flag. Corresponding
- * callback is {@link WifiNanSessionListener#onPublishTerminated(int)}.
- */
- public static final int LISTEN_PUBLISH_TERMINATED = 0x1 << 1;
-
- /**
- * Subscribe fail callback event registration flag. Corresponding callback
- * is {@link WifiNanSessionListener#onSubscribeFail(int)}.
- *
- * @hide
- */
- public static final int LISTEN_SUBSCRIBE_FAIL = 0x1 << 2;
-
- /**
- * Subscribe terminated callback event registration flag. Corresponding
- * callback is {@link WifiNanSessionListener#onSubscribeTerminated(int)}.
- */
- public static final int LISTEN_SUBSCRIBE_TERMINATED = 0x1 << 3;
-
- /**
- * Match (discovery: publish or subscribe) callback event registration flag.
- * Corresponding callback is
- * {@link WifiNanSessionListener#onMatch(int, byte[], int, byte[], int)}.
- *
- * @hide
- */
- public static final int LISTEN_MATCH = 0x1 << 4;
-
- /**
- * Message sent successfully callback event registration flag. Corresponding
- * callback is {@link WifiNanSessionListener#onMessageSendSuccess()}.
- *
- * @hide
- */
- public static final int LISTEN_MESSAGE_SEND_SUCCESS = 0x1 << 5;
-
- /**
- * Message sending failure callback event registration flag. Corresponding
- * callback is {@link WifiNanSessionListener#onMessageSendFail(int)}.
- *
- * @hide
- */
- public static final int LISTEN_MESSAGE_SEND_FAIL = 0x1 << 6;
-
- /**
- * Message received callback event registration flag. Corresponding callback
- * is {@link WifiNanSessionListener#onMessageReceived(int, byte[], int)}.
- *
- * @hide
- */
- public static final int LISTEN_MESSAGE_RECEIVED = 0x1 << 7;
-
- /**
- * List of hidden events: which are mandatory - i.e. they will be added to
- * every request.
- *
- * @hide
- */
- public static final int LISTEN_HIDDEN_FLAGS = LISTEN_PUBLISH_FAIL | LISTEN_SUBSCRIBE_FAIL
- | LISTEN_MATCH | LISTEN_MESSAGE_SEND_SUCCESS | LISTEN_MESSAGE_SEND_FAIL
- | LISTEN_MESSAGE_RECEIVED;
-
- /**
- * Failure reason flag for {@link WifiNanEventListener} and
- * {@link WifiNanSessionListener} callbacks. Indicates no resources to execute
- * the requested operation.
- */
- public static final int FAIL_REASON_NO_RESOURCES = 0;
-
- /**
- * Failure reason flag for {@link WifiNanEventListener} and
- * {@link WifiNanSessionListener} callbacks. Indicates invalid argument in the
- * requested operation.
- */
- public static final int FAIL_REASON_INVALID_ARGS = 1;
-
- /**
- * Failure reason flag for {@link WifiNanEventListener} and
- * {@link WifiNanSessionListener} callbacks. Indicates a message is transmitted
- * without a match (i.e. a discovery) occurring first.
- */
- public static final int FAIL_REASON_NO_MATCH_SESSION = 2;
-
- /**
- * Failure reason flag for {@link WifiNanEventListener} and
- * {@link WifiNanSessionListener} callbacks. Indicates an unspecified error
- * occurred during the operation.
- */
- public static final int FAIL_REASON_OTHER = 3;
-
- /**
- * Failure reason flag for
- * {@link WifiNanSessionListener#onPublishTerminated(int)} and
- * {@link WifiNanSessionListener#onSubscribeTerminated(int)} callbacks.
- * Indicates that publish or subscribe session is done - i.e. all the
- * requested operations (per {@link PublishSettings} or
- * {@link SubscribeSettings}) have been executed.
- */
- public static final int TERMINATE_REASON_DONE = 0;
-
- /**
- * Failure reason flag for
- * {@link WifiNanSessionListener#onPublishTerminated(int)} and
- * {@link WifiNanSessionListener#onSubscribeTerminated(int)} callbacks.
- * Indicates that publish or subscribe session is terminated due to a
- * failure.
- */
- public static final int TERMINATE_REASON_FAIL = 1;
-
- private static final String MESSAGE_BUNDLE_KEY_PEER_ID = "peer_id";
- private static final String MESSAGE_BUNDLE_KEY_MESSAGE = "message";
- private static final String MESSAGE_BUNDLE_KEY_MESSAGE2 = "message2";
-
- private final Handler mHandler;
-
- /**
- * Constructs a {@link WifiNanSessionListener} using the looper of the current
- * thread. I.e. all callbacks will be delivered on the current thread.
- */
- public WifiNanSessionListener() {
- this(Looper.myLooper());
- }
-
- /**
- * Constructs a {@link WifiNanSessionListener} using the specified looper. I.e.
- * all callbacks will delivered on the thread of the specified looper.
- *
- * @param looper The looper on which to execute the callbacks.
- */
- public WifiNanSessionListener(Looper looper) {
- if (VDBG) Log.v(TAG, "ctor: looper=" + looper);
- mHandler = new Handler(looper) {
- @Override
- public void handleMessage(Message msg) {
- if (DBG) Log.d(TAG, "What=" + msg.what + ", msg=" + msg);
- switch (msg.what) {
- case LISTEN_PUBLISH_FAIL:
- WifiNanSessionListener.this.onPublishFail(msg.arg1);
- break;
- case LISTEN_PUBLISH_TERMINATED:
- WifiNanSessionListener.this.onPublishTerminated(msg.arg1);
- break;
- case LISTEN_SUBSCRIBE_FAIL:
- WifiNanSessionListener.this.onSubscribeFail(msg.arg1);
- break;
- case LISTEN_SUBSCRIBE_TERMINATED:
- WifiNanSessionListener.this.onSubscribeTerminated(msg.arg1);
- break;
- case LISTEN_MATCH:
- WifiNanSessionListener.this.onMatch(
- msg.getData().getInt(MESSAGE_BUNDLE_KEY_PEER_ID),
- msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE), msg.arg1,
- msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE2), msg.arg2);
- break;
- case LISTEN_MESSAGE_SEND_SUCCESS:
- WifiNanSessionListener.this.onMessageSendSuccess(msg.arg1);
- break;
- case LISTEN_MESSAGE_SEND_FAIL:
- WifiNanSessionListener.this.onMessageSendFail(msg.arg1, msg.arg2);
- break;
- case LISTEN_MESSAGE_RECEIVED:
- WifiNanSessionListener.this.onMessageReceived(msg.arg2,
- msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE), msg.arg1);
- break;
- }
- }
- };
- }
-
- /**
- * Called when a publish operation fails. It is dummy method (empty
- * implementation printing out a log message). Override to implement your
- * custom response.
- *
- * @param reason The failure reason using {@code NanSessionListener.FAIL_*}
- * codes.
- */
- public void onPublishFail(int reason) {
- if (VDBG) Log.v(TAG, "onPublishFail: called in stub - override if interested");
- }
-
- /**
- * Called when a publish operation terminates. Event will only be delivered
- * if registered using {@link WifiNanSessionListener#LISTEN_PUBLISH_TERMINATED}.
- * A dummy (empty implementation printing out a warning). Make sure to
- * override if registered.
- *
- * @param reason The termination reason using
- * {@code NanSessionListener.TERMINATE_*} codes.
- */
- public void onPublishTerminated(int reason) {
- Log.w(TAG, "onPublishTerminated: called in stub - override if interested or disable");
- }
-
- /**
- * Called when a subscribe operation fails. It is dummy method (empty
- * implementation printing out a log message). Override to implement your
- * custom response.
- *
- * @param reason The failure reason using {@code NanSessionListener.FAIL_*}
- * codes.
- */
- public void onSubscribeFail(int reason) {
- if (VDBG) Log.v(TAG, "onSubscribeFail: called in stub - override if interested");
- }
-
- /**
- * Called when a subscribe operation terminates. Event will only be
- * delivered if registered using
- * {@link WifiNanSessionListener#LISTEN_SUBSCRIBE_TERMINATED}. A dummy (empty
- * implementation printing out a warning). Make sure to override if
- * registered.
- *
- * @param reason The termination reason using
- * {@code NanSessionListener.TERMINATE_*} codes.
- */
- public void onSubscribeTerminated(int reason) {
- Log.w(TAG, "onSubscribeTerminated: called in stub - override if interested or disable");
- }
-
- /**
- * Called when a discovery (publish or subscribe) operation results in a
- * match - i.e. when a peer is discovered. It is dummy method (empty
- * implementation printing out a log message). Override to implement your
- * custom response.
- *
- * @param peerId The ID of the peer matching our discovery operation.
- * @param serviceSpecificInfo The service specific information (arbitrary
- * byte array) provided by the peer as part of its discovery
- * packet.
- * @param serviceSpecificInfoLength The length of the service specific
- * information array.
- * @param matchFilter The filter (Tx on advertiser and Rx on listener) which
- * resulted in this match.
- * @param matchFilterLength The length of the match filter array.
- */
- public void onMatch(int peerId, byte[] serviceSpecificInfo,
- int serviceSpecificInfoLength, byte[] matchFilter, int matchFilterLength) {
- if (VDBG) Log.v(TAG, "onMatch: called in stub - override if interested");
- }
-
- /**
- * Called when a message is transmitted successfully - i.e. when we know
- * that it was received successfully (corresponding to an ACK being
- * received). It is dummy method (empty implementation printing out a log
- * message). Override to implement your custom response.
- * <p>
- * Note that either this callback or
- * {@link WifiNanSessionListener#onMessageSendFail(int, int)} will be
- * received - never both.
- */
- public void onMessageSendSuccess(int messageId) {
- if (VDBG) Log.v(TAG, "onMessageSendSuccess: called in stub - override if interested");
- }
-
- /**
- * Called when a message transmission fails - i.e. when no ACK is received.
- * The hardware will usually attempt to re-transmit several times - this
- * event is received after all retries are exhausted. There is a possibility
- * that message was received by the destination successfully but the ACK was
- * lost. It is dummy method (empty implementation printing out a log
- * message). Override to implement your custom response.
- * <p>
- * Note that either this callback or
- * {@link WifiNanSessionListener#onMessageSendSuccess(int)} will be received
- * - never both
- *
- * @param reason The failure reason using {@code NanSessionListener.FAIL_*}
- * codes.
- */
- public void onMessageSendFail(int messageId, int reason) {
- if (VDBG) Log.v(TAG, "onMessageSendFail: called in stub - override if interested");
- }
-
- /**
- * Called when a message is received from a discovery session peer. It is
- * dummy method (empty implementation printing out a log message). Override
- * to implement your custom response.
- *
- * @param peerId The ID of the peer sending the message.
- * @param message A byte array containing the message.
- * @param messageLength The length of the byte array containing the relevant
- * message bytes.
- */
- public void onMessageReceived(int peerId, byte[] message, int messageLength) {
- if (VDBG) Log.v(TAG, "onMessageReceived: called in stub - override if interested");
- }
-
- /**
- * {@hide}
- */
- public IWifiNanSessionListener callback = new IWifiNanSessionListener.Stub() {
- @Override
- public void onPublishFail(int reason) {
- if (VDBG) Log.v(TAG, "onPublishFail: reason=" + reason);
-
- Message msg = mHandler.obtainMessage(LISTEN_PUBLISH_FAIL);
- msg.arg1 = reason;
- mHandler.sendMessage(msg);
- }
-
- @Override
- public void onPublishTerminated(int reason) {
- if (VDBG) Log.v(TAG, "onPublishResponse: reason=" + reason);
-
- Message msg = mHandler.obtainMessage(LISTEN_PUBLISH_TERMINATED);
- msg.arg1 = reason;
- mHandler.sendMessage(msg);
- }
-
- @Override
- public void onSubscribeFail(int reason) {
- if (VDBG) Log.v(TAG, "onSubscribeFail: reason=" + reason);
-
- Message msg = mHandler.obtainMessage(LISTEN_SUBSCRIBE_FAIL);
- msg.arg1 = reason;
- mHandler.sendMessage(msg);
- }
-
- @Override
- public void onSubscribeTerminated(int reason) {
- if (VDBG) Log.v(TAG, "onSubscribeTerminated: reason=" + reason);
-
- Message msg = mHandler.obtainMessage(LISTEN_SUBSCRIBE_TERMINATED);
- msg.arg1 = reason;
- mHandler.sendMessage(msg);
- }
-
- @Override
- public void onMatch(int peerId, byte[] serviceSpecificInfo,
- int serviceSpecificInfoLength, byte[] matchFilter, int matchFilterLength) {
- if (VDBG) Log.v(TAG, "onMatch: peerId=" + peerId);
-
- Bundle data = new Bundle();
- data.putInt(MESSAGE_BUNDLE_KEY_PEER_ID, peerId);
- data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE, serviceSpecificInfo);
- data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE2, matchFilter);
-
- Message msg = mHandler.obtainMessage(LISTEN_MATCH);
- msg.arg1 = serviceSpecificInfoLength;
- msg.arg2 = matchFilterLength;
- msg.setData(data);
- mHandler.sendMessage(msg);
- }
-
- @Override
- public void onMessageSendSuccess(int messageId) {
- if (VDBG) Log.v(TAG, "onMessageSendSuccess");
-
- Message msg = mHandler.obtainMessage(LISTEN_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(LISTEN_MESSAGE_SEND_FAIL);
- msg.arg1 = messageId;
- msg.arg2 = reason;
- mHandler.sendMessage(msg);
- }
-
- @Override
- public void onMessageReceived(int peerId, byte[] message, int messageLength) {
- if (VDBG) {
- Log.v(TAG, "onMessageReceived: peerId='" + peerId + "', messageLength="
- + messageLength);
- }
-
- Bundle data = new Bundle();
- data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE, message);
-
- Message msg = mHandler.obtainMessage(LISTEN_MESSAGE_RECEIVED);
- msg.arg1 = messageLength;
- msg.arg2 = peerId;
- msg.setData(data);
- mHandler.sendMessage(msg);
- }
- };
-}
diff --git a/wifi/java/android/net/wifi/nan/WifiNanSubscribeSession.java b/wifi/java/android/net/wifi/nan/WifiNanSubscribeSession.java
index 7dfdd32..d0e56c5 100644
--- a/wifi/java/android/net/wifi/nan/WifiNanSubscribeSession.java
+++ b/wifi/java/android/net/wifi/nan/WifiNanSubscribeSession.java
@@ -16,15 +16,22 @@
package android.net.wifi.nan;
+import android.annotation.NonNull;
+import android.util.Log;
+
/**
- * A representation of a NAN subscribe session. Created when
- * {@link WifiNanManager#subscribe(SubscribeData, SubscribeSettings, WifiNanSessionListener, int)}
- * is executed. The object can be used to stop and re-start (re-configure) the
- * subscribe session.
+ * A class representing a NAN subscribe session. Created when
+ * {@link WifiNanManager#subscribe(SubscribeConfig, WifiNanSessionCallback)} is called and a
+ * discovery session is created and returned in
+ * {@link WifiNanSessionCallback#onSubscribeStarted(WifiNanSubscribeSession)}. See baseline
+ * functionality of all discovery sessions in {@link WifiNanSession}. This object allows updating
+ * an existing/running subscribe discovery session using {@link #updateSubscribe(SubscribeConfig)}.
*
* @hide PROPOSED_NAN_API
*/
public class WifiNanSubscribeSession extends WifiNanSession {
+ private static final String TAG = "WifiNanSubscribeSession";
+
/**
* {@hide}
*/
@@ -33,15 +40,33 @@
}
/**
- * Restart/re-configure the subscribe session. Note that the
- * {@link WifiNanSessionListener} is not replaced - the same listener used at
- * creation is still used.
+ * Re-configure the currently active subscribe session. The
+ * {@link WifiNanSessionCallback} is not replaced - the same listener used
+ * at creation is still used. The results of the configuration are returned using
+ * {@link WifiNanSessionCallback}:
+ * <ul>
+ * <li>{@link WifiNanSessionCallback#onSessionConfigSuccess()}: configuration update
+ * succeeded.
+ * <li>{@link WifiNanSessionCallback#onSessionConfigFail(int)}: 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 subscribeData The data ({@link SubscribeData}) to subscribe.
- * @param subscribeSettings The settings ({@link SubscribeSettings}) of the
- * subscribe session.
+ * @param subscribeConfig The new discovery subscribe session configuration
+ * ({@link SubscribeConfig}).
*/
- public void subscribe(SubscribeData subscribeData, SubscribeSettings subscribeSettings) {
- mManager.subscribe(mSessionId, subscribeData, subscribeSettings);
+ public void updateSubscribe(@NonNull SubscribeConfig subscribeConfig) {
+ if (mTerminated) {
+ Log.w(TAG, "updateSubscribe: called on terminated session");
+ return;
+ } else {
+ WifiNanManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.w(TAG, "updateSubscribe: called post GC on WifiNanManager");
+ return;
+ }
+
+ mgr.updateSubscribe(mSessionId, subscribeConfig);
+ }
}
}
diff --git a/wifi/java/android/net/wifi/nan/WifiNanUtils.java b/wifi/java/android/net/wifi/nan/WifiNanUtils.java
new file mode 100644
index 0000000..c0f36b4
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/WifiNanUtils.java
@@ -0,0 +1,54 @@
+/*
+ * 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.nan;
+
+/**
+ * Provides utilities for the Wifi NAN manager/service.
+ *
+ * @hide
+ */
+public class WifiNanUtils {
+ /**
+ * 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;
+ }
+ }
+}
diff --git a/wifi/java/android/net/wifi/nan/package.html b/wifi/java/android/net/wifi/nan/package.html
new file mode 100644
index 0000000..ae3cf6c
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/package.html
@@ -0,0 +1,43 @@
+<HTML>
+<BODY>
+<p>Provides classes which allow applications to use Wi-Fi NAN to discover peers and create
+ connections to them.</p>
+<p>Using the Wi-Fi NAN APIs, applications can advertise services, discover peers which are
+ advertising services, and connect to them.
+ Wi-Fi NAN is independent of Wi-Fi infrastructure (i.e. a device may or may
+ not be associated with an AP concurrent to using Wi-Fi NAN). </p>
+<p>The primary entry point to Wi-Fi NAN capabilities is the
+ {@link android.net.wifi.nan.WifiNanManager} class, which is acquired by calling
+ {@link android.content.Context#getSystemService(String)
+ Context.getSystemService(Context.WIFI_NAN_SERVICE)}</p>
+
+<p>Some APIs may require the following user permissions:</p>
+<ul>
+ <li>{@link android.Manifest.permission#ACCESS_WIFI_STATE}</li>
+ <li>{@link android.Manifest.permission#CHANGE_WIFI_STATE}</li>
+ <li>{@link android.Manifest.permission#ACCESS_COARSE_LOCATION}</li>
+</ul>
+
+<p class="note"><strong>Note:</strong> Not all Android-powered devices support Wi-Fi NAN
+ functionality.
+ If your application only works with Wi-Fi NAN (i.e. it should only be installed on devices which
+ support Wi-Fi NAN), declare so with a <a
+ href="{@docRoot}guide/topics/manifest/uses-feature-element.html">
+ {@code <uses-feature>}</a>
+ element in the manifest file:</p>
+<pre>
+<manifest ...>
+ <uses-feature android:name="android.hardware.wifi.nan" />
+ ...
+</manifest>
+</pre>
+<p>Alternatively, if you application does not require Wi-Fi NAN but can take advantage of it if
+ available, you can perform
+ the check at run-time in your code using {@link
+ android.content.pm.PackageManager#hasSystemFeature(String)} with {@link
+ android.content.pm.PackageManager#FEATURE_WIFI_NAN}:</p>
+<pre>
+ getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_NAN)
+</pre>
+</BODY>
+</HTML>
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
index a0cb035..a68bcd9 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
@@ -66,19 +66,28 @@
/* 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;
/**
@@ -305,6 +314,7 @@
return other.deviceAddress.equals(deviceAddress);
}
+ @Override
public String toString() {
StringBuffer sbuf = new StringBuffer();
sbuf.append("Device: ").append(deviceName);
@@ -320,6 +330,7 @@
}
/** Implement the Parcelable interface */
+ @Override
public int describeContents() {
return 0;
}
@@ -340,6 +351,7 @@
}
/** Implement the Parcelable interface */
+ @Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(deviceName);
dest.writeString(deviceAddress);
@@ -360,6 +372,7 @@
/** Implement the Parcelable interface */
public static final Creator<WifiP2pDevice> CREATOR =
new Creator<WifiP2pDevice>() {
+ @Override
public WifiP2pDevice createFromParcel(Parcel in) {
WifiP2pDevice device = new WifiP2pDevice();
device.deviceName = in.readString();
@@ -376,6 +389,7 @@
return device;
}
+ @Override
public WifiP2pDevice[] newArray(int size) {
return new WifiP2pDevice[size];
}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
index ca737f9..32673c7 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
@@ -146,7 +146,6 @@
}
if (nameValue[0].equals("persistent")) {
- mOwner = new WifiP2pDevice(sa);
mNetId = Integer.parseInt(nameValue[1]);
continue;
}