[AWARE] CTS for attaching to session + MAC address

Add CTS to verify initial session attach:
- Basic attach
- Attach with identity callback: use to verify MAC
  address change on subsequent attach.

Bug: 30556108
Test: CTS tests pass/fail per expectations
Change-Id: I8c2d29be81bef600a2c9eac99868326473d72b6e
diff --git a/tests/cts/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java b/tests/cts/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java
index 536e885..bea4500 100644
--- a/tests/cts/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java
+++ b/tests/cts/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java
@@ -16,17 +16,22 @@
 
 package android.net.wifi.aware.cts;
 
-import static android.net.wifi.aware.cts.TestUtils.shouldTestWifiAware;
-
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.net.wifi.WifiManager;
+import android.net.wifi.aware.AttachCallback;
 import android.net.wifi.aware.Characteristics;
+import android.net.wifi.aware.IdentityChangedListener;
 import android.net.wifi.aware.WifiAwareManager;
+import android.net.wifi.aware.WifiAwareSession;
 import android.test.AndroidTestCase;
 
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -35,15 +40,20 @@
  * device to validate Wi-Fi Aware.
  */
 public class SingleDeviceTest extends AndroidTestCase {
-    static private final String TAG = "WifiAwareCtsTests";
+    private static final String TAG = "WifiAwareCtsTests";
 
     // wait for Wi-Fi Aware to become available
     static private final int WAIT_FOR_AWARE_CHANGE_SECS = 10;
 
+    private final Object mLock = new Object();
+
     private WifiAwareManager mWifiAwareManager;
     private WifiManager mWifiManager;
     private WifiManager.WifiLock mWifiLock;
 
+    // used to store any WifiAwareSession allocated during tests - will clean-up after tests
+    private List<WifiAwareSession> mSessions = new ArrayList<>();
+
     private class WifiAwareBroadcastReceiver extends BroadcastReceiver {
         private CountDownLatch mBlocker = new CountDownLatch(1);
 
@@ -59,11 +69,92 @@
         }
     };
 
