package com.android.server.wifi; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.wifi.RttManager; import android.net.wifi.WifiManager; 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.RemoteException; import android.util.Log; import android.net.wifi.IRttManager; import android.util.Slog; import com.android.internal.util.AsyncChannel; import com.android.internal.util.Protocol; import com.android.internal.util.StateMachine; import com.android.internal.util.State; import com.android.server.SystemService; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.Queue; import android.Manifest; public final class RttService extends SystemService { public static final boolean DBG = true; class RttServiceImpl extends IRttManager.Stub { @Override public Messenger getMessenger() { return new Messenger(mClientHandler); } private class ClientHandler extends Handler { ClientHandler(android.os.Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { if (DBG) Log.d(TAG, "ClientHandler got" + msg); switch (msg.what) { case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { AsyncChannel c = (AsyncChannel) msg.obj; if (DBG) Slog.d(TAG, "New client listening to asynchronous messages: " + msg.replyTo); ClientInfo cInfo = new ClientInfo(c, msg.replyTo); mClients.put(msg.replyTo, cInfo); } else { Slog.e(TAG, "Client connection failure, error=" + msg.arg1); } return; case AsyncChannel.CMD_CHANNEL_DISCONNECTED: if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) { Slog.e(TAG, "Send failed, client connection lost"); } else { if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1); } if (DBG) Slog.d(TAG, "closing client " + msg.replyTo); ClientInfo ci = mClients.remove(msg.replyTo); if (ci != null) ci.cleanup(); return; case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: AsyncChannel ac = new AsyncChannel(); ac.connect(mContext, this, msg.replyTo); return; } ClientInfo ci = mClients.get(msg.replyTo); if (ci == null) { Slog.e(TAG, "Could not find client info for message " + msg.replyTo); replyFailed(msg, RttManager.REASON_INVALID_LISTENER, "Could not find listener"); return; } int validCommands[] = { RttManager.CMD_OP_START_RANGING, RttManager.CMD_OP_STOP_RANGING }; for(int cmd : validCommands) { if (cmd == msg.what) { mStateMachine.sendMessage(Message.obtain(msg)); return; } } replyFailed(msg, RttManager.REASON_INVALID_REQUEST, "Invalid request"); } } private Context mContext; private RttStateMachine mStateMachine; private ClientHandler mClientHandler; RttServiceImpl() { } RttServiceImpl(Context context) { mContext = context; } public void startService(Context context) { mContext = context; HandlerThread thread = new HandlerThread("WifiRttService"); thread.start(); mClientHandler = new ClientHandler(thread.getLooper()); mStateMachine = new RttStateMachine(thread.getLooper()); mContext.registerReceiver( new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int state = intent.getIntExtra( WifiManager.EXTRA_SCAN_AVAILABLE, WifiManager.WIFI_STATE_DISABLED); if (DBG) Log.d(TAG, "SCAN_AVAILABLE : " + state); if (state == WifiManager.WIFI_STATE_ENABLED) { mStateMachine.sendMessage(CMD_DRIVER_LOADED); } else if (state == WifiManager.WIFI_STATE_DISABLED) { mStateMachine.sendMessage(CMD_DRIVER_UNLOADED); } } }, new IntentFilter(WifiManager.WIFI_SCAN_AVAILABLE)); mStateMachine.start(); } private class RttRequest { Integer key; ClientInfo ci; RttManager.RttParams[] params; @Override public String toString() { String str = getClass().getName() + "@" + Integer.toHexString(hashCode()); if(this.key != null) { return str + " key: " + this.key; } else { return str + " key: " + " , null"; } } } private class ClientInfo { private final AsyncChannel mChannel; private final Messenger mMessenger; HashMap<Integer, RttRequest> mRequests = new HashMap<Integer, RttRequest>(); ClientInfo(AsyncChannel c, Messenger m) { mChannel = c; mMessenger = m; } boolean addRttRequest(int key, RttManager.ParcelableRttParams parcelableParams) { if (parcelableParams == null) { return false; } RttManager.RttParams params[] = parcelableParams.mParams; RttRequest request = new RttRequest(); request.key = key; request.ci = this; request.params = params; mRequests.put(key, request); mRequestQueue.add(request); return true; } void removeRttRequest(int key) { mRequests.remove(key); } void reportResult(RttRequest request, RttManager.RttResult[] results) { RttManager.ParcelableRttResults parcelableResults = new RttManager.ParcelableRttResults(results); mChannel.sendMessage(RttManager.CMD_OP_SUCCEEDED, 0, request.key, parcelableResults); mRequests.remove(request.key); } void reportFailed(RttRequest request, int reason, String description) { reportFailed(request.key, reason, description); } void reportFailed(int key, int reason, String description) { Bundle bundle = new Bundle(); bundle.putString(RttManager.DESCRIPTION_KEY, description); mChannel.sendMessage(RttManager.CMD_OP_FAILED, key, reason, bundle); mRequests.remove(key); } void reportAborted(int key) { mChannel.sendMessage(RttManager.CMD_OP_ABORTED, 0, key); //All Queued RTT request will be cleaned cleanup(); } void cleanup() { mRequests.clear(); mRequestQueue.clear(); } } private Queue<RttRequest> mRequestQueue = new LinkedList<RttRequest>(); private HashMap<Messenger, ClientInfo> mClients = new HashMap<Messenger, ClientInfo>(4); private static final int BASE = Protocol.BASE_WIFI_RTT_SERVICE; private static final int CMD_DRIVER_LOADED = BASE + 0; private static final int CMD_DRIVER_UNLOADED = BASE + 1; private static final int CMD_ISSUE_NEXT_REQUEST = BASE + 2; private static final int CMD_RTT_RESPONSE = BASE + 3; class RttStateMachine extends StateMachine { DefaultState mDefaultState = new DefaultState(); EnabledState mEnabledState = new EnabledState(); RequestPendingState mRequestPendingState = new RequestPendingState(); RttStateMachine(Looper looper) { super("RttStateMachine", looper); addState(mDefaultState); addState(mEnabledState); addState(mRequestPendingState, mEnabledState); setInitialState(mDefaultState); } class DefaultState extends State { @Override public boolean processMessage(Message msg) { if (DBG) Log.d(TAG, "DefaultState got" + msg); switch (msg.what) { case CMD_DRIVER_LOADED: transitionTo(mEnabledState); break; case CMD_ISSUE_NEXT_REQUEST: deferMessage(msg); break; case RttManager.CMD_OP_START_RANGING: replyFailed(msg, RttManager.REASON_NOT_AVAILABLE, "Try later"); break; case RttManager.CMD_OP_STOP_RANGING: return HANDLED; default: return NOT_HANDLED; } return HANDLED; } } class EnabledState extends State { @Override public boolean processMessage(Message msg) { if (DBG) Log.d(TAG, "EnabledState got" + msg); ClientInfo ci = mClients.get(msg.replyTo); switch (msg.what) { case CMD_DRIVER_UNLOADED: transitionTo(mDefaultState); break; case CMD_ISSUE_NEXT_REQUEST: deferMessage(msg); transitionTo(mRequestPendingState); break; case RttManager.CMD_OP_START_RANGING: { //check permission if(DBG) Log.d(TAG, "UID is: " + msg.sendingUid); if (!enforcePermissionCheck(msg)) { Log.e(TAG, "UID: " + msg.sendingUid + " has no" + " LOCATION_HARDWARE Permission"); break; } RttManager.ParcelableRttParams params = (RttManager.ParcelableRttParams)msg.obj; if (params == null) { replyFailed(msg, RttManager.REASON_INVALID_REQUEST, "No params"); } else if (ci.addRttRequest(msg.arg2, params) == false) { replyFailed(msg, RttManager.REASON_INVALID_REQUEST, "Unspecified"); } else { sendMessage(CMD_ISSUE_NEXT_REQUEST); } } break; case RttManager.CMD_OP_STOP_RANGING: if(!enforcePermissionCheck(msg)) { break; } for (Iterator<RttRequest> it = mRequestQueue.iterator(); it.hasNext(); ) { RttRequest request = it.next(); if (request.key == msg.arg2) { if (DBG) Log.d(TAG, "Cancelling not-yet-scheduled RTT"); mRequestQueue.remove(request); request.ci.reportAborted(request.key); break; } } break; default: return NOT_HANDLED; } return HANDLED; } } class RequestPendingState extends State { RttRequest mOutstandingRequest; @Override public boolean processMessage(Message msg) { if (DBG) Log.d(TAG, "RequestPendingState got" + msg); switch (msg.what) { case CMD_DRIVER_UNLOADED: if (mOutstandingRequest != null) { WifiNative.cancelRtt(mOutstandingRequest.params); if (DBG) Log.d(TAG, "abort key: " + mOutstandingRequest.key); mOutstandingRequest.ci.reportAborted(mOutstandingRequest.key); mOutstandingRequest = null; } transitionTo(mDefaultState); break; case CMD_ISSUE_NEXT_REQUEST: if (mOutstandingRequest == null) { mOutstandingRequest = issueNextRequest(); if (mOutstandingRequest == null) { transitionTo(mEnabledState); } if(mOutstandingRequest != null) { if (DBG) Log.d(TAG, "new mOutstandingRequest.key is: " + mOutstandingRequest.key); } else { if (DBG) Log.d(TAG, "CMD_ISSUE_NEXT_REQUEST: mOutstandingRequest =null "); } } else { /* just wait; we'll issue next request after * current one is finished */ if (DBG) Log.d(TAG, "Current mOutstandingRequest.key is: " + mOutstandingRequest.key); if (DBG) Log.d(TAG, "Ignoring CMD_ISSUE_NEXT_REQUEST"); } break; case CMD_RTT_RESPONSE: if (DBG) Log.d(TAG, "Received an RTT response from: " + msg.arg2); mOutstandingRequest.ci.reportResult( mOutstandingRequest, (RttManager.RttResult[])msg.obj); mOutstandingRequest = null; sendMessage(CMD_ISSUE_NEXT_REQUEST); break; case RttManager.CMD_OP_STOP_RANGING: if(!enforcePermissionCheck(msg)) { Log.e(TAG, "UID: " + msg.sendingUid + " has no " + "LOCATION_HARDWARE Permission"); break; } if (mOutstandingRequest != null && msg.arg2 == mOutstandingRequest.key) { if (DBG) Log.d(TAG, "Cancelling ongoing RTT of: " + msg.arg2); WifiNative.cancelRtt(mOutstandingRequest.params); mOutstandingRequest.ci.reportAborted(mOutstandingRequest.key); mOutstandingRequest = null; sendMessage(CMD_ISSUE_NEXT_REQUEST); } else { /* Let EnabledState handle this */ return NOT_HANDLED; } break; default: return NOT_HANDLED; } return HANDLED; } } } void replySucceeded(Message msg, Object obj) { if (msg.replyTo != null) { Message reply = Message.obtain(); reply.what = RttManager.CMD_OP_SUCCEEDED; reply.arg2 = msg.arg2; reply.obj = obj; try { msg.replyTo.send(reply); } catch (RemoteException e) { // There's not much we can do if reply can't be sent! } } else { // locally generated message; doesn't need a reply! } } void replyFailed(Message msg, int reason, String description) { Message reply = Message.obtain(); reply.what = RttManager.CMD_OP_FAILED; reply.arg1 = reason; reply.arg2 = msg.arg2; Bundle bundle = new Bundle(); bundle.putString(RttManager.DESCRIPTION_KEY, description); reply.obj = bundle; try { msg.replyTo.send(reply); } catch (RemoteException e) { // There's not much we can do if reply can't be sent! } } boolean enforcePermissionCheck(Message msg) { try { mContext.enforcePermission(Manifest.permission.LOCATION_HARDWARE, -1, msg.sendingUid, "LocationRTT"); } catch (SecurityException e) { replyFailed(msg,RttManager.REASON_PERMISSION_DENIED, "No params"); return false; } return true; } private WifiNative.RttEventHandler mEventHandler = new WifiNative.RttEventHandler() { @Override public void onRttResults(RttManager.RttResult[] result) { mStateMachine.sendMessage(CMD_RTT_RESPONSE, result); } }; RttRequest issueNextRequest() { RttRequest request = null; while (mRequestQueue.isEmpty() == false) { request = mRequestQueue.remove(); if(request != null) { if (WifiNative.requestRtt(request.params, mEventHandler)) { if (DBG) Log.d(TAG, "Issued next RTT request with key: " + request.key); return request; } else { Log.e(TAG, "Fail to issue key at native layer"); request.ci.reportFailed(request, RttManager.REASON_UNSPECIFIED, "Failed to start"); } } } /* all requests exhausted */ if (DBG) Log.d(TAG, "No more requests left"); return null; } @Override public RttManager.RttCapabilities getRttCapabilities() { return WifiNative.getRttCapabilities(); } } private static final String TAG = "RttService"; RttServiceImpl mImpl; public RttService(Context context) { super(context); Log.i(TAG, "Creating " + Context.WIFI_RTT_SERVICE); } @Override public void onStart() { mImpl = new RttServiceImpl(getContext()); Log.i(TAG, "Starting " + Context.WIFI_RTT_SERVICE); publishBinderService(Context.WIFI_RTT_SERVICE, mImpl); } @Override public void onBootPhase(int phase) { if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { Log.i(TAG, "Registering " + Context.WIFI_RTT_SERVICE); if (mImpl == null) { mImpl = new RttServiceImpl(getContext()); } mImpl.startService(getContext()); } } }