blob: e6e961d6906d0984fa28d4c54223480165c1a05a [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.Manifest;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.hardware.wifi.V1_0.NanStatusType;
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.IWifiAwareMacAddressProvider;
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.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.provider.Settings;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
import com.android.server.wifi.Clock;
import com.android.server.wifi.FrameworkFacade;
import com.android.server.wifi.WifiInjector;
import com.android.server.wifi.util.WifiPermissionsUtil;
import com.android.server.wifi.util.WifiPermissionsWrapper;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;
/**
* 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 VDBG = false; // STOPSHIP if true
/* package */ boolean mDbg = false;
private Context mContext;
private AppOpsManager mAppOps;
private WifiPermissionsUtil mWifiPermissionsUtil;
private WifiAwareStateManager mStateManager;
private WifiAwareShellCommand mShellCommand;
private final Object mLock = new Object();
private final SparseArray<IBinder.DeathRecipient> mDeathRecipientsByClientId =
new SparseArray<>();
private int mNextClientId = 1;
private final SparseIntArray mUidByClientId = new SparseIntArray();
public WifiAwareServiceImpl(Context context) {
mContext = context.getApplicationContext();
mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
}
/**
* 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,
WifiAwareShellCommand awareShellCommand, WifiAwareMetrics awareMetrics,
WifiPermissionsUtil wifiPermissionsUtil, WifiPermissionsWrapper permissionsWrapper,
FrameworkFacade frameworkFacade, WifiAwareNativeManager wifiAwareNativeManager,
WifiAwareNativeApi wifiAwareNativeApi,
WifiAwareNativeCallback wifiAwareNativeCallback) {
Log.i(TAG, "Starting Wi-Fi Aware service");
mWifiPermissionsUtil = wifiPermissionsUtil;
mStateManager = awareStateManager;
mShellCommand = awareShellCommand;
mStateManager.start(mContext, handlerThread.getLooper(), awareMetrics, wifiPermissionsUtil,
permissionsWrapper, new Clock());
frameworkFacade.registerContentObserver(mContext,
Settings.Global.getUriFor(Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED), true,
new ContentObserver(new Handler(handlerThread.getLooper())) {
@Override
public void onChange(boolean selfChange) {
enableVerboseLogging(frameworkFacade.getIntegerSetting(mContext,
Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED, 0), awareStateManager,
wifiAwareNativeManager, wifiAwareNativeApi,
wifiAwareNativeCallback);
}
});
enableVerboseLogging(frameworkFacade.getIntegerSetting(mContext,
Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED, 0), awareStateManager,
wifiAwareNativeManager, wifiAwareNativeApi, wifiAwareNativeCallback);
}
private void enableVerboseLogging(int verbose, WifiAwareStateManager awareStateManager,
WifiAwareNativeManager wifiAwareNativeManager, WifiAwareNativeApi wifiAwareNativeApi,
WifiAwareNativeCallback wifiAwareNativeCallback) {
boolean dbg;
if (verbose > 0) {
dbg = true;
} else {
dbg = false;
}
if (VDBG) {
dbg = true; // just override
}
mDbg = dbg;
awareStateManager.mDbg = dbg;
if (awareStateManager.mDataPathMgr != null) { // needed for unit tests
awareStateManager.mDataPathMgr.mDbg = dbg;
WifiInjector.getInstance().getWifiMetrics().getWifiAwareMetrics().mDbg = dbg;
}
wifiAwareNativeCallback.mDbg = dbg;
wifiAwareNativeManager.mDbg = dbg;
wifiAwareNativeApi.mDbg = dbg;
}
/**
* 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();
final int uid = getMockableCallingUid();
mAppOps.checkPackage(uid, callingPackage);
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(callingPackage, getMockableCallingUid());
}
if (configRequest != null) {
enforceNetworkStackPermission();
} else {
configRequest = new ConfigRequest.Builder().build();
}
configRequest.validate();
int pid = getCallingPid();
final int clientId;
synchronized (mLock) {
clientId = mNextClientId++;
}
if (mDbg) {
Log.v(TAG, "connect: uid=" + uid + ", clientId=" + clientId + ", configRequest"
+ configRequest + ", notifyOnIdentityChanged=" + notifyOnIdentityChanged);
}
IBinder.DeathRecipient dr = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (mDbg) Log.v(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 (mDbg) 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(String callingPackage, int clientId, PublishConfig publishConfig,
IWifiAwareDiscoverySessionCallback callback) {
enforceAccessPermission();
enforceChangePermission();
int uid = getMockableCallingUid();
mAppOps.checkPackage(uid, callingPackage);
enforceLocationPermission(callingPackage, getMockableCallingUid());
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(),
mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_RTT));
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(),
mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_RTT));
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(String callingPackage, int clientId, SubscribeConfig subscribeConfig,
IWifiAwareDiscoverySessionCallback callback) {
enforceAccessPermission();
enforceChangePermission();
int uid = getMockableCallingUid();
mAppOps.checkPackage(uid, callingPackage);
enforceLocationPermission(callingPackage, getMockableCallingUid());
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(),
mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_RTT));
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(),
mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_RTT));
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 (retryCount != 0) {
enforceNetworkStackPermission();
}
if (message != null && message.length
> mStateManager.getCharacteristics().getMaxServiceSpecificInfoLength()) {
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 void requestMacAddresses(int uid, List peerIds, IWifiAwareMacAddressProvider callback) {
enforceNetworkStackPermission();
mStateManager.requestMacAddresses(uid, peerIds, callback);
}
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
mShellCommand.exec(this, in, out, err, args, callback, resultReceiver);
}
@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(String callingPackage, int uid) {
mWifiPermissionsUtil.enforceLocationPermission(callingPackage, uid);
}
private void enforceNetworkStackPermission() {
mContext.enforceCallingOrSelfPermission(Manifest.permission.NETWORK_STACK, TAG);
}
}