package android.net.wifi; import android.annotation.SystemApi; import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.util.Log; import android.util.SparseArray; import com.android.internal.util.AsyncChannel; import com.android.internal.util.Protocol; 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_NOT_AVAILABLE = -2; public static final int REASON_INVALID_LISTENER = -3; public static final int REASON_INVALID_REQUEST = -4; public static final String DESCRIPTION_KEY = "android.net.wifi.RttManager.Description"; public class Capabilities { public int supportedType; public 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; } /** pseudo-private class used to parcel arguments */ public static class ParcelableRttParams implements Parcelable { public RttParams mParams[]; ParcelableRttParams(RttParams[] params) { mParams = params; } /** Implement the Parcelable interface {@hide} */ public int describeContents() { return 0; } /** Implement the Parcelable interface {@hide} */ public void writeToParcel(Parcel dest, int flags) { if (mParams != null) { dest.writeInt(mParams.length); for (RttParams params : mParams) { dest.writeInt(params.deviceType); dest.writeInt(params.requestType); dest.writeString(params.bssid); dest.writeInt(params.frequency); dest.writeInt(params.channelWidth); dest.writeInt(params.num_samples); dest.writeInt(params.num_retries); } } else { dest.writeInt(0); } } /** Implement the Parcelable interface {@hide} */ public static final Creator CREATOR = new Creator() { public ParcelableRttParams createFromParcel(Parcel in) { int num = in.readInt(); if (num == 0) { return new ParcelableRttParams(null); } RttParams params[] = new RttParams[num]; for (int i = 0; i < num; i++) { params[i] = new RttParams(); params[i].deviceType = in.readInt(); params[i].requestType = in.readInt(); params[i].bssid = in.readString(); params[i].frequency = in.readInt(); params[i].channelWidth = in.readInt(); params[i].num_samples = in.readInt(); params[i].num_retries = in.readInt(); } ParcelableRttParams parcelableParams = new ParcelableRttParams(params); return parcelableParams; } public ParcelableRttParams[] newArray(int size) { return new ParcelableRttParams[size]; } }; } /** specifies RTT results */ public static class RttResult { /** mac address of the device being ranged */ public String bssid; /** status of the request */ public int status; /** type of the request used */ public int requestType; /** 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 int distance_cm; /** standard deviation observed in distance */ public int distance_sd_cm; /** spread (i.e. max - min) distance */ public int distance_spread_cm; } /** pseudo-private class used to parcel results */ public static class ParcelableRttResults implements Parcelable { public RttResult mResults[]; public ParcelableRttResults(RttResult[] results) { mResults = results; } /** Implement the Parcelable interface {@hide} */ public int describeContents() { return 0; } /** Implement the Parcelable interface {@hide} */ public void writeToParcel(Parcel dest, int flags) { if (mResults != null) { dest.writeInt(mResults.length); for (RttResult result : mResults) { dest.writeString(result.bssid); dest.writeInt(result.status); dest.writeInt(result.requestType); dest.writeLong(result.ts); dest.writeInt(result.rssi); dest.writeInt(result.rssi_spread); dest.writeInt(result.tx_rate); dest.writeLong(result.rtt_ns); dest.writeLong(result.rtt_sd_ns); dest.writeLong(result.rtt_spread_ns); dest.writeInt(result.distance_cm); dest.writeInt(result.distance_sd_cm); dest.writeInt(result.distance_spread_cm); } } else { dest.writeInt(0); } } /** Implement the Parcelable interface {@hide} */ public static final Creator CREATOR = new Creator() { public ParcelableRttResults createFromParcel(Parcel in) { int num = in.readInt(); if (num == 0) { return new ParcelableRttResults(null); } RttResult results[] = new RttResult[num]; for (int i = 0; i < num; i++) { results[i] = new RttResult(); results[i].bssid = in.readString(); results[i].status = in.readInt(); results[i].requestType = in.readInt(); results[i].ts = in.readLong(); results[i].rssi = in.readInt(); results[i].rssi_spread = in.readInt(); results[i].tx_rate = in.readInt(); results[i].rtt_ns = in.readLong(); results[i].rtt_sd_ns = in.readLong(); results[i].rtt_spread_ns = in.readLong(); results[i].distance_cm = in.readInt(); results[i].distance_sd_cm = in.readInt(); results[i].distance_spread_cm = in.readInt(); } ParcelableRttResults parcelableResults = new ParcelableRttResults(results); return parcelableResults; } public ParcelableRttResults[] newArray(int size) { return new ParcelableRttResults[size]; } }; } 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(); ParcelableRttParams parcelableParams = new ParcelableRttParams(params); sAsyncChannel.sendMessage(CMD_OP_START_RANGING, 0, putListener(listener), parcelableParams); } public void stopRanging(RttListener listener) { validateChannel(); sAsyncChannel.sendMessage(CMD_OP_STOP_RANGING, 0, removeListener(listener)); } /* private methods */ public static final int BASE = Protocol.BASE_WIFI_RTT_MANAGER; public static final int CMD_OP_START_RANGING = BASE + 0; public static final int CMD_OP_STOP_RANGING = BASE + 1; public static final int CMD_OP_FAILED = BASE + 2; public static final int CMD_OP_SUCCEEDED = BASE + 3; public static final int CMD_OP_ABORTED = BASE + 4; 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_RTT_SERVICE Context.WIFI_RTT_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 { Log.d(TAG, "Get the messenger from " + mService); 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 : reportSuccess(listener, msg); removeListener(msg.arg2); break; case CMD_OP_FAILED : reportFailure(listener, msg); 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; } } void reportSuccess(Object listener, Message msg) { RttListener rttListener = (RttListener) listener; ParcelableRttResults parcelableResults = (ParcelableRttResults) msg.obj; ((RttListener) listener).onSuccess(parcelableResults.mResults); } void reportFailure(Object listener, Message msg) { RttListener rttListener = (RttListener) listener; Bundle bundle = (Bundle) msg.obj; ((RttListener) listener).onFailure(msg.arg1, bundle.getString(DESCRIPTION_KEY)); } } }