Introduce RTT Manager

Change-Id: Ic9a16d587897b272f85cdb2b8c52b51bca060d18
diff --git a/Android.mk b/Android.mk
index e2fa7d1..9cda341 100644
--- a/Android.mk
+++ b/Android.mk
@@ -368,6 +368,7 @@
 	wifi/java/android/net/wifi/passpoint/IWifiPasspointManager.aidl \
 	wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \
 	wifi/java/android/net/wifi/IWifiScanner.aidl \
+	wifi/java/android/net/wifi/IRttManager.aidl \
 	packages/services/PacProcessor/com/android/net/IProxyService.aidl \
 	packages/services/Proxy/com/android/net/IProxyCallback.aidl \
 	packages/services/Proxy/com/android/net/IProxyPortListener.aidl \
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index e274f09..d7c384a 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -16,8 +16,6 @@
 
 package android.app;
 
-import android.net.wifi.IWifiScanner;
-import android.net.wifi.WifiScanner;
 import android.os.Build;
 
 import android.service.persistentdata.IPersistentDataBlockService;
@@ -93,6 +91,10 @@
 import android.net.wifi.passpoint.WifiPasspointManager;
 import android.net.wifi.p2p.IWifiP2pManager;
 import android.net.wifi.p2p.WifiP2pManager;
+import android.net.wifi.IWifiScanner;
+import android.net.wifi.WifiScanner;
+import android.net.wifi.IRttManager;
+import android.net.wifi.RttManager;
 import android.nfc.NfcManager;
 import android.os.BatteryManager;
 import android.os.Binder;
@@ -622,6 +624,13 @@
                 return new WifiScanner(ctx.getOuterContext(), service);
             }});
 
