| /* |
| * 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); |
| } |
| } |