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.LocalLog; 32 import android.util.Log; 33 import android.util.SparseArray; 34 35 import com.android.server.wifi.util.WifiPermissionsUtil; 36 37 import java.io.FileDescriptor; 38 import java.io.PrintWriter; 39 import java.util.Arrays; 40 41 /** 42 * Manages the service-side Aware state of an individual "client". A client 43 * corresponds to a single instantiation of the WifiAwareManager - there could be 44 * multiple ones per UID/process (each of which is a separate client with its 45 * own session namespace). The client state is primarily: (1) callback (a 46 * singleton per client) through which Aware-wide events are called, and (2) a set 47 * of discovery sessions (publish and/or subscribe) which are created through 48 * this client and whose lifetime is tied to the lifetime of the client. 49 */ 50 public class WifiAwareClientState { 51 private static final String TAG = "WifiAwareClientState"; 52 private static final boolean VDBG = false; // STOPSHIP if true 53 private boolean mDbg = false; 54 55 private final Context mContext; 56 private final IWifiAwareEventCallback mCallback; 57 private final SparseArray<WifiAwareDiscoverySessionState> mSessions = new SparseArray<>(); 58 private final LocalLog mLocalLog; 59 60 private final int mClientId; 61 private ConfigRequest mConfigRequest; 62 private final int mUid; 63 private final int mPid; 64 private final String mCallingPackage; 65 public final @Nullable String mCallingFeatureId; 66 private final boolean mNotifyIdentityChange; 67 private final WifiPermissionsUtil mWifiPermissionsUtil; 68 private final Object mAttributionSource; 69 70 private final AppOpsManager mAppOps; 71 private final long mCreationTime; 72 private final boolean mAwareOffload; 73 public final int mCallerType; 74 75 private static final byte[] ALL_ZERO_MAC = new byte[] {0, 0, 0, 0, 0, 0}; 76 private byte[] mLastDiscoveryInterfaceMac = ALL_ZERO_MAC; 77 private byte[] mLastClusterId = ALL_ZERO_MAC; 78 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, LocalLog localLog, boolean awareOffload, int callerType)79 public WifiAwareClientState(Context context, int clientId, int uid, int pid, 80 String callingPackage, @Nullable String callingFeatureId, 81 IWifiAwareEventCallback callback, ConfigRequest configRequest, 82 boolean notifyIdentityChange, long creationTime, 83 WifiPermissionsUtil wifiPermissionsUtil, Object attributionSource, LocalLog localLog, 84 boolean awareOffload, int callerType) { 85 mContext = context; 86 mClientId = clientId; 87 mUid = uid; 88 mPid = pid; 89 mCallingPackage = callingPackage; 90 mCallingFeatureId = callingFeatureId; 91 mCallback = callback; 92 mConfigRequest = configRequest; 93 mNotifyIdentityChange = notifyIdentityChange; 94 mAwareOffload = awareOffload; 95 96 mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 97 mCreationTime = creationTime; 98 mWifiPermissionsUtil = wifiPermissionsUtil; 99 mAttributionSource = attributionSource; 100 mLocalLog = localLog; 101 mCallerType = callerType; 102 } 103 104 /** 105 * Enable verbose logging. 106 */ enableVerboseLogging(boolean verbose)107 public void enableVerboseLogging(boolean verbose) { 108 mDbg = verbose | VDBG; 109 } 110 111 /** 112 * Destroy the current client - corresponds to a disconnect() request from 113 * the client. Destroys all discovery sessions belonging to this client. 114 */ destroy()115 public void destroy() { 116 mLocalLog.log("onAwareSessionTerminated, ClientId:" + mClientId); 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 mLocalLog.log("onInterfaceAddressChange: mClientId=" + mClientId 254 + ", mNotifyIdentityChange=" + mNotifyIdentityChange 255 + ", mac=" + String.valueOf(HexEncoding.encode(mac)) 256 + ", mLastDiscoveryInterfaceMac=" 257 + String.valueOf(HexEncoding.encode(mLastDiscoveryInterfaceMac))); 258 if (mNotifyIdentityChange && !Arrays.equals(mac, mLastDiscoveryInterfaceMac)) { 259 boolean hasPermission = mWifiPermissionsUtil.checkCallersLocationPermission( 260 mCallingPackage, mCallingFeatureId, mUid, 261 /* coarseForTargetSdkLessThanQ */ true, null); 262 try { 263 if (VDBG) Log.v(TAG, "hasPermission=" + hasPermission); 264 mCallback.onIdentityChanged(hasPermission ? mac : ALL_ZERO_MAC); 265 } catch (RemoteException e) { 266 Log.w(TAG, "onIdentityChanged: RemoteException - ignored: " + e); 267 } 268 } 269 270 mLastDiscoveryInterfaceMac = mac; 271 } 272 273 /** 274 * Called to dispatch the Aware cluster change (due to joining of a new 275 * cluster or starting a cluster) to the client - as an identity change 276 * (interface address information not propagated to client - privacy 277 * concerns). Dispatched if the client registered for the identity changed 278 * event. 279 * 280 * @param clusterEventType The type of the cluster event that triggered the callback. 281 * @param clusterId The cluster ID of the cluster started or joined. 282 * @param currentDiscoveryInterfaceMac The MAC address of the discovery interface. 283 */ onClusterChange(@dentityChangedListener.ClusterChangeEvent int clusterEventType, byte[] clusterId, byte[] currentDiscoveryInterfaceMac)284 public void onClusterChange(@IdentityChangedListener.ClusterChangeEvent int clusterEventType, 285 byte[] clusterId, byte[] currentDiscoveryInterfaceMac) { 286 if (mDbg) { 287 Log.v(TAG, 288 "onClusterChange: mClientId=" + mClientId + ", mNotifyIdentityChange=" 289 + mNotifyIdentityChange + ", clusterId=" + String.valueOf( 290 HexEncoding.encode(clusterId)) + ", currentDiscoveryInterfaceMac=" 291 + String.valueOf(HexEncoding.encode(currentDiscoveryInterfaceMac)) 292 + ", mLastDiscoveryInterfaceMac=" + String.valueOf( 293 HexEncoding.encode(mLastDiscoveryInterfaceMac)) 294 + ", mLastClusterId=" 295 + String.valueOf(HexEncoding.encode(mLastClusterId))); 296 } 297 if (!mNotifyIdentityChange) { 298 mLastDiscoveryInterfaceMac = currentDiscoveryInterfaceMac; 299 mLastClusterId = clusterId; 300 return; 301 } 302 boolean hasPermission = mWifiPermissionsUtil.checkCallersLocationPermission( 303 mCallingPackage, mCallingFeatureId, mUid, 304 /* coarseForTargetSdkLessThanQ */ true, null); 305 if (VDBG) Log.v(TAG, "hasPermission=" + hasPermission); 306 if (!Arrays.equals(currentDiscoveryInterfaceMac, mLastDiscoveryInterfaceMac)) { 307 try { 308 mCallback.onIdentityChanged( 309 hasPermission ? currentDiscoveryInterfaceMac : ALL_ZERO_MAC); 310 } catch (RemoteException e) { 311 Log.w(TAG, "onIdentityChanged: RemoteException - ignored: " + e); 312 } 313 } 314 315 mLastDiscoveryInterfaceMac = currentDiscoveryInterfaceMac; 316 317 if (!Arrays.equals(clusterId, mLastClusterId)) { 318 try { 319 mCallback.onClusterIdChanged(clusterEventType, 320 hasPermission ? clusterId : ALL_ZERO_MAC); 321 } catch (RemoteException e) { 322 Log.w(TAG, "onClusterIdChanged: RemoteException - ignored: " + e); 323 } 324 } 325 326 mLastClusterId = clusterId; 327 } 328 329 /** 330 * Check if client needs ranging enabled. 331 * @return True if one of the discovery session has ranging enabled, false otherwise. 332 */ isRangingEnabled()333 public boolean isRangingEnabled() { 334 for (int i = 0; i < mSessions.size(); ++i) { 335 if (mSessions.valueAt(i).isRangingEnabled()) { 336 return true; 337 } 338 } 339 return false; 340 } 341 342 /** 343 * Check the highest instant communication mode of the client. 344 * @param timeout Specify an interval when instant mode config timeout 345 * @return current instant mode one of the {@code INSTANT_MODE_*} 346 */ getInstantMode(long timeout)347 public int getInstantMode(long timeout) { 348 int instantMode = INSTANT_MODE_DISABLED; 349 for (int i = 0; i < mSessions.size(); ++i) { 350 int currentSession = mSessions.valueAt(i).getInstantMode(timeout); 351 if (currentSession == INSTANT_MODE_5GHZ) { 352 return INSTANT_MODE_5GHZ; 353 } 354 if (currentSession == INSTANT_MODE_24GHZ) { 355 instantMode = currentSession; 356 } 357 } 358 return instantMode; 359 } 360 361 /** 362 * Dump the internal state of the class. 363 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)364 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 365 pw.println("AwareClientState:"); 366 pw.println(" mClientId: " + mClientId); 367 pw.println(" mConfigRequest: " + mConfigRequest); 368 pw.println(" mNotifyIdentityChange: " + mNotifyIdentityChange); 369 pw.println(" mCallback: " + mCallback); 370 pw.println(" mSessions: [" + mSessions + "]"); 371 for (int i = 0; i < mSessions.size(); ++i) { 372 mSessions.valueAt(i).dump(fd, pw, args); 373 } 374 } 375 } 376