WifiAwareServiceImpl.java revision c760a66378bbd844eb421658799b4d55c76b98fa
1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.wifi.aware;
18
19import android.content.Context;
20import android.content.pm.PackageManager;
21import android.hardware.wifi.V1_0.NanStatusType;
22import android.net.wifi.RttManager;
23import android.net.wifi.aware.Characteristics;
24import android.net.wifi.aware.ConfigRequest;
25import android.net.wifi.aware.DiscoverySession;
26import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback;
27import android.net.wifi.aware.IWifiAwareEventCallback;
28import android.net.wifi.aware.IWifiAwareManager;
29import android.net.wifi.aware.PublishConfig;
30import android.net.wifi.aware.SubscribeConfig;
31import android.os.Binder;
32import android.os.HandlerThread;
33import android.os.IBinder;
34import android.os.RemoteException;
35import android.os.ResultReceiver;
36import android.os.ShellCallback;
37import android.util.Log;
38import android.util.SparseArray;
39import android.util.SparseIntArray;
40
41import java.io.FileDescriptor;
42import java.io.PrintWriter;
43import java.util.Arrays;
44
45/**
46 * Implementation of the IWifiAwareManager AIDL interface. Performs validity
47 * (permission and clientID-UID mapping) checks and delegates execution to the
48 * WifiAwareStateManager singleton handler. Limited state to feedback which has to
49 * be provided instantly: client and session IDs.
50 */
51public class WifiAwareServiceImpl extends IWifiAwareManager.Stub {
52    private static final String TAG = "WifiAwareService";
53    private static final boolean DBG = false;
54    private static final boolean VDBG = false; // STOPSHIP if true
55
56    private Context mContext;
57    private WifiAwareStateManager mStateManager;
58    private WifiAwareShellCommand mShellCommand;
59
60    private final Object mLock = new Object();
61    private final SparseArray<IBinder.DeathRecipient> mDeathRecipientsByClientId =
62            new SparseArray<>();
63    private int mNextClientId = 1;
64    private int mNextRangingId = 1;
65    private final SparseIntArray mUidByClientId = new SparseIntArray();
66
67    public WifiAwareServiceImpl(Context context) {
68        mContext = context.getApplicationContext();
69    }
70
71    /**
72     * Proxy for the final native call of the parent class. Enables mocking of
73     * the function.
74     */
75    public int getMockableCallingUid() {
76        return getCallingUid();
77    }
78
79    /**
80     * Start the service: allocate a new thread (for now), start the handlers of
81     * the components of the service.
82     */
83    public void start(HandlerThread handlerThread, WifiAwareStateManager awareStateManager,
84            WifiAwareShellCommand awareShellCommand) {
85        Log.i(TAG, "Starting Wi-Fi Aware service");
86
87        mStateManager = awareStateManager;
88        mShellCommand = awareShellCommand;
89        mStateManager.start(mContext, handlerThread.getLooper());
90    }
91
92    /**
93     * Start/initialize portions of the service which require the boot stage to be complete.
94     */
95    public void startLate() {
96        Log.i(TAG, "Late initialization of Wi-Fi Aware service");
97
98        mStateManager.startLate();
99    }
100
101    @Override
102    public boolean isUsageEnabled() {
103        enforceAccessPermission();
104
105        return mStateManager.isUsageEnabled();
106    }
107
108    @Override
109    public Characteristics getCharacteristics() {
110        enforceAccessPermission();
111
112        return mStateManager.getCapabilities() == null ? null
113                : mStateManager.getCapabilities().toPublicCharacteristics();
114    }
115
116    @Override
117    public void connect(final IBinder binder, String callingPackage,
118            IWifiAwareEventCallback callback, ConfigRequest configRequest,
119            boolean notifyOnIdentityChanged) {
120        enforceAccessPermission();
121        enforceChangePermission();
122        if (callback == null) {
123            throw new IllegalArgumentException("Callback must not be null");
124        }
125        if (binder == null) {
126            throw new IllegalArgumentException("Binder must not be null");
127        }
128
129        if (notifyOnIdentityChanged) {
130            enforceLocationPermission();
131        }
132
133        if (configRequest != null) {
134            enforceConnectivityInternalPermission();
135        } else {
136            configRequest = new ConfigRequest.Builder().build();
137        }
138        configRequest.validate();
139
140        final int uid = getMockableCallingUid();
141        int pid = getCallingPid();
142
143        final int clientId;
144        synchronized (mLock) {
145            clientId = mNextClientId++;
146        }
147
148        if (VDBG) {
149            Log.v(TAG, "connect: uid=" + uid + ", clientId=" + clientId + ", configRequest"
150                    + configRequest + ", notifyOnIdentityChanged=" + notifyOnIdentityChanged);
151        }
152
153        IBinder.DeathRecipient dr = new IBinder.DeathRecipient() {
154            @Override
155            public void binderDied() {
156                if (DBG) Log.d(TAG, "binderDied: clientId=" + clientId);
157                binder.unlinkToDeath(this, 0);
158
159                synchronized (mLock) {
160                    mDeathRecipientsByClientId.delete(clientId);
161                    mUidByClientId.delete(clientId);
162                }
163
164                mStateManager.disconnect(clientId);
165            }
166        };
167
168        try {
169            binder.linkToDeath(dr, 0);
170        } catch (RemoteException e) {
171            Log.e(TAG, "Error on linkToDeath - " + e);
172            try {
173                callback.onConnectFail(NanStatusType.INTERNAL_FAILURE);
174            } catch (RemoteException e1) {
175                Log.e(TAG, "Error on onConnectFail()");
176            }
177            return;
178        }
179
180        synchronized (mLock) {
181            mDeathRecipientsByClientId.put(clientId, dr);
182            mUidByClientId.put(clientId, uid);
183        }
184
185        mStateManager.connect(clientId, uid, pid, callingPackage, callback, configRequest,
186                notifyOnIdentityChanged);
187    }
188
189    @Override
190    public void disconnect(int clientId, IBinder binder) {
191        enforceAccessPermission();
192        enforceChangePermission();
193
194        int uid = getMockableCallingUid();
195        enforceClientValidity(uid, clientId);
196        if (VDBG) Log.v(TAG, "disconnect: uid=" + uid + ", clientId=" + clientId);
197
198        if (binder == null) {
199            throw new IllegalArgumentException("Binder must not be null");
200        }
201
202        synchronized (mLock) {
203            IBinder.DeathRecipient dr = mDeathRecipientsByClientId.get(clientId);
204            if (dr != null) {
205                binder.unlinkToDeath(dr, 0);
206                mDeathRecipientsByClientId.delete(clientId);
207            }
208            mUidByClientId.delete(clientId);
209        }
210
211        mStateManager.disconnect(clientId);
212    }
213
214    @Override
215    public void terminateSession(int clientId, int sessionId) {
216        enforceAccessPermission();
217        enforceChangePermission();
218
219        int uid = getMockableCallingUid();
220        enforceClientValidity(uid, clientId);
221        if (VDBG) {
222            Log.v(TAG, "terminateSession: sessionId=" + sessionId + ", uid=" + uid + ", clientId="
223                    + clientId);
224        }
225
226        mStateManager.terminateSession(clientId, sessionId);
227    }
228
229    @Override
230    public void publish(int clientId, PublishConfig publishConfig,
231            IWifiAwareDiscoverySessionCallback callback) {
232        enforceAccessPermission();
233        enforceChangePermission();
234        enforceLocationPermission();
235
236        if (callback == null) {
237            throw new IllegalArgumentException("Callback must not be null");
238        }
239        if (publishConfig == null) {
240            throw new IllegalArgumentException("PublishConfig must not be null");
241        }
242        publishConfig.assertValid(mStateManager.getCharacteristics());
243
244        int uid = getMockableCallingUid();
245        enforceClientValidity(uid, clientId);
246        if (VDBG) {
247            Log.v(TAG, "publish: uid=" + uid + ", clientId=" + clientId + ", publishConfig="
248                    + publishConfig + ", callback=" + callback);
249        }
250
251        mStateManager.publish(clientId, publishConfig, callback);
252    }
253
254    @Override
255    public void updatePublish(int clientId, int sessionId, PublishConfig publishConfig) {
256        enforceAccessPermission();
257        enforceChangePermission();
258
259        if (publishConfig == null) {
260            throw new IllegalArgumentException("PublishConfig must not be null");
261        }
262        publishConfig.assertValid(mStateManager.getCharacteristics());
263
264        int uid = getMockableCallingUid();
265        enforceClientValidity(uid, clientId);
266        if (VDBG) {
267            Log.v(TAG, "updatePublish: uid=" + uid + ", clientId=" + clientId + ", sessionId="
268                    + sessionId + ", config=" + publishConfig);
269        }
270
271        mStateManager.updatePublish(clientId, sessionId, publishConfig);
272    }
273
274    @Override
275    public void subscribe(int clientId, SubscribeConfig subscribeConfig,
276            IWifiAwareDiscoverySessionCallback callback) {
277        enforceAccessPermission();
278        enforceChangePermission();
279        enforceLocationPermission();
280
281        if (callback == null) {
282            throw new IllegalArgumentException("Callback must not be null");
283        }
284        if (subscribeConfig == null) {
285            throw new IllegalArgumentException("SubscribeConfig must not be null");
286        }
287        subscribeConfig.assertValid(mStateManager.getCharacteristics());
288
289        int uid = getMockableCallingUid();
290        enforceClientValidity(uid, clientId);
291        if (VDBG) {
292            Log.v(TAG, "subscribe: uid=" + uid + ", clientId=" + clientId + ", config="
293                    + subscribeConfig + ", callback=" + callback);
294        }
295
296        mStateManager.subscribe(clientId, subscribeConfig, callback);
297    }
298
299    @Override
300    public void updateSubscribe(int clientId, int sessionId, SubscribeConfig subscribeConfig) {
301        enforceAccessPermission();
302        enforceChangePermission();
303
304        if (subscribeConfig == null) {
305            throw new IllegalArgumentException("SubscribeConfig must not be null");
306        }
307        subscribeConfig.assertValid(mStateManager.getCharacteristics());
308
309        int uid = getMockableCallingUid();
310        enforceClientValidity(uid, clientId);
311        if (VDBG) {
312            Log.v(TAG, "updateSubscribe: uid=" + uid + ", clientId=" + clientId + ", sessionId="
313                    + sessionId + ", config=" + subscribeConfig);
314        }
315
316        mStateManager.updateSubscribe(clientId, sessionId, subscribeConfig);
317    }
318
319    @Override
320    public void sendMessage(int clientId, int sessionId, int peerId, byte[] message, int messageId,
321            int retryCount) {
322        enforceAccessPermission();
323        enforceChangePermission();
324
325        if (retryCount != 0) {
326            enforceConnectivityInternalPermission();
327        }
328
329        if (message != null
330                && message.length > mStateManager.getCharacteristics().getMaxServiceNameLength()) {
331            throw new IllegalArgumentException(
332                    "Message length longer than supported by device characteristics");
333        }
334        if (retryCount < 0 || retryCount > DiscoverySession.getMaxSendRetryCount()) {
335            throw new IllegalArgumentException("Invalid 'retryCount' must be non-negative "
336                    + "and <= DiscoverySession.MAX_SEND_RETRY_COUNT");
337        }
338
339        int uid = getMockableCallingUid();
340        enforceClientValidity(uid, clientId);
341        if (VDBG) {
342            Log.v(TAG,
343                    "sendMessage: sessionId=" + sessionId + ", uid=" + uid + ", clientId="
344                            + clientId + ", peerId=" + peerId + ", messageId=" + messageId
345                            + ", retryCount=" + retryCount);
346        }
347
348        mStateManager.sendMessage(clientId, sessionId, peerId, message, messageId, retryCount);
349    }
350
351    @Override
352    public int startRanging(int clientId, int sessionId, RttManager.ParcelableRttParams params) {
353        enforceAccessPermission();
354        enforceLocationPermission();
355
356        // TODO: b/35676064 restricts access to this API until decide if will open.
357        enforceConnectivityInternalPermission();
358
359        int uid = getMockableCallingUid();
360        enforceClientValidity(uid, clientId);
361        if (VDBG) {
362            Log.v(TAG, "startRanging: clientId=" + clientId + ", sessionId=" + sessionId + ", "
363                    + ", parms=" + Arrays.toString(params.mParams));
364        }
365
366        if (params.mParams.length == 0) {
367            throw new IllegalArgumentException("Empty ranging parameters");
368        }
369
370        int rangingId;
371        synchronized (mLock) {
372            rangingId = mNextRangingId++;
373        }
374        mStateManager.startRanging(clientId, sessionId, params.mParams, rangingId);
375        return rangingId;
376    }
377
378    @Override
379    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
380            String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
381        mShellCommand.exec(this, in, out, err, args, callback, resultReceiver);
382    }
383
384    @Override
385    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
386        if (mContext.checkCallingOrSelfPermission(
387                android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
388            pw.println("Permission Denial: can't dump WifiAwareService from pid="
389                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
390            return;
391        }
392        pw.println("Wi-Fi Aware Service");
393        synchronized (mLock) {
394            pw.println("  mNextClientId: " + mNextClientId);
395            pw.println("  mDeathRecipientsByClientId: " + mDeathRecipientsByClientId);
396            pw.println("  mUidByClientId: " + mUidByClientId);
397        }
398        mStateManager.dump(fd, pw, args);
399    }
400
401    private void enforceClientValidity(int uid, int clientId) {
402        synchronized (mLock) {
403            int uidIndex = mUidByClientId.indexOfKey(clientId);
404            if (uidIndex < 0 || mUidByClientId.valueAt(uidIndex) != uid) {
405                throw new SecurityException("Attempting to use invalid uid+clientId mapping: uid="
406                        + uid + ", clientId=" + clientId);
407            }
408        }
409    }
410
411    private void enforceAccessPermission() {
412        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE, TAG);
413    }
414
415    private void enforceChangePermission() {
416        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE, TAG);
417    }
418
419    private void enforceLocationPermission() {
420        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION,
421                TAG);
422    }
423
424    private void enforceConnectivityInternalPermission() {
425        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL,
426                TAG);
427    }
428}
429