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 android.Manifest; 20 import android.app.AppOpsManager; 21 import android.content.Context; 22 import android.content.pm.PackageManager; 23 import android.net.wifi.RttManager; 24 import android.net.wifi.aware.ConfigRequest; 25 import android.net.wifi.aware.IWifiAwareEventCallback; 26 import android.os.RemoteException; 27 import android.util.Log; 28 import android.util.SparseArray; 29 30 import libcore.util.HexEncoding; 31 32 import java.io.FileDescriptor; 33 import java.io.PrintWriter; 34 import java.util.Arrays; 35 36 /** 37 * Manages the service-side Aware state of an individual "client". A client 38 * corresponds to a single instantiation of the WifiAwareManager - there could be 39 * multiple ones per UID/process (each of which is a separate client with its 40 * own session namespace). The client state is primarily: (1) callback (a 41 * singleton per client) through which Aware-wide events are called, and (2) a set 42 * of discovery sessions (publish and/or subscribe) which are created through 43 * this client and whose lifetime is tied to the lifetime of the client. 44 */ 45 public class WifiAwareClientState { 46 private static final String TAG = "WifiAwareClientState"; 47 private static final boolean DBG = false; 48 private static final boolean VDBG = false; // STOPSHIP if true 49 50 /* package */ static final int CLUSTER_CHANGE_EVENT_STARTED = 0; 51 /* package */ static final int CLUSTER_CHANGE_EVENT_JOINED = 1; 52 53 private final Context mContext; 54 private final IWifiAwareEventCallback mCallback; 55 private final SparseArray<WifiAwareDiscoverySessionState> mSessions = new SparseArray<>(); 56 57 private final int mClientId; 58 private ConfigRequest mConfigRequest; 59 private final int mUid; 60 private final int mPid; 61 private final String mCallingPackage; 62 private final boolean mNotifyIdentityChange; 63 64 private AppOpsManager mAppOps; 65 66 private static final byte[] ALL_ZERO_MAC = new byte[] {0, 0, 0, 0, 0, 0}; 67 private byte[] mLastDiscoveryInterfaceMac = ALL_ZERO_MAC; 68 69 public WifiAwareClientState(Context context, int clientId, int uid, int pid, 70 String callingPackage, IWifiAwareEventCallback callback, ConfigRequest configRequest, 71 boolean notifyIdentityChange) { 72 mContext = context; 73 mClientId = clientId; 74 mUid = uid; 75 mPid = pid; 76 mCallingPackage = callingPackage; 77 mCallback = callback; 78 mConfigRequest = configRequest; 79 mNotifyIdentityChange = notifyIdentityChange; 80 81 mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 82 } 83 84 /** 85 * Destroy the current client - corresponds to a disconnect() request from 86 * the client. Destroys all discovery sessions belonging to this client. 87 */ 88 public void destroy() { 89 for (int i = 0; i < mSessions.size(); ++i) { 90 mSessions.valueAt(i).terminate(); 91 } 92 mSessions.clear(); 93 mConfigRequest = null; 94 } 95 96 public ConfigRequest getConfigRequest() { 97 return mConfigRequest; 98 } 99 100 public int getClientId() { 101 return mClientId; 102 } 103 104 public int getUid() { 105 return mUid; 106 } 107 108 public boolean getNotifyIdentityChange() { 109 return mNotifyIdentityChange; 110 } 111 112 /** 113 * Searches the discovery sessions of this client and returns the one 114 * corresponding to the publish/subscribe ID. Used on callbacks from HAL to 115 * map callbacks to the correct discovery session. 116 * 117 * @param pubSubId The publish/subscribe match session ID. 118 * @return Aware session corresponding to the requested ID. 119 */ 120 public WifiAwareDiscoverySessionState getAwareSessionStateForPubSubId(int pubSubId) { 121 for (int i = 0; i < mSessions.size(); ++i) { 122 WifiAwareDiscoverySessionState session = mSessions.valueAt(i); 123 if (session.isPubSubIdSession(pubSubId)) { 124 return session; 125 } 126 } 127 128 return null; 129 } 130 131 /** 132 * Add the session to the client database. 133 * 134 * @param session Session to be added. 135 */ 136 public void addSession(WifiAwareDiscoverySessionState session) { 137 int sessionId = session.getSessionId(); 138 if (mSessions.get(sessionId) != null) { 139 Log.w(TAG, "createSession: sessionId already exists (replaced) - " + sessionId); 140 } 141 142 mSessions.put(sessionId, session); 143 } 144 145 /** 146 * Remove the specified session from the client database - without doing a 147 * terminate on the session. The assumption is that it is already 148 * terminated. 149 * 150 * @param sessionId The session ID of the session to be removed. 151 */ 152 public void removeSession(int sessionId) { 153 if (mSessions.get(sessionId) == null) { 154 Log.e(TAG, "removeSession: sessionId doesn't exist - " + sessionId); 155 return; 156 } 157 158 mSessions.delete(sessionId); 159 } 160 161 /** 162 * Destroy the discovery session: terminates discovery and frees up 163 * resources. 164 * 165 * @param sessionId The session ID of the session to be destroyed. 166 */ 167 public void terminateSession(int sessionId) { 168 WifiAwareDiscoverySessionState session = mSessions.get(sessionId); 169 if (session == null) { 170 Log.e(TAG, "terminateSession: sessionId doesn't exist - " + sessionId); 171 return; 172 } 173 174 session.terminate(); 175 mSessions.delete(sessionId); 176 } 177 178 /** 179 * Retrieve a session. 180 * 181 * @param sessionId Session ID of the session to be retrieved. 182 * @return Session or null if there's no session corresponding to the 183 * sessionId. 184 */ 185 public WifiAwareDiscoverySessionState getSession(int sessionId) { 186 return mSessions.get(sessionId); 187 } 188 189 /** 190 * Called to dispatch the Aware interface address change to the client - as an 191 * identity change (interface address information not propagated to client - 192 * privacy concerns). 193 * 194 * @param mac The new MAC address of the discovery interface - optionally propagated to the 195 * client. 196 */ 197 public void onInterfaceAddressChange(byte[] mac) { 198 if (VDBG) { 199 Log.v(TAG, 200 "onInterfaceAddressChange: mClientId=" + mClientId + ", mNotifyIdentityChange=" 201 + mNotifyIdentityChange + ", mac=" + String.valueOf( 202 HexEncoding.encode(mac)) + ", mLastDiscoveryInterfaceMac=" 203 + String.valueOf(HexEncoding.encode(mLastDiscoveryInterfaceMac))); 204 } 205 if (mNotifyIdentityChange && !Arrays.equals(mac, mLastDiscoveryInterfaceMac)) { 206 try { 207 boolean hasPermission = hasLocationingPermission(); 208 if (VDBG) Log.v(TAG, "hasPermission=" + hasPermission); 209 mCallback.onIdentityChanged(hasPermission ? mac : ALL_ZERO_MAC); 210 } catch (RemoteException e) { 211 Log.w(TAG, "onIdentityChanged: RemoteException - ignored: " + e); 212 } 213 } 214 215 mLastDiscoveryInterfaceMac = mac; 216 } 217 218 /** 219 * Called to dispatch the Aware cluster change (due to joining of a new 220 * cluster or starting a cluster) to the client - as an identity change 221 * (interface address information not propagated to client - privacy 222 * concerns). Dispatched if the client registered for the identity changed 223 * event. 224 * 225 * @param mac The cluster ID of the cluster started or joined. 226 * @param currentDiscoveryInterfaceMac The MAC address of the discovery interface. 227 */ 228 public void onClusterChange(int flag, byte[] mac, byte[] currentDiscoveryInterfaceMac) { 229 if (VDBG) { 230 Log.v(TAG, 231 "onClusterChange: mClientId=" + mClientId + ", mNotifyIdentityChange=" 232 + mNotifyIdentityChange + ", mac=" + String.valueOf( 233 HexEncoding.encode(mac)) + ", currentDiscoveryInterfaceMac=" 234 + String.valueOf(HexEncoding.encode(currentDiscoveryInterfaceMac)) 235 + ", mLastDiscoveryInterfaceMac=" + String.valueOf( 236 HexEncoding.encode(mLastDiscoveryInterfaceMac))); 237 } 238 if (mNotifyIdentityChange && !Arrays.equals(currentDiscoveryInterfaceMac, 239 mLastDiscoveryInterfaceMac)) { 240 try { 241 boolean hasPermission = hasLocationingPermission(); 242 if (VDBG) Log.v(TAG, "hasPermission=" + hasPermission); 243 mCallback.onIdentityChanged(hasPermission ? mac : ALL_ZERO_MAC); 244 } catch (RemoteException e) { 245 Log.w(TAG, "onIdentityChanged: RemoteException - ignored: " + e); 246 } 247 } 248 249 mLastDiscoveryInterfaceMac = currentDiscoveryInterfaceMac; 250 } 251 252 private boolean hasLocationingPermission() { 253 // FINE provides COARSE, so only have to check for the latter 254 return mContext.checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION, mPid, mUid) 255 == PackageManager.PERMISSION_GRANTED && mAppOps.noteOp( 256 AppOpsManager.OP_COARSE_LOCATION, mUid, mCallingPackage) 257 == AppOpsManager.MODE_ALLOWED; 258 } 259 260 /** 261 * Called on RTT success - forwards call to client. 262 */ 263 public void onRangingSuccess(int rangingId, RttManager.ParcelableRttResults results) { 264 if (VDBG) { 265 Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", results=" + results); 266 } 267 try { 268 mCallback.onRangingSuccess(rangingId, results); 269 } catch (RemoteException e) { 270 Log.w(TAG, "onRangingSuccess: RemoteException - ignored: " + e); 271 } 272 } 273 274 /** 275 * Called on RTT failure - forwards call to client. 276 */ 277 public void onRangingFailure(int rangingId, int reason, String description) { 278 if (VDBG) { 279 Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", reason=" + reason 280 + ", description=" + description); 281 } 282 try { 283 mCallback.onRangingFailure(rangingId, reason, description); 284 } catch (RemoteException e) { 285 Log.w(TAG, "onRangingFailure: RemoteException - ignored: " + e); 286 } 287 } 288 289 /** 290 * Called on RTT operation aborted - forwards call to client. 291 */ 292 public void onRangingAborted(int rangingId) { 293 if (VDBG) Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId); 294 try { 295 mCallback.onRangingAborted(rangingId); 296 } catch (RemoteException e) { 297 Log.w(TAG, "onRangingAborted: RemoteException - ignored: " + e); 298 } 299 } 300 301 /** 302 * Dump the internal state of the class. 303 */ 304 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 305 pw.println("AwareClientState:"); 306 pw.println(" mClientId: " + mClientId); 307 pw.println(" mConfigRequest: " + mConfigRequest); 308 pw.println(" mNotifyIdentityChange: " + mNotifyIdentityChange); 309 pw.println(" mCallback: " + mCallback); 310 pw.println(" mSessions: [" + mSessions + "]"); 311 for (int i = 0; i < mSessions.size(); ++i) { 312 mSessions.valueAt(i).dump(fd, pw, args); 313 } 314 } 315 } 316