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