blob: e96ec3042fe3ff23af5f6e86325e07adf0cea7e2 [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.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
import android.net.wifi.aware.ConfigRequest;
import android.net.wifi.aware.IWifiAwareEventCallback;
import android.net.wifi.util.HexEncoding;
import android.os.RemoteException;
import android.os.WorkSource;
import android.util.Log;
import android.util.SparseArray;
import com.android.server.wifi.util.WifiPermissionsUtil;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
/**
* Manages the service-side Aware state of an individual "client". A client
* corresponds to a single instantiation of the WifiAwareManager - there could be
* multiple ones per UID/process (each of which is a separate client with its
* own session namespace). The client state is primarily: (1) callback (a
* singleton per client) through which Aware-wide events are called, and (2) a set
* of discovery sessions (publish and/or subscribe) which are created through
* this client and whose lifetime is tied to the lifetime of the client.
*/
public class WifiAwareClientState {
private static final String TAG = "WifiAwareClientState";
private static final boolean VDBG = false; // STOPSHIP if true
private boolean mDbg = false;
/* package */ static final int CLUSTER_CHANGE_EVENT_STARTED = 0;
/* package */ static final int CLUSTER_CHANGE_EVENT_JOINED = 1;
private final Context mContext;
private final IWifiAwareEventCallback mCallback;
private final SparseArray<WifiAwareDiscoverySessionState> mSessions = new SparseArray<>();
private final int mClientId;
private ConfigRequest mConfigRequest;
private final int mUid;
private final int mPid;
private final String mCallingPackage;
private final @Nullable String mCallingFeatureId;
private final boolean mNotifyIdentityChange;
private final WifiPermissionsUtil mWifiPermissionsUtil;
private final AppOpsManager mAppOps;
private final long mCreationTime;
private static final byte[] ALL_ZERO_MAC = new byte[] {0, 0, 0, 0, 0, 0};
private byte[] mLastDiscoveryInterfaceMac = ALL_ZERO_MAC;
public WifiAwareClientState(Context context, int clientId, int uid, int pid,
String callingPackage, @Nullable String callingFeatureId,
IWifiAwareEventCallback callback, ConfigRequest configRequest,
boolean notifyIdentityChange, long creationTime,
WifiPermissionsUtil wifiPermissionsUtil) {
mContext = context;
mClientId = clientId;
mUid = uid;
mPid = pid;
mCallingPackage = callingPackage;
mCallingFeatureId = callingFeatureId;
mCallback = callback;
mConfigRequest = configRequest;
mNotifyIdentityChange = notifyIdentityChange;
mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mCreationTime = creationTime;
mWifiPermissionsUtil = wifiPermissionsUtil;
}
/**
* Enable verbose logging.
*/
public void enableVerboseLogging(boolean verbose) {
mDbg = verbose | VDBG;
}
/**
* Destroy the current client - corresponds to a disconnect() request from
* the client. Destroys all discovery sessions belonging to this client.
*/
public void destroy() {
for (int i = 0; i < mSessions.size(); ++i) {
mSessions.valueAt(i).terminate();
}
mSessions.clear();
mConfigRequest = null;
}
public ConfigRequest getConfigRequest() {
return mConfigRequest;
}
public int getClientId() {
return mClientId;
}
public int getUid() {
return mUid;
}
public String getCallingPackage() {
return mCallingPackage;
}
public boolean getNotifyIdentityChange() {
return mNotifyIdentityChange;
}
public long getCreationTime() {
return mCreationTime;
}
public SparseArray<WifiAwareDiscoverySessionState> getSessions() {
return mSessions;
}
/**
* Searches the discovery sessions of this client and returns the one
* corresponding to the publish/subscribe ID. Used on callbacks from HAL to
* map callbacks to the correct discovery session.
*
* @param pubSubId The publish/subscribe match session ID.
* @return Aware session corresponding to the requested ID.
*/
public WifiAwareDiscoverySessionState getAwareSessionStateForPubSubId(int pubSubId) {
for (int i = 0; i < mSessions.size(); ++i) {
WifiAwareDiscoverySessionState session = mSessions.valueAt(i);
if (session.isPubSubIdSession(pubSubId)) {
return session;
}
}
return null;
}
/**
* Add the session to the client database.
*
* @param session Session to be added.
*/
public void addSession(WifiAwareDiscoverySessionState session) {
int sessionId = session.getSessionId();
if (mSessions.get(sessionId) != null) {
Log.w(TAG, "createSession: sessionId already exists (replaced) - " + sessionId);
}
mSessions.put(sessionId, session);
}
/**
* Remove the specified session from the client database - without doing a
* terminate on the session. The assumption is that it is already
* terminated.
*
* @param sessionId The session ID of the session to be removed.
*/
public void removeSession(int sessionId) {
if (mSessions.get(sessionId) == null) {
Log.e(TAG, "removeSession: sessionId doesn't exist - " + sessionId);
return;
}
mSessions.delete(sessionId);
}
/**
* Destroy the discovery session: terminates discovery and frees up
* resources.
*
* @param sessionId The session ID of the session to be destroyed.
*/
public WifiAwareDiscoverySessionState terminateSession(int sessionId) {
WifiAwareDiscoverySessionState session = mSessions.get(sessionId);
if (session == null) {
Log.e(TAG, "terminateSession: sessionId doesn't exist - " + sessionId);
return null;
}
session.terminate();
mSessions.delete(sessionId);
return session;
}
/**
* Retrieve a session.
*
* @param sessionId Session ID of the session to be retrieved.
* @return Session or null if there's no session corresponding to the
* sessionId.
*/
public WifiAwareDiscoverySessionState getSession(int sessionId) {
return mSessions.get(sessionId);
}
/**
* Called to dispatch the Aware interface address change to the client - as an
* identity change (interface address information not propagated to client -
* privacy concerns).
*
* @param mac The new MAC address of the discovery interface - optionally propagated to the
* client.
*/
public void onInterfaceAddressChange(byte[] mac) {
if (mDbg) {
Log.v(TAG,
"onInterfaceAddressChange: mClientId=" + mClientId + ", mNotifyIdentityChange="
+ mNotifyIdentityChange + ", mac=" + String.valueOf(
HexEncoding.encode(mac)) + ", mLastDiscoveryInterfaceMac="
+ String.valueOf(HexEncoding.encode(mLastDiscoveryInterfaceMac)));
}
if (mNotifyIdentityChange && !Arrays.equals(mac, mLastDiscoveryInterfaceMac)) {
try {
boolean hasPermission = mWifiPermissionsUtil.checkCallersLocationPermission(
mCallingPackage, mCallingFeatureId, mUid,
/* coarseForTargetSdkLessThanQ */ true, null);
if (VDBG) Log.v(TAG, "hasPermission=" + hasPermission);
mCallback.onIdentityChanged(hasPermission ? mac : ALL_ZERO_MAC);
} catch (RemoteException e) {
Log.w(TAG, "onIdentityChanged: RemoteException - ignored: " + e);
}
}
mLastDiscoveryInterfaceMac = mac;
}
/**
* Called to dispatch the Aware cluster change (due to joining of a new
* cluster or starting a cluster) to the client - as an identity change
* (interface address information not propagated to client - privacy
* concerns). Dispatched if the client registered for the identity changed
* event.
*
* @param mac The cluster ID of the cluster started or joined.
* @param currentDiscoveryInterfaceMac The MAC address of the discovery interface.
*/
public void onClusterChange(int flag, byte[] mac, byte[] currentDiscoveryInterfaceMac) {
if (mDbg) {
Log.v(TAG,
"onClusterChange: mClientId=" + mClientId + ", mNotifyIdentityChange="
+ mNotifyIdentityChange + ", mac=" + String.valueOf(
HexEncoding.encode(mac)) + ", currentDiscoveryInterfaceMac="
+ String.valueOf(HexEncoding.encode(currentDiscoveryInterfaceMac))
+ ", mLastDiscoveryInterfaceMac=" + String.valueOf(
HexEncoding.encode(mLastDiscoveryInterfaceMac)));
}
if (mNotifyIdentityChange && !Arrays.equals(currentDiscoveryInterfaceMac,
mLastDiscoveryInterfaceMac)) {
try {
boolean hasPermission = mWifiPermissionsUtil.checkCallersLocationPermission(
mCallingPackage, mCallingFeatureId, mUid,
/* coarseForTargetSdkLessThanQ */ true, null);
if (VDBG) Log.v(TAG, "hasPermission=" + hasPermission);
mCallback.onIdentityChanged(
hasPermission ? currentDiscoveryInterfaceMac : ALL_ZERO_MAC);
} catch (RemoteException e) {
Log.w(TAG, "onIdentityChanged: RemoteException - ignored: " + e);
}
}
mLastDiscoveryInterfaceMac = currentDiscoveryInterfaceMac;
}
/**
* Check if client needs ranging enabled.
* @return True if one of the discovery session has ranging enabled, false otherwise.
*/
public boolean isRangingEnabled() {
for (int i = 0; i < mSessions.size(); ++i) {
if (mSessions.valueAt(i).isRangingEnabled()) {
return true;
}
}
return false;
}
/**
* Dump the internal state of the class.
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("AwareClientState:");
pw.println(" mClientId: " + mClientId);
pw.println(" mConfigRequest: " + mConfigRequest);
pw.println(" mNotifyIdentityChange: " + mNotifyIdentityChange);
pw.println(" mCallback: " + mCallback);
pw.println(" mSessions: [" + mSessions + "]");
for (int i = 0; i < mSessions.size(); ++i) {
mSessions.valueAt(i).dump(fd, pw, args);
}
}
}