1package com.android.server.wifi;
2
3import android.Manifest;
4import android.content.BroadcastReceiver;
5import android.content.Context;
6import android.content.Intent;
7import android.content.IntentFilter;
8import android.net.wifi.IRttManager;
9import android.net.wifi.RttManager;
10import android.net.wifi.RttManager.ResponderConfig;
11import android.net.wifi.WifiManager;
12import android.os.Bundle;
13import android.os.Handler;
14import android.os.HandlerThread;
15import android.os.Looper;
16import android.os.Message;
17import android.os.Messenger;
18import android.os.RemoteException;
19import android.util.Log;
20import android.util.Slog;
21
22import com.android.internal.util.AsyncChannel;
23import com.android.internal.util.Protocol;
24import com.android.internal.util.State;
25import com.android.internal.util.StateMachine;
26import com.android.server.SystemService;
27
28import java.util.HashMap;
29import java.util.HashSet;
30import java.util.Iterator;
31import java.util.LinkedList;
32import java.util.Queue;
33import java.util.Set;
34
35public final class RttService extends SystemService {
36
37    public static final boolean DBG = true;
38
39    static class RttServiceImpl extends IRttManager.Stub {
40
41        @Override
42        public Messenger getMessenger() {
43            return new Messenger(mClientHandler);
44        }
45
46        private class ClientHandler extends Handler {
47
48            ClientHandler(android.os.Looper looper) {
49                super(looper);
50            }
51
52            @Override
53            public void handleMessage(Message msg) {
54
55                if (DBG) {
56                    Log.d(TAG, "ClientHandler got" + msg + " what = " + getDescription(msg.what));
57                }
58
59                switch (msg.what) {
60
61                    case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
62                        if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) {
63                            Slog.e(TAG, "Send failed, client connection lost");
64                        } else {
65                            if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
66                        }
67                        if (DBG) Slog.d(TAG, "closing client " + msg.replyTo);
68                        ClientInfo ci = mClients.remove(msg.replyTo);
69                        if (ci != null) ci.cleanup();
70                        return;
71                    case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
72                        AsyncChannel ac = new AsyncChannel();
73                        ac.connected(mContext, this, msg.replyTo);
74                        ClientInfo client = new ClientInfo(ac, msg.replyTo);
75                        mClients.put(msg.replyTo, client);
76                        ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
77                                AsyncChannel.STATUS_SUCCESSFUL);
78                        return;
79                }
80
81                ClientInfo ci = mClients.get(msg.replyTo);
82                if (ci == null) {
83                    Slog.e(TAG, "Could not find client info for message " + msg.replyTo);
84                    replyFailed(msg, RttManager.REASON_INVALID_LISTENER, "Could not find listener");
85                    return;
86                }
87                if (!enforcePermissionCheck(msg)) {
88                    replyFailed(msg, RttManager.REASON_PERMISSION_DENIED,
89                            "Client doesn't have LOCATION_HARDWARE permission");
90                    return;
91                }
92                final int validCommands[] = {
93                        RttManager.CMD_OP_START_RANGING,
94                        RttManager.CMD_OP_STOP_RANGING,
95                        RttManager.CMD_OP_ENABLE_RESPONDER,
96                        RttManager.CMD_OP_DISABLE_RESPONDER,
97                        };
98
99                for (int cmd : validCommands) {
100                    if (cmd == msg.what) {
101                        mStateMachine.sendMessage(Message.obtain(msg));
102                        return;
103                    }
104                }
105
106                replyFailed(msg, RttManager.REASON_INVALID_REQUEST, "Invalid request");
107            }
108
109            private String getDescription(int what) {
110                switch(what) {
111                    case RttManager.CMD_OP_ENABLE_RESPONDER:
112                        return "CMD_OP_ENABLE_RESPONDER";
113                    case RttManager.CMD_OP_DISABLE_RESPONDER:
114                        return "CMD_OP_DISABLE_RESPONDER";
115                    default:
116                        return "CMD_UNKNOWN";
117                }
118            }
119        }
120
121        private final WifiNative mWifiNative;
122        private final Context mContext;
123        private final Looper mLooper;
124        private RttStateMachine mStateMachine;
125        private ClientHandler mClientHandler;
126
127        RttServiceImpl(Context context, Looper looper) {
128            mContext = context;
129            mWifiNative = WifiNative.getWlanNativeInterface();
130            mLooper = looper;
131        }
132
133        public void startService() {
134            mClientHandler = new ClientHandler(mLooper);
135            mStateMachine = new RttStateMachine(mLooper);
136
137            mContext.registerReceiver(
138                    new BroadcastReceiver() {
139                        @Override
140                        public void onReceive(Context context, Intent intent) {
141                            int state = intent.getIntExtra(
142                                    WifiManager.EXTRA_SCAN_AVAILABLE, WifiManager.WIFI_STATE_DISABLED);
143                            if (DBG) Log.d(TAG, "SCAN_AVAILABLE : " + state);
144                            if (state == WifiManager.WIFI_STATE_ENABLED) {
145                                mStateMachine.sendMessage(CMD_DRIVER_LOADED);
146                            } else if (state == WifiManager.WIFI_STATE_DISABLED) {
147                                mStateMachine.sendMessage(CMD_DRIVER_UNLOADED);
148                            }
149                        }
150                    }, new IntentFilter(WifiManager.WIFI_SCAN_AVAILABLE));
151
152            mStateMachine.start();
153        }
154
155        private class RttRequest {
156            Integer key;
157            ClientInfo ci;
158            RttManager.RttParams[] params;
159
160            @Override
161            public String toString() {
162                String str = getClass().getName() + "@" + Integer.toHexString(hashCode());
163                if(this.key != null) {
164                    return str + " key: " + this.key;
165                } else {
166                    return str + " key: " + " , null";
167                }
168            }
169        }
170
171        private class ClientInfo {
172            private final AsyncChannel mChannel;
173            private final Messenger mMessenger;
174            HashMap<Integer, RttRequest> mRequests = new HashMap<Integer,
175                    RttRequest>();
176            // Client keys of all outstanding responders.
177            Set<Integer> mResponderRequests = new HashSet<>();
178
179            ClientInfo(AsyncChannel c, Messenger m) {
180                mChannel = c;
181                mMessenger = m;
182            }
183
184            void addResponderRequest(int key) {
185                mResponderRequests.add(key);
186            }
187
188            void removeResponderRequest(int key) {
189                mResponderRequests.remove(key);
190            }
191
192            boolean addRttRequest(int key, RttManager.ParcelableRttParams parcelableParams) {
193                if (parcelableParams == null) {
194                    return false;
195                }
196
197                RttManager.RttParams params[] = parcelableParams.mParams;
198
199                RttRequest request = new RttRequest();
200                request.key = key;
201                request.ci = this;
202                request.params = params;
203                mRequests.put(key, request);
204                mRequestQueue.add(request);
205                return true;
206            }
207
208            void removeRttRequest(int key) {
209                mRequests.remove(key);
210            }
211
212            void reportResponderEnableSucceed(int key, ResponderConfig config) {
213                mChannel.sendMessage(RttManager.CMD_OP_ENALBE_RESPONDER_SUCCEEDED, 0, key, config);
214            }
215
216            void reportResponderEnableFailed(int key, int reason) {
217                mChannel.sendMessage(RttManager.CMD_OP_ENALBE_RESPONDER_FAILED, reason, key);
218                mResponderRequests.remove(key);
219            }
220
221            void reportResult(RttRequest request, RttManager.RttResult[] results) {
222                RttManager.ParcelableRttResults parcelableResults =
223                        new RttManager.ParcelableRttResults(results);
224
225                mChannel.sendMessage(RttManager.CMD_OP_SUCCEEDED,
226                        0, request.key, parcelableResults);
227                mRequests.remove(request.key);
228            }
229
230            void reportFailed(RttRequest request, int reason, String description) {
231                reportFailed(request.key, reason, description);
232            }
233
234            void reportFailed(int key, int reason, String description) {
235                Bundle bundle = new Bundle();
236                bundle.putString(RttManager.DESCRIPTION_KEY, description);
237                mChannel.sendMessage(RttManager.CMD_OP_FAILED, key, reason, bundle);
238                mRequests.remove(key);
239            }
240
241            void reportAborted(int key) {
242                mChannel.sendMessage(RttManager.CMD_OP_ABORTED, 0, key);
243                //All Queued RTT request will be cleaned
244                cleanup();
245            }
246
247            void cleanup() {
248                mRequests.clear();
249                mRequestQueue.clear();
250                // When client is lost, clean up responder requests and send disable responder
251                // message to RttStateMachine.
252                mResponderRequests.clear();
253                mStateMachine.sendMessage(RttManager.CMD_OP_DISABLE_RESPONDER);
254            }
255        }
256
257        private Queue<RttRequest> mRequestQueue = new LinkedList<RttRequest>();
258        private HashMap<Messenger, ClientInfo> mClients = new HashMap<Messenger, ClientInfo>(4);
259
260        private static final int BASE = Protocol.BASE_WIFI_RTT_SERVICE;
261
262        private static final int CMD_DRIVER_LOADED                       = BASE + 0;
263        private static final int CMD_DRIVER_UNLOADED                     = BASE + 1;
264        private static final int CMD_ISSUE_NEXT_REQUEST                  = BASE + 2;
265        private static final int CMD_RTT_RESPONSE                        = BASE + 3;
266
267        // Maximum duration for responder role.
268        private static final int MAX_RESPONDER_DURATION_SECONDS = 60 * 10;
269
270        class RttStateMachine extends StateMachine {
271
272            DefaultState mDefaultState = new DefaultState();
273            EnabledState mEnabledState = new EnabledState();
274            InitiatorEnabledState mInitiatorEnabledState = new InitiatorEnabledState();
275            ResponderEnabledState mResponderEnabledState = new ResponderEnabledState();
276            ResponderConfig mResponderConfig;
277
278            RttStateMachine(Looper looper) {
279                super("RttStateMachine", looper);
280
281                // CHECKSTYLE:OFF IndentationCheck
282                addState(mDefaultState);
283                addState(mEnabledState);
284                    addState(mInitiatorEnabledState, mEnabledState);
285                    addState(mResponderEnabledState, mEnabledState);
286                // CHECKSTYLE:ON IndentationCheck
287
288                setInitialState(mDefaultState);
289            }
290
291            class DefaultState extends State {
292                @Override
293                public boolean processMessage(Message msg) {
294                    if (DBG) Log.d(TAG, "DefaultState got" + msg);
295                    switch (msg.what) {
296                        case CMD_DRIVER_LOADED:
297                            transitionTo(mEnabledState);
298                            break;
299                        case CMD_ISSUE_NEXT_REQUEST:
300                            deferMessage(msg);
301                            break;
302                        case RttManager.CMD_OP_START_RANGING:
303                            replyFailed(msg, RttManager.REASON_NOT_AVAILABLE, "Try later");
304                            break;
305                        case RttManager.CMD_OP_STOP_RANGING:
306                            return HANDLED;
307                        case RttManager.CMD_OP_ENABLE_RESPONDER:
308                            ClientInfo client = mClients.get(msg.replyTo);
309                            if (client == null) {
310                                Log.e(TAG, "client not connected yet!");
311                                break;
312                            }
313                            int key = msg.arg2;
314                            client.reportResponderEnableFailed(key,
315                                    RttManager.REASON_NOT_AVAILABLE);
316                            break;
317                        case RttManager.CMD_OP_DISABLE_RESPONDER:
318                            return HANDLED;
319                        default:
320                            return NOT_HANDLED;
321                    }
322                    return HANDLED;
323                }
324            }
325
326            class EnabledState extends State {
327                @Override
328                public boolean processMessage(Message msg) {
329                    if (DBG) Log.d(TAG, "EnabledState got" + msg);
330                    ClientInfo ci = mClients.get(msg.replyTo);
331
332                    switch (msg.what) {
333                        case CMD_DRIVER_UNLOADED:
334                            transitionTo(mDefaultState);
335                            break;
336                        case CMD_ISSUE_NEXT_REQUEST:
337                            deferMessage(msg);
338                            transitionTo(mInitiatorEnabledState);
339                            break;
340                        case RttManager.CMD_OP_START_RANGING: {
341                            RttManager.ParcelableRttParams params =
342                                    (RttManager.ParcelableRttParams)msg.obj;
343                            if (params == null || params.mParams == null
344                                    || params.mParams.length == 0) {
345                                replyFailed(msg,
346                                        RttManager.REASON_INVALID_REQUEST, "No params");
347                            } else if (ci.addRttRequest(msg.arg2, params) == false) {
348                                replyFailed(msg,
349                                        RttManager.REASON_INVALID_REQUEST, "Unspecified");
350                            } else {
351                                sendMessage(CMD_ISSUE_NEXT_REQUEST);
352                            }
353                        }
354                            break;
355                        case RttManager.CMD_OP_STOP_RANGING:
356                            for (Iterator<RttRequest> it = mRequestQueue.iterator();
357                                    it.hasNext(); ) {
358                                RttRequest request = it.next();
359                                if (request.key == msg.arg2) {
360                                    if (DBG) Log.d(TAG, "Cancelling not-yet-scheduled RTT");
361                                    mRequestQueue.remove(request);
362                                    request.ci.reportAborted(request.key);
363                                    break;
364                                }
365                            }
366                            break;
367                        case RttManager.CMD_OP_ENABLE_RESPONDER:
368                            int key = msg.arg2;
369                            mResponderConfig =
370                                    mWifiNative.enableRttResponder(MAX_RESPONDER_DURATION_SECONDS);
371                            if (DBG) Log.d(TAG, "mWifiNative.enableRttResponder called");
372
373                            if (mResponderConfig != null) {
374                                // TODO: remove once mac address is added when enabling responder.
375                                mResponderConfig.macAddress = mWifiNative.getMacAddress();
376                                ci.addResponderRequest(key);
377                                ci.reportResponderEnableSucceed(key, mResponderConfig);
378                                transitionTo(mResponderEnabledState);
379                            } else {
380                                Log.e(TAG, "enable responder failed");
381                                ci.reportResponderEnableFailed(key, RttManager.REASON_UNSPECIFIED);
382                            }
383                            break;
384                        case RttManager.CMD_OP_DISABLE_RESPONDER:
385                            break;
386                        default:
387                            return NOT_HANDLED;
388                    }
389                    return HANDLED;
390                }
391            }
392
393            class InitiatorEnabledState extends State {
394                RttRequest mOutstandingRequest;
395                @Override
396                public boolean processMessage(Message msg) {
397                    if (DBG) Log.d(TAG, "RequestPendingState got" + msg);
398                    switch (msg.what) {
399                        case CMD_DRIVER_UNLOADED:
400                            if (mOutstandingRequest != null) {
401                                mWifiNative.cancelRtt(mOutstandingRequest.params);
402                                if (DBG) Log.d(TAG, "abort key: " + mOutstandingRequest.key);
403                                mOutstandingRequest.ci.reportAborted(mOutstandingRequest.key);
404                                mOutstandingRequest = null;
405                            }
406                            transitionTo(mDefaultState);
407                            break;
408                        case CMD_ISSUE_NEXT_REQUEST:
409                            if (mOutstandingRequest == null) {
410                                mOutstandingRequest = issueNextRequest();
411                                if (mOutstandingRequest == null) {
412                                    transitionTo(mEnabledState);
413                                }
414                                if(mOutstandingRequest != null) {
415                                    if (DBG) Log.d(TAG, "new mOutstandingRequest.key is: " +
416                                            mOutstandingRequest.key);
417                                } else {
418                                    if (DBG) Log.d(TAG,
419                                            "CMD_ISSUE_NEXT_REQUEST: mOutstandingRequest =null ");
420                                }
421                            } else {
422                                /* just wait; we'll issue next request after
423                                 * current one is finished */
424                                 if (DBG) Log.d(TAG, "Current mOutstandingRequest.key is: " +
425                                         mOutstandingRequest.key);
426                                 if (DBG) Log.d(TAG, "Ignoring CMD_ISSUE_NEXT_REQUEST");
427                            }
428                            break;
429                        case CMD_RTT_RESPONSE:
430                            if (DBG) Log.d(TAG, "Received an RTT response from: " + msg.arg2);
431                            mOutstandingRequest.ci.reportResult(
432                                    mOutstandingRequest, (RttManager.RttResult[])msg.obj);
433                            mOutstandingRequest = null;
434                            sendMessage(CMD_ISSUE_NEXT_REQUEST);
435                            break;
436                        case RttManager.CMD_OP_STOP_RANGING:
437                            if (mOutstandingRequest != null
438                                    && msg.arg2 == mOutstandingRequest.key) {
439                                if (DBG) Log.d(TAG, "Cancelling ongoing RTT of: " + msg.arg2);
440                                mWifiNative.cancelRtt(mOutstandingRequest.params);
441                                mOutstandingRequest.ci.reportAborted(mOutstandingRequest.key);
442                                mOutstandingRequest = null;
443                                sendMessage(CMD_ISSUE_NEXT_REQUEST);
444                            } else {
445                                /* Let EnabledState handle this */
446                                return NOT_HANDLED;
447                            }
448                            break;
449                        default:
450                            return NOT_HANDLED;
451                    }
452                    return HANDLED;
453                }
454            }
455
456            // Check if there are still outstanding responder requests from any client.
457            private boolean hasOutstandingReponderRequests() {
458                for (ClientInfo client : mClients.values()) {
459                    if (!client.mResponderRequests.isEmpty()) {
460                        return true;
461                    }
462                }
463                return false;
464            }
465
466            /**
467             * Representing an outstanding RTT responder state.
468             */
469            class ResponderEnabledState extends State {
470                @Override
471                public boolean processMessage(Message msg) {
472                    if (DBG) Log.d(TAG, "ResponderEnabledState got " + msg);
473                    ClientInfo ci = mClients.get(msg.replyTo);
474                    int key = msg.arg2;
475                    switch(msg.what) {
476                        case RttManager.CMD_OP_ENABLE_RESPONDER:
477                            // Responder already enabled, simply return the responder config.
478                            ci.addResponderRequest(key);
479                            ci.reportResponderEnableSucceed(key, mResponderConfig);
480                            return HANDLED;
481                        case RttManager.CMD_OP_DISABLE_RESPONDER:
482                            if (ci != null) {
483                                ci.removeResponderRequest(key);
484                            }
485                            // Only disable responder when there are no outstanding clients.
486                            if (!hasOutstandingReponderRequests()) {
487                                if (!mWifiNative.disableRttResponder()) {
488                                    Log.e(TAG, "disable responder failed");
489                                }
490                                if (DBG) Log.d(TAG, "mWifiNative.disableRttResponder called");
491                                transitionTo(mEnabledState);
492                            }
493                            return HANDLED;
494                        case RttManager.CMD_OP_START_RANGING:
495                        case RttManager.CMD_OP_STOP_RANGING:  // fall through
496                            // Concurrent initiator and responder role is not supported.
497                            replyFailed(msg,
498                                    RttManager.REASON_INITIATOR_NOT_ALLOWED_WHEN_RESPONDER_ON,
499                                    "Initiator not allowed when responder is turned on");
500                            return HANDLED;
501                        default:
502                            return NOT_HANDLED;
503                    }
504                }
505            }
506        }
507
508        void replySucceeded(Message msg, Object obj) {
509            if (msg.replyTo != null) {
510                Message reply = Message.obtain();
511                reply.what = RttManager.CMD_OP_SUCCEEDED;
512                reply.arg2 = msg.arg2;
513                reply.obj = obj;
514                try {
515                    msg.replyTo.send(reply);
516                } catch (RemoteException e) {
517                    // There's not much we can do if reply can't be sent!
518                }
519            } else {
520                // locally generated message; doesn't need a reply!
521            }
522        }
523
524        void replyFailed(Message msg, int reason, String description) {
525            Message reply = Message.obtain();
526            reply.what = RttManager.CMD_OP_FAILED;
527            reply.arg1 = reason;
528            reply.arg2 = msg.arg2;
529
530            Bundle bundle = new Bundle();
531            bundle.putString(RttManager.DESCRIPTION_KEY, description);
532            reply.obj = bundle;
533
534            try {
535                if (msg.replyTo != null) {
536                    msg.replyTo.send(reply);
537                }
538            } catch (RemoteException e) {
539                // There's not much we can do if reply can't be sent!
540            }
541        }
542
543        boolean enforcePermissionCheck(Message msg) {
544            try {
545                mContext.enforcePermission(Manifest.permission.LOCATION_HARDWARE,
546                         -1, msg.sendingUid, "LocationRTT");
547            } catch (SecurityException e) {
548                Log.e(TAG, "UID: " + msg.sendingUid + " has no LOCATION_HARDWARE Permission");
549                return false;
550            }
551            return true;
552        }
553
554        private WifiNative.RttEventHandler mEventHandler = new WifiNative.RttEventHandler() {
555            @Override
556            public void onRttResults(RttManager.RttResult[] result) {
557                mStateMachine.sendMessage(CMD_RTT_RESPONSE, result);
558            }
559        };
560
561        RttRequest issueNextRequest() {
562            RttRequest request = null;
563            while (mRequestQueue.isEmpty() == false) {
564                request = mRequestQueue.remove();
565                if(request !=  null) {
566                    if (mWifiNative.requestRtt(request.params, mEventHandler)) {
567                        if (DBG) Log.d(TAG, "Issued next RTT request with key: " + request.key);
568                        return request;
569                    } else {
570                        Log.e(TAG, "Fail to issue key at native layer");
571                        request.ci.reportFailed(request,
572                                RttManager.REASON_UNSPECIFIED, "Failed to start");
573                    }
574                }
575            }
576
577            /* all requests exhausted */
578            if (DBG) Log.d(TAG, "No more requests left");
579            return null;
580        }
581        @Override
582        public RttManager.RttCapabilities getRttCapabilities() {
583            return mWifiNative.getRttCapabilities();
584        }
585    }
586
587    private static final String TAG = "RttService";
588    RttServiceImpl mImpl;
589    private final HandlerThread mHandlerThread;
590
591    public RttService(Context context) {
592        super(context);
593        mHandlerThread = new HandlerThread("WifiRttService");
594        mHandlerThread.start();
595        Log.i(TAG, "Creating " + Context.WIFI_RTT_SERVICE);
596    }
597
598    @Override
599    public void onStart() {
600        mImpl = new RttServiceImpl(getContext(), mHandlerThread.getLooper());
601
602        Log.i(TAG, "Starting " + Context.WIFI_RTT_SERVICE);
603        publishBinderService(Context.WIFI_RTT_SERVICE, mImpl);
604    }
605
606    @Override
607    public void onBootPhase(int phase) {
608        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
609            Log.i(TAG, "Registering " + Context.WIFI_RTT_SERVICE);
610            if (mImpl == null) {
611                mImpl = new RttServiceImpl(getContext(), mHandlerThread.getLooper());
612            }
613            mImpl.startService();
614        }
615    }
616
617
618}
619