blob: 521d7a1ec91aac6424caca7f8fa7a58e61623743 [file] [log] [blame]
/*
* Copyright (C) 2016 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 com.android.server.wifi.aware;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.wifi.V1_0.NanStatusType;
import android.net.wifi.RttManager;
import android.net.wifi.aware.Characteristics;
import android.net.wifi.aware.ConfigRequest;
import android.net.wifi.aware.DiscoverySession;
import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback;
import android.net.wifi.aware.IWifiAwareEventCallback;
import android.net.wifi.aware.IWifiAwareManager;
import android.net.wifi.aware.PublishConfig;
import android.net.wifi.aware.SubscribeConfig;
import android.os.Binder;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
/**
* Implementation of the IWifiAwareManager AIDL interface. Performs validity
* (permission and clientID-UID mapping) checks and delegates execution to the
* WifiAwareStateManager singleton handler. Limited state to feedback which has to
* be provided instantly: client and session IDs.
*/
public class WifiAwareServiceImpl extends IWifiAwareManager.Stub {
private static final String TAG = "WifiAwareService";
private static final boolean DBG = false;
private static final boolean VDBG = false; // STOPSHIP if true
private Context mContext;
private WifiAwareStateManager mStateManager;
private final Object mLock = new Object();
private final SparseArray<IBinder.DeathRecipient> mDeathRecipientsByClientId =
new SparseArray<>();
private int mNextClientId = 1;
private int mNextRangingId = 1;
private final SparseIntArray mUidByClientId = new SparseIntArray();
public WifiAwareServiceImpl(Context context) {
mContext = context.getApplicationContext();
}
/**
* Proxy for the final native call of the parent class. Enables mocking of
* the function.
*/
public int getMockableCallingUid() {
return getCallingUid();
}
/**
* Start the service: allocate a new thread (for now), start the handlers of
* the components of the service.
*/
public void start(HandlerThread handlerThread, WifiAwareStateManager awareStateManager) {
Log.i(TAG, "Starting Wi-Fi Aware service");
mStateManager = awareStateManager;
mStateManager.start(mContext, handlerThread.getLooper());
}
/**
* Start/initialize portions of the service which require the boot stage to be complete.
*/
public void startLate() {
Log.i(TAG, "Late initialization of Wi-Fi Aware service");
mStateManager.startLate();
}
@Override
public boolean isUsageEnabled() {
enforceAccessPermission();
return mStateManager.isUsageEnabled();
}
@Override
public Characteristics getCharacteristics() {
enforceAccessPermission();
return mStateManager.getCapabilities() == null ? null
: mStateManager.getCapabilities().toPublicCharacteristics();
}
@Override
public void connect(final IBinder binder, String callingPackage,
IWifiAwareEventCallback callback, ConfigRequest configRequest,
boolean notifyOnIdentityChanged) {
enforceAccessPermission();
enforceChangePermission();
if (callback == null) {
throw new IllegalArgumentException("Callback must not be null");
}
if (binder == null) {
throw new IllegalArgumentException("Binder must not be null");
}
if (notifyOnIdentityChanged) {
enforceLocationPermission();
}
if (configRequest != null) {
enforceConnectivityInternalPermission();
} else {
configRequest = new ConfigRequest.Builder().build();
}
configRequest.validate();
final int uid = getMockableCallingUid();
int pid = getCallingPid();
final int clientId;
synchronized (mLock) {
clientId = mNextClientId++;
}
if (VDBG) {
Log.v(TAG, "connect: uid=" + uid + ", clientId=" + clientId + ", configRequest"
+ configRequest + ", notifyOnIdentityChanged=" + notifyOnIdentityChanged);
}
IBinder.DeathRecipient dr = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (DBG) Log.d(TAG, "binderDied: clientId=" + clientId);
binder.unlinkToDeath(this, 0);
synchronized (mLock) {
mDeathRecipientsByClientId.delete(clientId);
mUidByClientId.delete(clientId);
}
mStateManager.disconnect(clientId);
}
};
try {
binder.linkToDeath(dr, 0);
} catch (RemoteException e) {
Log.e(TAG, "Error on linkToDeath - " + e);
try {
callback.onConnectFail(NanStatusType.INTERNAL_FAILURE);
} catch (RemoteException e1) {
Log.e(TAG, "Error on onConnectFail()");
}
return;
}
synchronized (mLock) {
mDeathRecipientsByClientId.put(clientId, dr);
mUidByClientId.put(clientId, uid);
}
mStateManager.connect(clientId, uid, pid, callingPackage, callback, configRequest,
notifyOnIdentityChanged);
}
@Override
public void disconnect(int clientId, IBinder binder) {
enforceAccessPermission();
enforceChangePermission();
int uid = getMockableCallingUid();
enforceClientValidity(uid, clientId);
if (VDBG) Log.v(TAG, "disconnect: uid=" + uid + ", clientId=" + clientId);
if (binder == null) {
throw new IllegalArgumentException("Binder must not be null");
}
synchronized (mLock) {
IBinder.DeathRecipient dr = mDeathRecipientsByClientId.get(clientId);
if (dr != null) {
binder.unlinkToDeath(dr, 0);
mDeathRecipientsByClientId.delete(clientId);
}
mUidByClientId.delete(clientId);
}
mStateManager.disconnect(clientId);
}
@Override
public void terminateSession(int clientId, int sessionId) {
enforceAccessPermission();
enforceChangePermission();
int uid = getMockableCallingUid();
enforceClientValidity(uid, clientId);
if (VDBG) {
Log.v(TAG, "terminateSession: sessionId=" + sessionId + ", uid=" + uid + ", clientId="
+ clientId);
}
mStateManager.terminateSession(clientId, sessionId);
}
@Override
public void publish(int clientId, PublishConfig publishConfig,
IWifiAwareDiscoverySessionCallback callback) {
enforceAccessPermission();
enforceChangePermission();
enforceLocationPermission();
if (callback == null) {
throw new IllegalArgumentException("Callback must not be null");
}
if (publishConfig == null) {
throw new IllegalArgumentException("PublishConfig must not be null");
}
publishConfig.assertValid(mStateManager.getCharacteristics());
int uid = getMockableCallingUid();
enforceClientValidity(uid, clientId);
if (VDBG) {
Log.v(TAG, "publish: uid=" + uid + ", clientId=" + clientId + ", publishConfig="
+ publishConfig + ", callback=" + callback);
}
mStateManager.publish(clientId, publishConfig, callback);
}
@Override
public void updatePublish(int clientId, int sessionId, PublishConfig publishConfig) {
enforceAccessPermission();
enforceChangePermission();
if (publishConfig == null) {
throw new IllegalArgumentException("PublishConfig must not be null");
}
publishConfig.assertValid(mStateManager.getCharacteristics());
int uid = getMockableCallingUid();
enforceClientValidity(uid, clientId);
if (VDBG) {
Log.v(TAG, "updatePublish: uid=" + uid + ", clientId=" + clientId + ", sessionId="
+ sessionId + ", config=" + publishConfig);
}
mStateManager.updatePublish(clientId, sessionId, publishConfig);
}
@Override
public void subscribe(int clientId, SubscribeConfig subscribeConfig,
IWifiAwareDiscoverySessionCallback callback) {
enforceAccessPermission();
enforceChangePermission();
enforceLocationPermission();
if (callback == null) {
throw new IllegalArgumentException("Callback must not be null");
}
if (subscribeConfig == null) {
throw new IllegalArgumentException("SubscribeConfig must not be null");
}
subscribeConfig.assertValid(mStateManager.getCharacteristics());
int uid = getMockableCallingUid();
enforceClientValidity(uid, clientId);
if (VDBG) {
Log.v(TAG, "subscribe: uid=" + uid + ", clientId=" + clientId + ", config="
+ subscribeConfig + ", callback=" + callback);
}
mStateManager.subscribe(clientId, subscribeConfig, callback);
}
@Override
public void updateSubscribe(int clientId, int sessionId, SubscribeConfig subscribeConfig) {
enforceAccessPermission();
enforceChangePermission();
if (subscribeConfig == null) {
throw new IllegalArgumentException("SubscribeConfig must not be null");
}
subscribeConfig.assertValid(mStateManager.getCharacteristics());
int uid = getMockableCallingUid();
enforceClientValidity(uid, clientId);
if (VDBG) {
Log.v(TAG, "updateSubscribe: uid=" + uid + ", clientId=" + clientId + ", sessionId="
+ sessionId + ", config=" + subscribeConfig);
}
mStateManager.updateSubscribe(clientId, sessionId, subscribeConfig);
}
@Override
public void sendMessage(int clientId, int sessionId, int peerId, byte[] message, int messageId,
int retryCount) {
enforceAccessPermission();
enforceChangePermission();
if (message != null
&& message.length > mStateManager.getCharacteristics().getMaxServiceNameLength()) {
throw new IllegalArgumentException(
"Message length longer than supported by device characteristics");
}
if (retryCount < 0 || retryCount > DiscoverySession.getMaxSendRetryCount()) {
throw new IllegalArgumentException("Invalid 'retryCount' must be non-negative "
+ "and <= DiscoverySession.MAX_SEND_RETRY_COUNT");
}
int uid = getMockableCallingUid();
enforceClientValidity(uid, clientId);
if (VDBG) {
Log.v(TAG,
"sendMessage: sessionId=" + sessionId + ", uid=" + uid + ", clientId="
+ clientId + ", peerId=" + peerId + ", messageId=" + messageId
+ ", retryCount=" + retryCount);
}
mStateManager.sendMessage(clientId, sessionId, peerId, message, messageId, retryCount);
}
@Override
public int startRanging(int clientId, int sessionId, RttManager.ParcelableRttParams params) {
enforceAccessPermission();
enforceLocationPermission();
int uid = getMockableCallingUid();
enforceClientValidity(uid, clientId);
if (VDBG) {
Log.v(TAG, "startRanging: clientId=" + clientId + ", sessionId=" + sessionId + ", "
+ ", parms=" + Arrays.toString(params.mParams));
}
if (params.mParams.length == 0) {
throw new IllegalArgumentException("Empty ranging parameters");
}
int rangingId;
synchronized (mLock) {
rangingId = mNextRangingId++;
}
mStateManager.startRanging(clientId, sessionId, params.mParams, rangingId);
return rangingId;
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(
android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
pw.println("Permission Denial: can't dump WifiAwareService from pid="
+ Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
return;
}
pw.println("Wi-Fi Aware Service");
synchronized (mLock) {
pw.println(" mNextClientId: " + mNextClientId);
pw.println(" mDeathRecipientsByClientId: " + mDeathRecipientsByClientId);
pw.println(" mUidByClientId: " + mUidByClientId);
}
mStateManager.dump(fd, pw, args);
}
private void enforceClientValidity(int uid, int clientId) {
synchronized (mLock) {
int uidIndex = mUidByClientId.indexOfKey(clientId);
if (uidIndex < 0 || mUidByClientId.valueAt(uidIndex) != uid) {
throw new SecurityException("Attempting to use invalid uid+clientId mapping: uid="
+ uid + ", clientId=" + clientId);
}
}
}
private void enforceAccessPermission() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE, TAG);
}
private void enforceChangePermission() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE, TAG);
}
private void enforceLocationPermission() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION,
TAG);
}
private void enforceConnectivityInternalPermission() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL,
TAG);
}
}