am 5a2906a8: am 19a37cad: am 6469dee2: am d15dd73c: am d26b8028: am 64bb1b75: am 799044f3: am 505ce805: Merge "Change SSLCertificateSocketFactoryTest to use googlemail.com" into jb-dev

* commit '5a2906a8e88154a6647f1d30079039a2bd712a4d':
  Change SSLCertificateSocketFactoryTest to use googlemail.com
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index d4bff12..6f67ed9 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -97,6 +97,7 @@
         assertTrue(mCm.isNetworkTypeValid(ConnectivityManager.TYPE_MOBILE_IMS));
         assertTrue(mCm.isNetworkTypeValid(ConnectivityManager.TYPE_MOBILE_CBS));
         assertTrue(mCm.isNetworkTypeValid(ConnectivityManager.TYPE_WIFI_P2P));
+        assertTrue(mCm.isNetworkTypeValid(ConnectivityManager.TYPE_MOBILE_IA));
         assertFalse(mCm.isNetworkTypeValid(-1));
         assertTrue(mCm.isNetworkTypeValid(0));
         assertTrue(mCm.isNetworkTypeValid(ConnectivityManager.MAX_NETWORK_TYPE));
diff --git a/tests/cts/net/src/android/net/cts/DnsTest.java b/tests/cts/net/src/android/net/cts/DnsTest.java
new file mode 100644
index 0000000..cdd95aa
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/DnsTest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts;
+
+import android.os.SystemClock;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+
+public class DnsTest extends AndroidTestCase {
+
+    static {
+        System.loadLibrary("nativedns_jni");
+    }
+
+    private static final boolean DBG = false;
+    private static final String TAG = "DnsTest";
+
+    /**
+     * @return true on success
+     */
+    private static native boolean testNativeDns();
+
+    /**
+     * Verify:
+     * DNS works - forwards and backwards, giving ipv4 and ipv6
+     * Test that DNS work on v4 and v6 networks
+     * Test Native dns calls (4)
+     * Todo:
+     * Cache is flushed when we change networks
+     * have per-network caches
+     * No cache when there's no network
+     * Perf - measure size of first and second tier caches and their effect
+     * Assert requires network permission
+     */
+    public void testDnsWorks() {
+        InetAddress addrs[] = {};
+        try {
+            addrs = InetAddress.getAllByName("www.google.com");
+        } catch (UnknownHostException e) {}
+        assertTrue(addrs.length != 0);
+        boolean foundV4 = false, foundV6 = false;
+        for (InetAddress addr : addrs) {
+            if (addr instanceof Inet4Address) foundV4 = true;
+            else if (addr instanceof Inet6Address) foundV6 = true;
+            if (DBG) Log.e(TAG, "www.google.com gave " + addr.toString());
+        }
+        assertTrue(foundV4);
+        assertTrue(foundV6);
+        try {
+            addrs = InetAddress.getAllByName("ipv6.google.com");
+        } catch (UnknownHostException e) {}
+        assertTrue(addrs.length != 0);
+        foundV4 = false;
+        foundV6 = false;
+        for (InetAddress addr : addrs) {
+            if (addr instanceof Inet4Address) foundV4 = true;
+            else if (addr instanceof Inet6Address) foundV6 = true;
+            if (DBG) Log.e(TAG, "ipv6.google.com gave " + addr.toString());
+        }
+        assertTrue(foundV4 == false);
+        assertTrue(foundV6 == true);
+        assertTrue(testNativeDns());
+    }
+
+    private static final String[] URLS = { "www.google.com", "ipv6.google.com", "www.yahoo.com",
+            "facebook.com", "youtube.com", "blogspot.com", "baidu.com", "wikipedia.org",
+// live.com fails rev lookup.
+            "twitter.com", "qq.com", "msn.com", "yahoo.co.jp", "linkedin.com",
+            "taobao.com", "google.co.in", "sina.com.cn", "amazon.com", "wordpress.com",
+            "google.co.uk", "ebay.com", "yandex.ru", "163.com", "google.co.jp", "google.fr",
+            "microsoft.com", "paypal.com", "google.com.br", "flickr.com",
+            "mail.ru", "craigslist.org", "fc2.com", "google.it",
+// "apple.com", fails rev lookup
+            "google.es",
+            "imdb.com", "google.ru", "soho.com", "bbc.co.uk", "vkontakte.ru", "ask.com",
+            "tumblr.com", "weibo.com", "go.com", "xvideos.com", "livejasmin.com", "cnn.com",
+            "youku.com", "blogspot.com", "soso.com", "google.ca", "aol.com", "tudou.com",
+            "xhamster.com", "megaupload.com", "ifeng.com", "zedo.com", "mediafire.com", "ameblo.jp",
+            "pornhub.com", "google.co.id", "godaddy.com", "adobe.com", "rakuten.co.jp", "about.com",
+            "espn.go.com", "4shared.com", "alibaba.com","ebay.de", "yieldmanager.com",
+            "wordpress.org", "livejournal.com", "google.com.tr", "google.com.mx", "renren.com",
+           "livedoor.com", "google.com.au", "youporn.com", "uol.com.br", "cnet.com", "conduit.com",
+            "google.pl", "myspace.com", "nytimes.com", "ebay.co.uk", "chinaz.com", "hao123.com",
+            "thepiratebay.org", "doubleclick.com", "alipay.com", "netflix.com", "cnzz.com",
+            "huffingtonpost.com", "twitpic.com", "weather.com", "babylon.com", "amazon.de",
+            "dailymotion.com", "orkut.com", "orkut.com.br", "google.com.sa", "odnoklassniki.ru",
+            "amazon.co.jp", "google.nl", "goo.ne.jp", "stumbleupon.com", "tube8.com", "tmall.com",
+            "imgur.com", "globo.com", "secureserver.net", "fileserve.com", "tianya.cn", "badoo.com",
+            "ehow.com", "photobucket.com", "imageshack.us", "xnxx.com", "deviantart.com",
+            "filestube.com", "addthis.com", "douban.com", "vimeo.com", "sogou.com",
+            "stackoverflow.com", "reddit.com", "dailymail.co.uk", "redtube.com", "megavideo.com",
+            "taringa.net", "pengyou.com", "amazon.co.uk", "fbcdn.net", "aweber.com", "spiegel.de",
+            "rapidshare.com", "mixi.jp", "360buy.com", "google.cn", "digg.com", "answers.com",
+            "bit.ly", "indiatimes.com", "skype.com", "yfrog.com", "optmd.com", "google.com.eg",
+            "google.com.pk", "58.com", "hotfile.com", "google.co.th",
+            "bankofamerica.com", "sourceforge.net", "maktoob.com", "warriorforum.com", "rediff.com",
+            "google.co.za", "56.com", "torrentz.eu", "clicksor.com", "avg.com",
+            "download.com", "ku6.com", "statcounter.com", "foxnews.com", "google.com.ar",
+            "nicovideo.jp", "reference.com", "liveinternet.ru", "ucoz.ru", "xinhuanet.com",
+            "xtendmedia.com", "naver.com", "youjizz.com", "domaintools.com", "sparkstudios.com",
+            "rambler.ru", "scribd.com", "kaixin001.com", "mashable.com", "adultfirendfinder.com",
+            "files.wordpress.com", "guardian.co.uk", "bild.de", "yelp.com", "wikimedia.org",
+            "chase.com", "onet.pl", "ameba.jp", "pconline.com.cn", "free.fr", "etsy.com",
+            "typepad.com", "youdao.com", "megaclick.com", "digitalpoint.com", "blogfa.com",
+            "salesforce.com", "adf.ly", "ganji.com", "wikia.com", "archive.org", "terra.com.br",
+            "w3schools.com", "ezinearticles.com", "wjs.com", "google.com.my", "clickbank.com",
+            "squidoo.com", "hulu.com", "repubblica.it", "google.be", "allegro.pl", "comcast.net",
+            "narod.ru", "zol.com.cn", "orange.fr", "soufun.com", "hatena.ne.jp", "google.gr",
+            "in.com", "techcrunch.com", "orkut.co.in", "xunlei.com",
+            "reuters.com", "google.com.vn", "hostgator.com", "kaskus.us", "espncricinfo.com",
+            "hootsuite.com", "qiyi.com", "gmx.net", "xing.com", "php.net", "soku.com", "web.de",
+            "libero.it", "groupon.com", "51.la", "slideshare.net", "booking.com", "seesaa.net",
+            "126.com", "telegraph.co.uk", "wretch.cc", "twimg.com", "rutracker.org", "angege.com",
+            "nba.com", "dell.com", "leboncoin.fr", "people.com", "google.com.tw", "walmart.com",
+            "daum.net", "2ch.net", "constantcontact.com", "nifty.com", "mywebsearch.com",
+            "tripadvisor.com", "google.se", "paipai.com", "google.com.ua", "ning.com", "hp.com",
+            "google.at", "joomla.org", "icio.us", "hudong.com", "csdn.net", "getfirebug.com",
+            "ups.com", "cj.com", "google.ch", "camzap.com", "wordreference.com", "tagged.com",
+            "wp.pl", "mozilla.com", "google.ru", "usps.com", "china.com", "themeforest.net",
+            "search-results.com", "tribalfusion.com", "thefreedictionary.com", "isohunt.com",
+            "linkwithin.com", "cam4.com", "plentyoffish.com", "wellsfargo.com", "metacafe.com",
+            "depositfiles.com", "freelancer.com", "opendns.com", "homeway.com", "engadget.com",
+            "10086.cn", "360.cn", "marca.com", "dropbox.com", "ign.com", "match.com", "google.pt",
+            "facemoods.com", "hardsextube.com", "google.com.ph", "lockerz.com", "istockphoto.com",
+            "partypoker.com", "netlog.com", "outbrain.com", "elpais.com", "fiverr.com",
+            "biglobe.ne.jp", "corriere.it", "love21cn.com", "yesky.com", "spankwire.com",
+            "ig.com.br", "imagevenue.com", "hubpages.com", "google.co.ve"};
+
+// TODO - this works, but is slow and cts doesn't do anything with the result.
+// Maybe require a min performance, a min cache size (detectable) and/or move
+// to perf testing
+    private static final int LOOKUP_COUNT_GOAL = URLS.length;
+    public void skiptestDnsPerf() {
+        ArrayList<String> results = new ArrayList<String>();
+        int failures = 0;
+        try {
+            for (int numberOfUrls = URLS.length; numberOfUrls > 0; numberOfUrls--) {
+                failures = 0;
+                int iterationLimit = LOOKUP_COUNT_GOAL / numberOfUrls;
+                long startTime = SystemClock.elapsedRealtimeNanos();
+                for (int iteration = 0; iteration < iterationLimit; iteration++) {
+                    for (int urlIndex = 0; urlIndex < numberOfUrls; urlIndex++) {
+                        try {
+                            InetAddress addr = InetAddress.getByName(URLS[urlIndex]);
+                        } catch (UnknownHostException e) {
+                            Log.e(TAG, "failed first lookup of " + URLS[urlIndex]);
+                            failures++;
+                            try {
+                                InetAddress addr = InetAddress.getByName(URLS[urlIndex]);
+                            } catch (UnknownHostException ee) {
+                                failures++;
+                                Log.e(TAG, "failed SECOND lookup of " + URLS[urlIndex]);
+                            }
+                        }
+                    }
+                }
+                long endTime = SystemClock.elapsedRealtimeNanos();
+                float nsPer = ((float)(endTime-startTime) / iterationLimit) / numberOfUrls/ 1000;
+                String thisResult = new String("getByName for " + numberOfUrls + " took " +
+                        (endTime - startTime)/1000 + "(" + nsPer + ") with " +
+                        failures + " failures\n");
+                Log.d(TAG, thisResult);
+                results.add(thisResult);
+            }
+            // build up a list of addresses
+            ArrayList<byte[]> addressList = new ArrayList<byte[]>();
+            for (String url : URLS) {
+                try {
+                    InetAddress addr = InetAddress.getByName(url);
+                    addressList.add(addr.getAddress());
+                } catch (UnknownHostException e) {
+                    Log.e(TAG, "Exception making reverseDNS list: " + e.toString());
+                }
+            }
+            for (int numberOfAddrs = addressList.size(); numberOfAddrs > 0; numberOfAddrs--) {
+                int iterationLimit = LOOKUP_COUNT_GOAL / numberOfAddrs;
+                failures = 0;
+                long startTime = SystemClock.elapsedRealtimeNanos();
+                for (int iteration = 0; iteration < iterationLimit; iteration++) {
+                    for (int addrIndex = 0; addrIndex < numberOfAddrs; addrIndex++) {
+                        try {
+                            InetAddress addr = InetAddress.getByAddress(addressList.get(addrIndex));
+                            String hostname = addr.getHostName();
+                        } catch (UnknownHostException e) {
+                            failures++;
+                            Log.e(TAG, "Failure doing reverse DNS lookup: " + e.toString());
+                            try {
+                                InetAddress addr =
+                                        InetAddress.getByAddress(addressList.get(addrIndex));
+                                String hostname = addr.getHostName();
+
+                            } catch (UnknownHostException ee) {
+                                failures++;
+                                Log.e(TAG, "Failure doing SECOND reverse DNS lookup: " +
+                                        ee.toString());
+                            }
+                        }
+                    }
+                }
+                long endTime = SystemClock.elapsedRealtimeNanos();
+                float nsPer = ((endTime-startTime) / iterationLimit) / numberOfAddrs / 1000;
+                String thisResult = new String("getHostName for " + numberOfAddrs + " took " +
+                        (endTime - startTime)/1000 + "(" + nsPer + ") with " +
+                        failures + " failures\n");
+                Log.d(TAG, thisResult);
+                results.add(thisResult);
+            }
+            for (String result : results) Log.d(TAG, result);
+
+            InetAddress exit = InetAddress.getByName("exitrightnow.com");
+            Log.e(TAG, " exit address= "+exit.toString());
+
+        } catch (Exception e) {
+            Log.e(TAG, "bad URL in testDnsPerf: " + e.toString());
+        }
+    }
+}
diff --git a/tests/cts/net/src/android/net/http/cts/SslErrorTest.java b/tests/cts/net/src/android/net/http/cts/SslErrorTest.java
old mode 100755
new mode 100644
diff --git a/tests/cts/net/src/android/net/ipv6/cts/PingTest.java b/tests/cts/net/src/android/net/ipv6/cts/PingTest.java
new file mode 100644
index 0000000..41eb03d
--- /dev/null
+++ b/tests/cts/net/src/android/net/ipv6/cts/PingTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipv6.cts;
+
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import libcore.io.ErrnoException;
+import libcore.io.Libcore;
+import libcore.io.StructTimeval;
+import static libcore.io.OsConstants.*;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Inet6Address;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Random;
+
+public class PingTest extends AndroidTestCase {
+    /** Maximum size of the packets we're using to test. */
+    private static final int MAX_SIZE = 4096;
+
+    /** Number of packets to test. */
+    private static final int NUM_PACKETS = 10;
+
+    /** The beginning of an ICMPv6 echo request: type, code, and uninitialized checksum. */
+    private static final byte[] PING_HEADER = new byte[] {
+        (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00
+    };
+
+    /**
+     * Returns a byte array containing an ICMPv6 echo request with the specified payload length.
+     */
+    private byte[] pingPacket(int payloadLength) {
+        byte[] packet = new byte[payloadLength + 8];
+        new Random().nextBytes(packet);
+        System.arraycopy(PING_HEADER, 0, packet, 0, PING_HEADER.length);
+        return packet;
+    }
+
+    /**
+     * Checks that the first length bytes of two byte arrays are equal.
+     */
+    private void assertArrayBytesEqual(byte[] expected, byte[] actual, int length) {
+        for (int i = 0; i < length; i++) {
+            assertEquals("Arrays differ at index " + i + ":", expected[i], actual[i]);
+        }
+    }
+
+    /**
+     * Creates an IPv6 ping socket and sets a receive timeout of 100ms.
+     */
+    private FileDescriptor createPingSocket() throws ErrnoException {
+        FileDescriptor s = Libcore.os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6);
+        Libcore.os.setsockoptTimeval(s, SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(100));
+        return s;
+    }
+
+    /**
+     * Sends a ping packet to a random port on the specified address on the specified socket.
+     */
+    private void sendPing(FileDescriptor s,
+            InetAddress address, byte[] packet) throws ErrnoException, IOException {
+        // Pick a random port. Choose a range that gives a reasonable chance of picking a low port.
+        int port = (int) (Math.random() * 2048);
+
+        // Send the packet.
+        int ret = Libcore.os.sendto(s, ByteBuffer.wrap(packet), 0, address, port);
+        assertEquals(packet.length, ret);
+    }
+
+    /**
+     * Checks that a socket has received a response appropriate to the specified packet.
+     */
+    private void checkResponse(FileDescriptor s,
+            InetAddress dest, byte[] sent) throws ErrnoException, IOException {
+        // Receive the response.
+        InetSocketAddress from = new InetSocketAddress();
+        ByteBuffer responseBuffer = ByteBuffer.allocate(MAX_SIZE);
+        int bytesRead = Libcore.os.recvfrom(s, responseBuffer, 0, from);
+
+        // Check the source address and scope ID.
+        assertTrue(from.getAddress() instanceof Inet6Address);
+        Inet6Address fromAddress = (Inet6Address) from.getAddress();
+        assertEquals(0, fromAddress.getScopeId());
+        assertNull(fromAddress.getScopedInterface());
+        assertEquals(dest.getHostAddress(), fromAddress.getHostAddress());
+
+        // Check the packet length.
+        assertEquals(sent.length, bytesRead);
+
+        // Check the response is an echo reply.
+        byte[] response = new byte[bytesRead];
+        responseBuffer.get(response, 0, bytesRead);
+        assertEquals((byte) 0x81, response[0]);
+
+        // Find out what ICMP ID was used in the packet that was sent.
+        int id = ((InetSocketAddress) Libcore.os.getsockname(s)).getPort();
+        sent[4] = (byte) (id / 256);
+        sent[5] = (byte) (id % 256);
+
+        // Ensure the response is the same as the packet, except for the type (which is 0x81)
+        // and the ID and checksum,  which are set by the kernel.
+        response[0] = (byte) 0x80;                 // Type.
+        response[2] = response[3] = (byte) 0x00;   // Checksum.
+        assertArrayBytesEqual(response, sent, bytesRead);
+    }
+
+    /**
+     * Sends NUM_PACKETS random ping packets to ::1 and checks the replies.
+     */
+    public void testLoopbackPing() throws ErrnoException, IOException {
+        // Generate a random ping packet and send it to localhost.
+        InetAddress ipv6Loopback = InetAddress.getByName(null);
+        assertEquals("localhost/::1", ipv6Loopback.toString());
+
+        for (int i = 0; i < NUM_PACKETS; i++) {
+            byte[] packet = pingPacket((int) (Math.random() * MAX_SIZE));
+            FileDescriptor s = createPingSocket();
+            sendPing(s, ipv6Loopback, packet);
+            checkResponse(s, ipv6Loopback, packet);
+            // Check closing the socket doesn't raise an exception.
+            Libcore.os.close(s);
+        }
+    }
+}
diff --git a/tests/cts/net/src/android/net/wifi/cts/NsdManagerTest.java b/tests/cts/net/src/android/net/wifi/cts/NsdManagerTest.java
new file mode 100644
index 0000000..482a4e3
--- /dev/null
+++ b/tests/cts/net/src/android/net/wifi/cts/NsdManagerTest.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.cts;
+
+import android.content.Context;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.util.Random;
+import java.util.List;
+import java.util.ArrayList;
+
+public class NsdManagerTest extends AndroidTestCase {
+
+    private static final String TAG = "NsdManagerTest";
+    private static final String SERVICE_TYPE = "_nmt._tcp";
+    private static final int TIMEOUT = 2000;
+
+    private static final boolean DBG = false;
+
+    NsdManager mNsdManager;
+
+    NsdManager.RegistrationListener mRegistrationListener;
+    NsdManager.DiscoveryListener mDiscoveryListener;
+    NsdManager.ResolveListener mResolveListener;
+
+    public NsdManagerTest() {
+        mRegistrationListener = new NsdManager.RegistrationListener() {
+            @Override
+            public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
+                setEvent("onRegistrationFailed", errorCode);
+            }
+
+            @Override
+            public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
+                setEvent("onUnregistrationFailed", errorCode);
+            }
+
+            @Override
+            public void onServiceRegistered(NsdServiceInfo serviceInfo) {
+                setEvent("onServiceRegistered", serviceInfo);
+            }
+
+            @Override
+            public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
+                setEvent("onServiceUnregistered", serviceInfo);
+            }
+        };
+        mDiscoveryListener = new NsdManager.DiscoveryListener() {
+            @Override
+            public void onStartDiscoveryFailed(String serviceType, int errorCode) {
+                setEvent("onStartDiscoveryFailed", errorCode);
+            }
+
+            @Override
+            public void onStopDiscoveryFailed(String serviceType, int errorCode) {
+                setEvent("onStopDiscoveryFailed", errorCode);
+            }
+
+            @Override
+            public void onDiscoveryStarted(String serviceType) {
+                NsdServiceInfo info = new NsdServiceInfo();
+                info.setServiceType(serviceType);
+                setEvent("onDiscoveryStarted", info);
+            }
+
+            @Override
+            public void onDiscoveryStopped(String serviceType) {
+                NsdServiceInfo info = new NsdServiceInfo();
+                info.setServiceType(serviceType);
+                setEvent("onDiscoveryStopped", info);
+            }
+
+            @Override
+            public void onServiceFound(NsdServiceInfo serviceInfo) {
+                setEvent("onServiceFound", serviceInfo);
+            }
+
+            @Override
+            public void onServiceLost(NsdServiceInfo serviceInfo) {
+                setEvent("onServiceLost", serviceInfo);
+            }
+        };
+        mResolveListener = new NsdManager.ResolveListener() {
+            @Override
+            public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
+                setEvent("onResolveFailed", errorCode);
+            }
+
+            @Override
+            public void onServiceResolved(NsdServiceInfo serviceInfo) {
+                setEvent("onServiceResolved", serviceInfo);
+            }
+        };
+    }
+
+    private final class EventData {
+        EventData(String callbackName, NsdServiceInfo info) {
+            mCallbackName = callbackName;
+            mSucceeded = true;
+            mErrorCode = 0;
+            mInfo = info;
+        }
+        EventData(String callbackName, int errorCode) {
+            mCallbackName = callbackName;
+            mSucceeded = false;
+            mErrorCode = errorCode;
+            mInfo = null;
+        }
+        private final String mCallbackName;
+        private final boolean mSucceeded;
+        private final int mErrorCode;
+        private final NsdServiceInfo mInfo;
+    }
+
+    private final List<EventData> mEventCache = new ArrayList<EventData>();
+
+    private void setEvent(String callbackName, int errorCode) {
+        if (DBG) Log.d(TAG, callbackName + " failed with " + String.valueOf(errorCode));
+        EventData eventData = new EventData(callbackName, errorCode);
+        synchronized (mEventCache) {
+            mEventCache.add(eventData);
+            mEventCache.notify();
+        }
+    }
+
+    private void setEvent(String callbackName, NsdServiceInfo info) {
+        if (DBG) Log.d(TAG, "Received event " + callbackName + " for " + info.getServiceName());
+        EventData eventData = new EventData(callbackName, info);
+        synchronized (mEventCache) {
+            mEventCache.add(eventData);
+            mEventCache.notify();
+        }
+    }
+
+    void clearEventCache() {
+        synchronized(mEventCache) {
+            mEventCache.clear();
+        }
+    }
+
+    int eventCacheSize() {
+        synchronized(mEventCache) {
+            return mEventCache.size();
+        }
+    }
+
+    private int mWaitId = 0;
+    private EventData waitForCallback(String callbackName) {
+
+        synchronized(mEventCache) {
+
+            mWaitId ++;
+            if (DBG) Log.d(TAG, "Waiting for " + callbackName + ", id=" + String.valueOf(mWaitId));
+
+            try {
+                long startTime = android.os.SystemClock.uptimeMillis();
+                long elapsedTime = 0;
+                int index = 0;
+                while (elapsedTime < TIMEOUT ) {
+                    // first check if we've received that event
+                    for (; index < mEventCache.size(); index++) {
+                        EventData e = mEventCache.get(index);
+                        if (e.mCallbackName.equals(callbackName)) {
+                            if (DBG) Log.d(TAG, "exiting wait id=" + String.valueOf(mWaitId));
+                            return e;
+                        }
+                    }
+
+                    // Not yet received, just wait
+                    mEventCache.wait(TIMEOUT - elapsedTime);
+                    elapsedTime = android.os.SystemClock.uptimeMillis() - startTime;
+                }
+                // we exited the loop because of TIMEOUT; fail the call
+                if (DBG) Log.d(TAG, "timed out waiting id=" + String.valueOf(mWaitId));
+                return null;
+            } catch (InterruptedException e) {
+                return null;                       // wait timed out!
+            }
+        }
+    }
+
+    private String mServiceName;
+
+    @Override
+    public void setUp() {
+        if (DBG) Log.d(TAG, "Setup test ...");
+        mNsdManager = (NsdManager) getContext().getSystemService(Context.NSD_SERVICE);
+
+        Random rand = new Random();
+        mServiceName = new String("NsdTest");
+        for (int i = 0; i < 4; i++) {
+            mServiceName = mServiceName + String.valueOf(rand.nextInt(10));
+        }
+    }
+
+    @Override
+    public void tearDown() {
+        if (DBG) Log.d(TAG, "Tear down test ...");
+    }
+
+    public void runTest() throws Exception {
+        NsdServiceInfo si = new NsdServiceInfo();
+        si.setServiceType(SERVICE_TYPE);
+        si.setServiceName(mServiceName);
+
+        EventData lastEvent = null;
+
+        if (DBG) Log.d(TAG, "Starting test ...");
+
+        ServerSocket socket;
+        int localPort;
+
+        try {
+            socket = new ServerSocket(0);
+            localPort = socket.getLocalPort();
+            si.setPort(localPort);
+        } catch (IOException e) {
+            if (DBG) Log.d(TAG, "Could not open a local socket");
+            assertTrue(false);
+            return;
+        }
+
+        if (DBG) Log.d(TAG, "Port = " + String.valueOf(localPort));
+
+        clearEventCache();
+
+        mNsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
+        lastEvent = waitForCallback("onServiceRegistered");                 // id = 1
+        assertTrue(lastEvent != null);
+        assertTrue(lastEvent.mSucceeded);
+        assertTrue(eventCacheSize() == 1);
+
+        // We may not always get the name that we tried to register;
+        // This events tells us the name that was registered.
+        String registeredName = lastEvent.mInfo.getServiceName();
+        si.setServiceName(registeredName);
+
+        clearEventCache();
+
+        mNsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
+                mDiscoveryListener);
+
+        // Expect discovery started
+        lastEvent = waitForCallback("onDiscoveryStarted");                  // id = 2
+
+        assertTrue(lastEvent != null);
+        assertTrue(lastEvent.mSucceeded);
+
+        // Remove this event, so accounting becomes easier later
+        mEventCache.remove(lastEvent);
+
+        // Expect a service record to be discovered (and filter the ones
+        // that are unrelated to this test)
+        boolean found = false;
+        for (int i = 0; i < 32; i++) {
+
+            lastEvent = waitForCallback("onServiceFound");                  // id = 3
+            if (lastEvent == null) {
+                // no more onServiceFound events are being reported!
+                break;
+            }
+
+            assertTrue(lastEvent.mSucceeded);
+
+            if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " +
+                    lastEvent.mInfo.getServiceName());
+
+            if (lastEvent.mInfo.getServiceName().equals(registeredName)) {
+                // Save it, as it will get overwritten with new serviceFound events
+                si = lastEvent.mInfo;
+                found = true;
+            }
+
+            // Remove this event from the event cache, so it won't be found by subsequent
+            // calls to waitForCallback
+            mEventCache.remove(lastEvent);
+        }
+
+        assertTrue(found);
+
+        // We've removed all serviceFound events, and we've removed the discoveryStarted
+        // event as well, so now the event cache should be empty!
+        assertTrue(eventCacheSize() == 0);
+
+        // Resolve the service
+        clearEventCache();
+        mNsdManager.resolveService(si, mResolveListener);
+        lastEvent = waitForCallback("onServiceResolved");                   // id = 4
+
+        assertTrue(lastEvent != null);
+        assertTrue(lastEvent.mSucceeded);
+
+        if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": Port = " +
+                String.valueOf(lastEvent.mInfo.getPort()));
+
+        assertTrue(lastEvent.mInfo.getPort() == localPort);
+        assertTrue(eventCacheSize() == 1);
+
+        clearEventCache();
+
+        // Unregister the service
+        mNsdManager.unregisterService(mRegistrationListener);
+        lastEvent = waitForCallback("onServiceUnregistered");               // id = 5
+
+        assertTrue(lastEvent != null);
+        assertTrue(lastEvent.mSucceeded);
+
+        // Expect a callback for service lost
+        lastEvent = waitForCallback("onServiceLost");                       // id = 6
+
+        assertTrue(lastEvent != null);
+        assertTrue(lastEvent.mInfo.getServiceName().equals(registeredName));
+
+        assertTrue(eventCacheSize() == 2);
+
+        // Register service again to see if we discover it
+        clearEventCache();
+
+        si = new NsdServiceInfo();
+        si.setServiceType(SERVICE_TYPE);
+        si.setServiceName(mServiceName);
+        si.setPort(localPort);
+        mNsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
+
+        lastEvent = waitForCallback("onServiceRegistered");                 // id = 7
+
+        assertTrue(lastEvent != null);
+        assertTrue(lastEvent.mSucceeded);
+
+        registeredName = lastEvent.mInfo.getServiceName();
+
+        // Expect a record to be discovered
+        lastEvent = waitForCallback("onServiceFound");                      // id = 8
+
+        assertTrue(lastEvent != null);
+        assertTrue(lastEvent.mSucceeded);
+
+        if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " +
+                lastEvent.mInfo.getServiceName());
+
+        assertTrue(lastEvent.mInfo.getServiceName().equals(registeredName));
+
+        assertTrue(eventCacheSize() == 2);
+        clearEventCache();
+
+        mNsdManager.stopServiceDiscovery(mDiscoveryListener);
+        lastEvent = waitForCallback("onDiscoveryStopped");                  // id = 9
+        assertTrue(lastEvent != null);
+        assertTrue(lastEvent.mSucceeded);
+        assertTrue(eventCacheSize() == 1);
+
+        clearEventCache();
+        mNsdManager.unregisterService(mRegistrationListener);
+
+        lastEvent =  waitForCallback("onServiceUnregistered");              // id = 10
+        assertTrue(lastEvent != null);
+        assertTrue(lastEvent.mSucceeded);
+        assertTrue(eventCacheSize() == 1);
+    }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+