WifiAwareClientState.java revision 475637585ac0b80e5c9c3a137c722f1d88916d51
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
17package com.android.server.wifi.aware;
18
19import android.Manifest;
20import android.app.AppOpsManager;
21import android.content.Context;
22import android.content.pm.PackageManager;
23import android.net.wifi.RttManager;
24import android.net.wifi.aware.ConfigRequest;
25import android.net.wifi.aware.IWifiAwareEventCallback;
26import android.os.RemoteException;
27import android.util.Log;
28import android.util.SparseArray;
29
30import libcore.util.HexEncoding;
31
32import java.io.FileDescriptor;
33import java.io.PrintWriter;
34import 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 */
45public 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(
244                        hasPermission ? currentDiscoveryInterfaceMac : ALL_ZERO_MAC);
245            } catch (RemoteException e) {
246                Log.w(TAG, "onIdentityChanged: RemoteException - ignored: " + e);
247            }
248        }
249
250        mLastDiscoveryInterfaceMac = currentDiscoveryInterfaceMac;
251    }
252
253    private boolean hasLocationingPermission() {
254        // FINE provides COARSE, so only have to check for the latter
255        return mContext.checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION, mPid, mUid)
256                == PackageManager.PERMISSION_GRANTED && mAppOps.noteOp(
257                AppOpsManager.OP_COARSE_LOCATION, mUid, mCallingPackage)
258                == AppOpsManager.MODE_ALLOWED;
259    }
260
261    /**
262     * Called on RTT success - forwards call to client.
263     */
264    public void onRangingSuccess(int rangingId, RttManager.ParcelableRttResults results) {
265        if (VDBG) {
266            Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", results=" + results);
267        }
268        try {
269            mCallback.onRangingSuccess(rangingId, results);
270        } catch (RemoteException e) {
271            Log.w(TAG, "onRangingSuccess: RemoteException - ignored: " + e);
272        }
273    }
274
275    /**
276     * Called on RTT failure - forwards call to client.
277     */
278    public void onRangingFailure(int rangingId, int reason, String description) {
279        if (VDBG) {
280            Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", reason=" + reason
281                    + ", description=" + description);
282        }
283        try {
284            mCallback.onRangingFailure(rangingId, reason, description);
285        } catch (RemoteException e) {
286            Log.w(TAG, "onRangingFailure: RemoteException - ignored: " + e);
287        }
288    }
289
290    /**
291     * Called on RTT operation aborted - forwards call to client.
292     */
293    public void onRangingAborted(int rangingId) {
294        if (VDBG) Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId);
295        try {
296            mCallback.onRangingAborted(rangingId);
297        } catch (RemoteException e) {
298            Log.w(TAG, "onRangingAborted: RemoteException - ignored: " + e);
299        }
300    }
301
302    /**
303     * Dump the internal state of the class.
304     */
305    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
306        pw.println("AwareClientState:");
307        pw.println("  mClientId: " + mClientId);
308        pw.println("  mConfigRequest: " + mConfigRequest);
309        pw.println("  mNotifyIdentityChange: " + mNotifyIdentityChange);
310        pw.println("  mCallback: " + mCallback);
311        pw.println("  mSessions: [" + mSessions + "]");
312        for (int i = 0; i < mSessions.size(); ++i) {
313            mSessions.valueAt(i).dump(fd, pw, args);
314        }
315    }
316}
317