1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wifi.aware; 18 19 import static com.android.server.wifi.aware.WifiAwareStateManager.INSTANT_MODE_24GHZ; 20 import static com.android.server.wifi.aware.WifiAwareStateManager.INSTANT_MODE_5GHZ; 21 import static com.android.server.wifi.aware.WifiAwareStateManager.INSTANT_MODE_DISABLED; 22 23 import android.annotation.Nullable; 24 import android.app.AppOpsManager; 25 import android.content.Context; 26 import android.net.wifi.aware.ConfigRequest; 27 import android.net.wifi.aware.IWifiAwareEventCallback; 28 import android.net.wifi.aware.IdentityChangedListener; 29 import android.net.wifi.util.HexEncoding; 30 import android.os.RemoteException; 31 import android.util.Log; 32 import android.util.SparseArray; 33 34 import com.android.server.wifi.util.WifiPermissionsUtil; 35 36 import java.io.FileDescriptor; 37 import java.io.PrintWriter; 38 import java.util.Arrays; 39 40 /** 41 * Manages the service-side Aware state of an individual "client". A client 42 * corresponds to a single instantiation of the WifiAwareManager - there could be 43 * multiple ones per UID/process (each of which is a separate client with its 44 * own session namespace). The client state is primarily: (1) callback (a 45 * singleton per client) through which Aware-wide events are called, and (2) a set 46 * of discovery sessions (publish and/or subscribe) which are created through 47 * this client and whose lifetime is tied to the lifetime of the client. 48 */ 49 public class WifiAwareClientState { 50 private static final String TAG = "WifiAwareClientState"; 51 private boolean mVdbg = false; // STOPSHIP if true 52 private boolean mDbg = false; 53 54 private final Context mContext; 55 private final IWifiAwareEventCallback mCallback; 56 private final SparseArray<WifiAwareDiscoverySessionState> mSessions = new SparseArray<>(); 57 58 private final int mClientId; 59 private ConfigRequest mConfigRequest; 60 private final int mUid; 61 private final int mPid; 62 private final String mCallingPackage; 63 public final @Nullable String mCallingFeatureId; 64 private final boolean mNotifyIdentityChange; 65 private final WifiPermissionsUtil mWifiPermissionsUtil; 66 private final Object mAttributionSource; 67 68 private final AppOpsManager mAppOps; 69 private final long mCreationTime; 70 private final boolean mAwareOffload; 71 public final int mCallerType; 72 73 private static final byte[] ALL_ZERO_MAC = new byte[] {0, 0, 0, 0, 0, 0}; 74 private byte[] mLastDiscoveryInterfaceMac = ALL_ZERO_MAC; 75 private byte[] mLastClusterId = ALL_ZERO_MAC; 76 WifiAwareClientState(Context context, int clientId, int uid, int pid, String callingPackage, @Nullable String callingFeatureId, IWifiAwareEventCallback callback, ConfigRequest configRequest, boolean notifyIdentityChange, long creationTime, WifiPermissionsUtil wifiPermissionsUtil, Object attributionSource, boolean awareOffload, int callerType)77 public WifiAwareClientState(Context context, int clientId, int uid, int pid, 78 String callingPackage, @Nullable String callingFeatureId, 79 IWifiAwareEventCallback callback, ConfigRequest configRequest, 80 boolean notifyIdentityChange, long creationTime, 81 WifiPermissionsUtil wifiPermissionsUtil, Object attributionSource, 82 boolean awareOffload, int callerType) { 83 mContext = context; 84 mClientId = clientId; 85 mUid = uid; 86 mPid = pid; 87 mCallingPackage = callingPackage; 88 mCallingFeatureId = callingFeatureId; 89 mCallback = callback; 90 mConfigRequest = configRequest; 91 mNotifyIdentityChange = notifyIdentityChange; 92 mAwareOffload = awareOffload; 93 94 mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 95 mCreationTime = creationTime; 96 mWifiPermissionsUtil = wifiPermissionsUtil; 97 mAttributionSource = attributionSource; 98 mCallerType = callerType; 99 } 100 101 /** 102 * Enable verbose logging. 103 */ enableVerboseLogging(boolean verbose, boolean vDbg)104 public void enableVerboseLogging(boolean verbose, boolean vDbg) { 105 mDbg = verbose; 106 mVdbg = vDbg; 107 } 108 109 /** 110 * Destroy the current client - corresponds to a disconnect() request from 111 * the client. Destroys all discovery sessions belonging to this client. 112 */ destroy()113 public void destroy() { 114 if (mDbg) { 115 Log.v(TAG, "onAwareSessionTerminated, ClientId:" + mClientId); 116 } 117 for (int i = 0; i < mSessions.size(); ++i) { 118 mSessions.valueAt(i).terminate(); 119 } 120 mSessions.clear(); 121 mConfigRequest = null; 122 123 try { 124 mCallback.onAttachTerminate(); 125 } catch (RemoteException e1) { 126 Log.e(TAG, "Error on onSessionTerminate()"); 127 } 128 } 129 getConfigRequest()130 public ConfigRequest getConfigRequest() { 131 return mConfigRequest; 132 } 133 getClientId()134 public int getClientId() { 135 return mClientId; 136 } 137 getUid()138 public int getUid() { 139 return mUid; 140 } 141 getCallingPackage()142 public String getCallingPackage() { 143 return mCallingPackage; 144 } 145 getNotifyIdentityChange()146 public boolean getNotifyIdentityChange() { 147 return mNotifyIdentityChange; 148 } 149 getCreationTime()150 public long getCreationTime() { 151 return mCreationTime; 152 } 153 getSessions()154 public SparseArray<WifiAwareDiscoverySessionState> getSessions() { 155 return mSessions; 156 } 157 getAttributionSource()158 public Object getAttributionSource() { 159 return mAttributionSource; 160 } 161 isAwareOffload()162 public boolean isAwareOffload() { 163 return mAwareOffload; 164 } 165 /** 166 * Searches the discovery sessions of this client and returns the one 167 * corresponding to the publish/subscribe ID. Used on callbacks from HAL to 168 * map callbacks to the correct discovery session. 169 * 170 * @param pubSubId The publish/subscribe match session ID. 171 * @return Aware session corresponding to the requested ID. 172 */ getAwareSessionStateForPubSubId(int pubSubId)173 public WifiAwareDiscoverySessionState getAwareSessionStateForPubSubId(int pubSubId) { 174 for (int i = 0; i < mSessions.size(); ++i) { 175 WifiAwareDiscoverySessionState session = mSessions.valueAt(i); 176 if (session.isPubSubIdSession(pubSubId)) { 177 return session; 178 } 179 } 180 181 return null; 182 } 183 184 /** 185 * Add the session to the client database. 186 * 187 * @param session Session to be added. 188 */ addSession(WifiAwareDiscoverySessionState session)189 public void addSession(WifiAwareDiscoverySessionState session) { 190 int sessionId = session.getSessionId(); 191 if (mSessions.get(sessionId) != null) { 192 Log.w(TAG, "createSession: sessionId already exists (replaced) - " + sessionId); 193 } 194 195 mSessions.put(sessionId, session); 196 } 197 198 /** 199 * Remove the specified session from the client database - without doing a 200 * terminate on the session. The assumption is that it is already 201 * terminated. 202 * 203 * @param sessionId The session ID of the session to be removed. 204 */ removeSession(int sessionId)205 public void removeSession(int sessionId) { 206 if (mSessions.get(sessionId) == null) { 207 Log.e(TAG, "removeSession: sessionId doesn't exist - " + sessionId); 208 return; 209 } 210 211 mSessions.delete(sessionId); 212 } 213 214 /** 215 * Destroy the discovery session: terminates discovery and frees up 216 * resources. 217 * 218 * @param sessionId The session ID of the session to be destroyed. 219 */ terminateSession(int sessionId)220 public WifiAwareDiscoverySessionState terminateSession(int sessionId) { 221 WifiAwareDiscoverySessionState session = mSessions.get(sessionId); 222 if (session == null) { 223 Log.e(TAG, "terminateSession: sessionId doesn't exist - " + sessionId); 224 return null; 225 } 226 227 session.terminate(); 228 mSessions.delete(sessionId); 229 230 return session; 231 } 232 233 /** 234 * Retrieve a session. 235 * 236 * @param sessionId Session ID of the session to be retrieved. 237 * @return Session or null if there's no session corresponding to the 238 * sessionId. 239 */ getSession(int sessionId)240 public WifiAwareDiscoverySessionState getSession(int sessionId) { 241 return mSessions.get(sessionId); 242 } 243 244 /** 245 * Called to dispatch the Aware interface address change to the client - as an 246 * identity change (interface address information not propagated to client - 247 * privacy concerns). 248 * 249 * @param mac The new MAC address of the discovery interface - optionally propagated to the 250 * client. 251 */ onInterfaceAddressChange(byte[] mac)252 public void onInterfaceAddressChange(byte[] mac) { 253 if (mDbg) { 254 Log.v(TAG, "onInterfaceAddressChange: mClientId=" + mClientId 255 + ", mNotifyIdentityChange=" + mNotifyIdentityChange 256 + ", mac=" + String.valueOf(HexEncoding.encode(mac)) 257 + ", mLastDiscoveryInterfaceMac=" 258 + String.valueOf(HexEncoding.encode(mLastDiscoveryInterfaceMac))); 259 } 260 if (mNotifyIdentityChange && !Arrays.equals(mac, mLastDiscoveryInterfaceMac)) { 261 boolean hasPermission = mWifiPermissionsUtil.checkCallersLocationPermission( 262 mCallingPackage, mCallingFeatureId, mUid, 263 /* coarseForTargetSdkLessThanQ */ true, null); 264 try { 265 if (mVdbg) Log.v(TAG, "hasPermission=" + hasPermission); 266 mCallback.onIdentityChanged(hasPermission ? mac : ALL_ZERO_MAC); 267 } catch (RemoteException e) { 268 Log.w(TAG, "onIdentityChanged: RemoteException - ignored: " + e); 269 } 270 } 271 272 mLastDiscoveryInterfaceMac = mac; 273 } 274 275 /** 276 * Called to dispatch the Aware cluster change (due to joining of a new 277 * cluster or starting a cluster) to the client - as an identity change 278 * (interface address information not propagated to client - privacy 279 * concerns). Dispatched if the client registered for the identity changed 280 * event. 281 * 282 * @param clusterEventType The type of the cluster event that triggered the callback. 283 * @param clusterId The cluster ID of the cluster started or joined. 284 * @param currentDiscoveryInterfaceMac The MAC address of the discovery interface. 285 */ onClusterChange(@dentityChangedListener.ClusterChangeEvent int clusterEventType, byte[] clusterId, byte[] currentDiscoveryInterfaceMac)286 public void onClusterChange(@IdentityChangedListener.ClusterChangeEvent int clusterEventType, 287 byte[] clusterId, byte[] currentDiscoveryInterfaceMac) { 288 if (mDbg) { 289 Log.v(TAG, 290 "onClusterChange: mClientId=" + mClientId + ", mNotifyIdentityChange=" 291 + mNotifyIdentityChange + ", clusterId=" + String.valueOf( 292 HexEncoding.encode(clusterId)) + ", currentDiscoveryInterfaceMac=" 293 + String.valueOf(HexEncoding.encode(currentDiscoveryInterfaceMac)) 294 + ", mLastDiscoveryInterfaceMac=" + String.valueOf( 295 HexEncoding.encode(mLastDiscoveryInterfaceMac)) 296 + ", mLastClusterId=" 297 + String.valueOf(HexEncoding.encode(mLastClusterId))); 298 } 299 if (!mNotifyIdentityChange) { 300 mLastDiscoveryInterfaceMac = currentDiscoveryInterfaceMac; 301 mLastClusterId = clusterId; 302 return; 303 } 304 boolean hasPermission = mWifiPermissionsUtil.checkCallersLocationPermission( 305 mCallingPackage, mCallingFeatureId, mUid, 306 /* coarseForTargetSdkLessThanQ */ true, null); 307 if (mVdbg) Log.v(TAG, "hasPermission=" + hasPermission); 308 if (!Arrays.equals(currentDiscoveryInterfaceMac, mLastDiscoveryInterfaceMac)) { 309 try { 310 mCallback.onIdentityChanged( 311 hasPermission ? currentDiscoveryInterfaceMac : ALL_ZERO_MAC); 312 } catch (RemoteException e) { 313 Log.w(TAG, "onIdentityChanged: RemoteException - ignored: " + e); 314 } 315 } 316 317 mLastDiscoveryInterfaceMac = currentDiscoveryInterfaceMac; 318 319 if (!Arrays.equals(clusterId, mLastClusterId)) { 320 try { 321 mCallback.onClusterIdChanged(clusterEventType, 322 hasPermission ? clusterId : ALL_ZERO_MAC); 323 } catch (RemoteException e) { 324 Log.w(TAG, "onClusterIdChanged: RemoteException - ignored: " + e); 325 } 326 } 327 328 mLastClusterId = clusterId; 329 } 330 331 /** 332 * Check if client needs ranging enabled. 333 * @return True if one of the discovery session has ranging enabled, false otherwise. 334 */ isRangingEnabled()335 public boolean isRangingEnabled() { 336 for (int i = 0; i < mSessions.size(); ++i) { 337 if (mSessions.valueAt(i).isRangingEnabled()) { 338 return true; 339 } 340 } 341 return false; 342 } 343 344 /** 345 * Check the highest instant communication mode of the client. 346 * @param timeout Specify an interval when instant mode config timeout 347 * @return current instant mode one of the {@code INSTANT_MODE_*} 348 */ getInstantMode(long timeout)349 public int getInstantMode(long timeout) { 350 int instantMode = INSTANT_MODE_DISABLED; 351 for (int i = 0; i < mSessions.size(); ++i) { 352 int currentSession = mSessions.valueAt(i).getInstantMode(timeout); 353 if (currentSession == INSTANT_MODE_5GHZ) { 354 return INSTANT_MODE_5GHZ; 355 } 356 if (currentSession == INSTANT_MODE_24GHZ) { 357 instantMode = currentSession; 358 } 359 } 360 return instantMode; 361 } 362 363 /** 364 * Dump the internal state of the class. 365 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)366 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 367 pw.println("AwareClientState:"); 368 pw.println(" mClientId: " + mClientId); 369 pw.println(" mConfigRequest: " + mConfigRequest); 370 pw.println(" mNotifyIdentityChange: " + mNotifyIdentityChange); 371 pw.println(" mCallback: " + mCallback); 372 pw.println(" mSessions: [" + mSessions + "]"); 373 for (int i = 0; i < mSessions.size(); ++i) { 374 mSessions.valueAt(i).dump(fd, pw, args); 375 } 376 } 377 } 378