+    private class AttachCallbackTest extends AttachCallback {
+        static final int ATTACHED = 0;
+        static final int ATTACH_FAILED = 1;
+        static final int ERROR = 2; // no callback: timeout, interruption
+
+        private CountDownLatch mBlocker = new CountDownLatch(1);
+        private int mCallbackCalled = ERROR; // garbage init
+        private WifiAwareSession mSession = null;
+
+        @Override
+        public void onAttached(WifiAwareSession session) {
+            mCallbackCalled = ATTACHED;
+            mSession = session;
+            synchronized (mLock) {
+                mSessions.add(session);
+            }
+            mBlocker.countDown();
+        }
+
+        @Override
+        public void onAttachFailed() {
+            mCallbackCalled = ATTACH_FAILED;
+            mBlocker.countDown();
+        }
+
+        /**
+         * Waits for any of the callbacks to be called - or an error (timeout, interruption).
+         * Returns one of the ATTACHED, ATTACH_FAILED, or ERROR values.
+         */
+        int waitForAnyCallback() {
+            try {
+                boolean noTimeout = mBlocker.await(WAIT_FOR_AWARE_CHANGE_SECS, TimeUnit.SECONDS);
+                if (noTimeout) {
+                    return mCallbackCalled;
+                } else {
+                    return ERROR;
+                }
+            } catch (InterruptedException e) {
+                return ERROR;
+            }
+        }
+
+        /**
+         * Access the session created by a callback. Only useful to be called after calling
+         * waitForAnyCallback() and getting the ATTACHED code back.
+         */
+        WifiAwareSession getSession() {
+            return mSession;
+        }
+    }
+
+    private class IdentityChangedListenerTest extends IdentityChangedListener {
+        private CountDownLatch mBlocker = new CountDownLatch(1);
+        private byte[] mMac = null;
+
+        @Override
+        public void onIdentityChanged(byte[] mac) {
+            mMac = mac;
+            mBlocker.countDown();
+        }
+
+        /**
+         * Waits for the listener callback to be called - or an error (timeout, interruption).
+         * Returns true on callback called, false on error (timeout, interruption).
+         */
+        boolean waitForListener() {
+            try {
+                return mBlocker.await(WAIT_FOR_AWARE_CHANGE_SECS, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        /**
+         * Returns the MAC address of the discovery interface supplied to the triggered callback.
+         */
+        byte[] getMac() {
+            return mMac;
+        }
+    }
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
 
-        if (!shouldTestWifiAware(getContext())) {
+        if (!TestUtils.shouldTestWifiAware(getContext())) {
             return;
         }
 
@@ -92,11 +183,19 @@
 
     @Override
     protected void tearDown() throws Exception {
-        if (!shouldTestWifiAware(getContext())) {
+        if (!TestUtils.shouldTestWifiAware(getContext())) {
             super.tearDown();
             return;
         }
 
+        synchronized (mLock) {
+            for (WifiAwareSession session : mSessions) {
+                // no damage from destroying twice (i.e. ok if test cleaned up after itself already)
+                session.destroy();
+            }
+            mSessions.clear();
+        }
+
         super.tearDown();
     }
 
@@ -107,7 +206,7 @@
      *   based on the Wi-Fi Aware protocol.
      */
     public void testCharacteristics() {
-        if (!shouldTestWifiAware(getContext())) {
+        if (!TestUtils.shouldTestWifiAware(getContext())) {
             return;
         }
 
@@ -124,7 +223,7 @@
      * correct status.
      */
     public void testAvailabilityStatusChange() throws Exception {
-        if (!shouldTestWifiAware(getContext())) {
+        if (!TestUtils.shouldTestWifiAware(getContext())) {
             return;
         }
 
@@ -149,4 +248,61 @@
                 receiver2.waitForStateChange());
         assertTrue("Wi-Fi Aware is not available (should be)", mWifiAwareManager.isAvailable());
     }
+
+    /**
+     * Validate that can attach to Wi-Fi Aware.
+     */
+    public void testAttachNoIdentity() {
+        if (!TestUtils.shouldTestWifiAware(getContext())) {
+            return;
+        }
+
+        AttachCallbackTest attachCb = new AttachCallbackTest();
+        mWifiAwareManager.attach(attachCb, null);
+        int cbCalled = attachCb.waitForAnyCallback();
+        assertEquals("Wi-Fi Aware attach", AttachCallbackTest.ATTACHED, cbCalled);
+
+        WifiAwareSession session = attachCb.getSession();
+        assertNotNull("Wi-Fi Aware session", session);
+
+        session.destroy();
+    }
+
+    /**
+     * Validate that can attach to Wi-Fi Aware and get identity information. Use the identity
+     * information to validate that MAC address changes on every attach.
+     *
+     * Note: relies on no other entity using Wi-Fi Aware during the CTS test. Since if it is used
+     * then the attach/destroy will not correspond to enable/disable and will not result in a new
+     * MAC address being generated.
+     */
+    public void testAttachDiscoveryAddressChanges() {
+        if (!TestUtils.shouldTestWifiAware(getContext())) {
+            return;
+        }
+
+        final int numIterations = 10;
+        Set<TestUtils.MacWrapper> macs = new HashSet<>();
+
+        for (int i = 0; i < numIterations; ++i) {
+            AttachCallbackTest attachCb = new AttachCallbackTest();
+            IdentityChangedListenerTest identityL = new IdentityChangedListenerTest();
+            mWifiAwareManager.attach(attachCb, identityL, null);
+            assertEquals("Wi-Fi Aware attach: iteration " + i, AttachCallbackTest.ATTACHED,
+                    attachCb.waitForAnyCallback());
+            assertTrue("Wi-Fi Aware attach: iteration " + i, identityL.waitForListener());
+
+            WifiAwareSession session = attachCb.getSession();
+            assertNotNull("Wi-Fi Aware session: iteration " + i, session);
+
+            byte[] mac = identityL.getMac();
+            assertNotNull("Wi-Fi Aware discovery MAC: iteration " + i, mac);
+
+            session.destroy();
+
+            macs.add(new TestUtils.MacWrapper(mac));
+        }
+
+        assertEquals("", numIterations, macs.size());
+    }
 }
diff --git a/tests/cts/net/src/android/net/wifi/aware/cts/TestUtils.java b/tests/cts/net/src/android/net/wifi/aware/cts/TestUtils.java
index ff9a5d2..a12c8bb 100644
--- a/tests/cts/net/src/android/net/wifi/aware/cts/TestUtils.java
+++ b/tests/cts/net/src/android/net/wifi/aware/cts/TestUtils.java
@@ -19,18 +19,51 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 
+import java.util.Arrays;
+
 /**
  * Test utilities for Wi-Fi Aware CTS test suite.
  */
-public class TestUtils {
+class TestUtils {
     static final String TAG = "WifiAwareCtsTests";
 
     /**
      * Returns a flag indicating whether or not Wi-Fi Aware should be tested. Wi-Fi Aware
      * should be tested if the feature is supported on the current device.
      */
-    public static boolean shouldTestWifiAware(Context context) {
+    static boolean shouldTestWifiAware(Context context) {
         final PackageManager pm = context.getPackageManager();
         return pm.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
     }
+
+    /**
+     * Wraps a byte[] (MAC address representation). Intended to provide hash and equality operators
+     * so that the MAC address can be used in containers.
+     */
+    static class MacWrapper {
+        private byte[] mMac;
+
+        MacWrapper(byte[] mac) {
+            mMac = mac;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+
+            if (!(o instanceof MacWrapper)) {
+                return false;
+            }
+
+            MacWrapper lhs = (MacWrapper) o;
+            return Arrays.equals(mMac, lhs.mMac);
+        }
+
+        @Override
+        public int hashCode() {
+            return Arrays.hashCode(mMac);
+        }
+    }
 }