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