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