+        registerService(WIFI_RTT_SERVICE, new ServiceFetcher() {
+            public Object createService(ContextImpl ctx) {
+                IBinder b = ServiceManager.getService(WIFI_RTT_SERVICE);
+                IRttManager service = IRttManager.Stub.asInterface(b);
+                return new RttManager(ctx.getOuterContext(), service);
+            }});
+
         registerService(ETHERNET_SERVICE, new ServiceFetcher() {
                 public Object createService(ContextImpl ctx) {
                     IBinder b = ServiceManager.getService(ETHERNET_SERVICE);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 1569b9f..7d15233 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2067,6 +2067,7 @@
             WIFI_P2P_SERVICE,
             WIFI_SCANNING_SERVICE,
             //@hide: ETHERNET_SERVICE,
+            WIFI_RTT_SERVICE,
             NSD_SERVICE,
             AUDIO_SERVICE,
             MEDIA_ROUTER_SERVICE,
@@ -2463,6 +2464,17 @@
 
     /**
      * Use with {@link #getSystemService} to retrieve a {@link
+     * android.net.wifi.RttManager} for ranging devices with wifi
+     *
+     * @see #getSystemService
+     * @see android.net.wifi.RttManager
+     * @hide
+     */
+    @SystemApi
+    public static final String WIFI_RTT_SERVICE = "rttmanager";
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a {@link
      * android.net.ethernet.EthernetManager} for handling management of
      * Ethernet access.
      *
diff --git a/wifi/java/android/net/wifi/IRttManager.aidl b/wifi/java/android/net/wifi/IRttManager.aidl
new file mode 100644
index 0000000..d929f55
--- /dev/null
+++ b/wifi/java/android/net/wifi/IRttManager.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.os.Messenger;
+
+/**
+ * {@hide}
+ */
+interface IRttManager
+{
+    Messenger getMessenger();
+}
diff --git a/wifi/java/android/net/wifi/RttManager.java b/wifi/java/android/net/wifi/RttManager.java
new file mode 100644
index 0000000..50fd260
--- /dev/null
+++ b/wifi/java/android/net/wifi/RttManager.java
@@ -0,0 +1,334 @@
+package android.net.wifi;
+
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.util.AsyncChannel;
+
+import java.util.concurrent.CountDownLatch;
+
+/** @hide */
+@SystemApi
+public class RttManager {
+
+    private static final boolean DBG = true;
+    private static final String TAG = "RttManager";
+
+    public static final int RTT_TYPE_UNSPECIFIED    = 0;
+    public static final int RTT_TYPE_ONE_SIDED      = 1;
+    public static final int RTT_TYPE_11_V           = 2;
+    public static final int RTT_TYPE_11_MC          = 4;
+
+    public static final int RTT_PEER_TYPE_UNSPECIFIED    = 0;
+    public static final int RTT_PEER_TYPE_AP             = 1;
+    public static final int RTT_PEER_TYPE_STA            = 2;       /* requires NAN */
+
+    public static final int RTT_CHANNEL_WIDTH_20      = 0;
+    public static final int RTT_CHANNEL_WIDTH_40      = 1;
+    public static final int RTT_CHANNEL_WIDTH_80      = 2;
+    public static final int RTT_CHANNEL_WIDTH_160     = 3;
+    public static final int RTT_CHANNEL_WIDTH_80P80   = 4;
+    public static final int RTT_CHANNEL_WIDTH_5       = 5;
+    public static final int RTT_CHANNEL_WIDTH_10      = 6;
+    public static final int RTT_CHANNEL_WIDTH_UNSPECIFIED = -1;
+
+    public static final int RTT_STATUS_SUCCESS                  = 0;
+    public static final int RTT_STATUS_FAILURE                  = 1;
+    public static final int RTT_STATUS_FAIL_NO_RSP              = 2;
+    public static final int RTT_STATUS_FAIL_REJECTED            = 3;
+    public static final int RTT_STATUS_FAIL_NOT_SCHEDULED_YET   = 4;
+    public static final int RTT_STATUS_FAIL_TM_TIMEOUT          = 5;
+    public static final int RTT_STATUS_FAIL_AP_ON_DIFF_CHANNEL  = 6;
+    public static final int RTT_STATUS_FAIL_NO_CAPABILITY       = 7;
+    public static final int RTT_STATUS_ABORTED                  = 8;
+
+    public static final int REASON_UNSPECIFIED              = -1;
+    public static final int REASON_INVALID_LISTENER         = -2;
+    public static final int REASON_INVALID_REQUEST          = -3;
+
+    public class Capabilities {
+        int supportedType;
+        int supportedPeerType;
+    }
+
+    public Capabilities getCapabilities() {
+        return new Capabilities();
+    }
+
+    /** specifies parameters for RTT request */
+    public static class RttParams {
+
+        /** type of device being ranged; one of RTT_PEER_TYPE_AP or RTT_PEER_TYPE_STA */
+        public int deviceType;
+
+        /** type of RTT being sought; one of RTT_TYPE_ONE_SIDED
+         *  RTT_TYPE_11_V or RTT_TYPE_11_MC or RTT_TYPE_UNSPECIFIED */
+        public int requestType;
+
+        /** mac address of the device being ranged */
+        public String bssid;
+
+        /** channel frequency that the device is on; optional */
+        public int frequency;
+
+        /** optional channel width. wider channels result in better accuracy,
+         *  but they take longer time, and even get aborted may times; use
+         *  RTT_CHANNEL_WIDTH_UNSPECIFIED if not specifying */
+        public int channelWidth;
+
+        /** number of samples to be taken */
+        public int num_samples;
+
+        /** number of retries if a sample fails */
+        public int num_retries;
+    }
+
+    /** specifies RTT results */
+    public static class RttResult {
+        /** mac address of the device being ranged */
+        public String bssid;
+
+        /** status of the request */
+        public int status;
+
+        /** timestamp of completion, in microsecond since boot */
+        public long ts;
+
+        /** average RSSI observed */
+        public int rssi;
+
+        /** RSSI spread (i.e. max - min) */
+        public int rssi_spread;
+
+        /** average transmit rate */
+        public int tx_rate;
+
+        /** average round trip time in nano second */
+        public long rtt_ns;
+
+        /** standard deviation observed in round trip time */
+        public long rtt_sd_ns;
+
+        /** spread (i.e. max - min) round trip time */
+        public long rtt_spread_ns;
+
+        /** average distance in centimeter, computed based on rtt_ns */
+        public long distance_cm;
+
+        /** standard deviation observed in distance */
+        public long distance_sd_cm;
+
+        /** spread (i.e. max - min) distance */
+        public long distance_spread_cm;
+    }
+
+    public static interface RttListener {
+        public void onSuccess(RttResult results[]);
+        public void onFailure(int reason, String description);
+        public void onAborted();
+    }
+
+    public void startRanging(RttParams params[], RttListener listener) {
+        validateChannel();
+        sAsyncChannel.sendMessage(CMD_OP_START_RANGING, 0, removeListener(listener), params);
+    }
+
+    public void stopRanging(RttListener listener) {
+        validateChannel();
+        sAsyncChannel.sendMessage(CMD_OP_STOP_RANGING, 0, removeListener(listener));
+    }
+
+    /* private methods */
+    public static final int CMD_OP_START_RANGING        = 0;
+    public static final int CMD_OP_STOP_RANGING         = 1;
+    public static final int CMD_OP_FAILED               = 2;
+    public static final int CMD_OP_SUCCEEDED            = 3;
+    public static final int CMD_OP_ABORTED              = 4;
+
+    private Context mContext;
+    private IRttManager mService;
+
+    private static final int INVALID_KEY = 0;
+    private static int sListenerKey = 1;
+
+    private static final SparseArray sListenerMap = new SparseArray();
+    private static final Object sListenerMapLock = new Object();
+
+    private static AsyncChannel sAsyncChannel;
+    private static CountDownLatch sConnected;
+
+    private static final Object sThreadRefLock = new Object();
+    private static int sThreadRefCount;
+    private static HandlerThread sHandlerThread;
+
+    /**
+     * Create a new WifiScanner instance.
+     * Applications will almost always want to use
+     * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
+     * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}.
+     * @param context the application context
+     * @param service the Binder interface
+     * @hide
+     */
+
+    public RttManager(Context context, IRttManager service) {
+        mContext = context;
+        mService = service;
+        init();
+    }
+
+    private void init() {
+        synchronized (sThreadRefLock) {
+            if (++sThreadRefCount == 1) {
+                Messenger messenger = null;
+                try {
+                    messenger = mService.getMessenger();
+                } catch (RemoteException e) {
+                    /* do nothing */
+                } catch (SecurityException e) {
+                    /* do nothing */
+                }
+
+                if (messenger == null) {
+                    sAsyncChannel = null;
+                    return;
+                }
+
+                sHandlerThread = new HandlerThread("WifiScanner");
+                sAsyncChannel = new AsyncChannel();
+                sConnected = new CountDownLatch(1);
+
+                sHandlerThread.start();
+                Handler handler = new ServiceHandler(sHandlerThread.getLooper());
+                sAsyncChannel.connect(mContext, handler, messenger);
+                try {
+                    sConnected.await();
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "interrupted wait at init");
+                }
+            }
+        }
+    }
+
+    private void validateChannel() {
+        if (sAsyncChannel == null) throw new IllegalStateException(
+                "No permission to access and change wifi or a bad initialization");
+    }
+
+    private static int putListener(Object listener) {
+        if (listener == null) return INVALID_KEY;
+        int key;
+        synchronized (sListenerMapLock) {
+            do {
+                key = sListenerKey++;
+            } while (key == INVALID_KEY);
+            sListenerMap.put(key, listener);
+        }
+        return key;
+    }
+
+    private static Object getListener(int key) {
+        if (key == INVALID_KEY) return null;
+        synchronized (sListenerMapLock) {
+            Object listener = sListenerMap.get(key);
+            return listener;
+        }
+    }
+
+    private static int getListenerKey(Object listener) {
+        if (listener == null) return INVALID_KEY;
+        synchronized (sListenerMapLock) {
+            int index = sListenerMap.indexOfValue(listener);
+            if (index == -1) {
+                return INVALID_KEY;
+            } else {
+                return sListenerMap.keyAt(index);
+            }
+        }
+    }
+
+    private static Object removeListener(int key) {
+        if (key == INVALID_KEY) return null;
+        synchronized (sListenerMapLock) {
+            Object listener = sListenerMap.get(key);
+            sListenerMap.remove(key);
+            return listener;
+        }
+    }
+
+    private static int removeListener(Object listener) {
+        int key = getListenerKey(listener);
+        if (key == INVALID_KEY) return key;
+        synchronized (sListenerMapLock) {
+            sListenerMap.remove(key);
+            return key;
+        }
+    }
+
+    private static class ServiceHandler extends Handler {
+        ServiceHandler(Looper looper) {
+            super(looper);
+        }
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
+                    if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+                        sAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
+                    } else {
+                        Log.e(TAG, "Failed to set up channel connection");
+                        // This will cause all further async API calls on the WifiManager
+                        // to fail and throw an exception
+                        sAsyncChannel = null;
+                    }
+                    sConnected.countDown();
+                    return;
+                case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
+                    return;
+                case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+                    Log.e(TAG, "Channel connection lost");
+                    // This will cause all further async API calls on the WifiManager
+                    // to fail and throw an exception
+                    sAsyncChannel = null;
+                    getLooper().quit();
+                    return;
+            }
+
+            Object listener = getListener(msg.arg2);
+            if (listener == null) {
+                if (DBG) Log.d(TAG, "invalid listener key = " + msg.arg2);
+                return;
+            } else {
+                if (DBG) Log.d(TAG, "listener key = " + msg.arg2);
+            }
+
+            switch (msg.what) {
+                /* ActionListeners grouped together */
+                case CMD_OP_SUCCEEDED :
+                    ((RttListener) listener).onSuccess((RttResult[])msg.obj);
+                    break;
+                case CMD_OP_FAILED :
+                    ((RttListener) listener).onFailure(msg.arg1, (String)msg.obj);
+                    removeListener(msg.arg2);
+                    break;
+                case CMD_OP_ABORTED :
+                    ((RttListener) listener).onAborted();
+                    removeListener(msg.arg2);
+                    break;
+                default:
+                    if (DBG) Log.d(TAG, "Ignoring message " + msg.what);
+                    return;
+            }
+        }
+    }
+
+}
+
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index f3294bb..4cdbc44 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -771,7 +771,13 @@
             }
 
             Object listener = getListener(msg.arg2);
-            if (DBG) Log.d(TAG, "listener key = " + msg.arg2);
+
+            if (listener == null) {
+                if (DBG) Log.d(TAG, "invalid listener key = " + msg.arg2);
+                return;
+            } else {
+                if (DBG) Log.d(TAG, "listener key = " + msg.arg2);
+            }
 
             switch (msg.what) {
                     /* ActionListeners grouped together */