1 /*
2  * Copyright (C) 2017 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 package com.android.server.wifi;
17 
18 import android.annotation.NonNull;
19 import android.content.Context;
20 import android.net.MacAddress;
21 import android.net.wifi.SoftApConfiguration;
22 import android.net.wifi.WifiManager;
23 import android.os.Handler;
24 import android.util.Log;
25 
26 import com.android.internal.annotations.VisibleForTesting;
27 import com.android.server.wifi.WifiNative.HostapdDeathEventHandler;
28 import com.android.server.wifi.WifiNative.SoftApHalCallback;
29 
30 import java.io.PrintWriter;
31 
32 import javax.annotation.concurrent.ThreadSafe;
33 
34 /**
35  * To maintain thread-safety, the locking protocol is that every non-static method (regardless of
36  * access level) acquires mLock.
37  */
38 @ThreadSafe
39 public class HostapdHal {
40     private static final String TAG = "HostapdHal";
41 
42     private final Object mLock = new Object();
43     private boolean mVerboseLoggingEnabled = false;
44     private boolean mVerboseHalLoggingEnabled = false;
45     private final Context mContext;
46     private final Handler mEventHandler;
47 
48     // Hostapd HAL interface object - might be implemented by HIDL or AIDL
49     private IHostapdHal mIHostapd;
50 
HostapdHal(Context context, Handler handler)51     public HostapdHal(Context context, Handler handler) {
52         mContext = context;
53         mEventHandler = handler;
54     }
55 
56     /**
57      * Enable/Disable verbose logging.
58      */
enableVerboseLogging(boolean verboseEnabled, boolean halVerboseEnabled)59     public void enableVerboseLogging(boolean verboseEnabled, boolean halVerboseEnabled) {
60         synchronized (mLock) {
61             mVerboseLoggingEnabled = verboseEnabled;
62             mVerboseHalLoggingEnabled = halVerboseEnabled;
63             if (mIHostapd != null) {
64                 mIHostapd.enableVerboseLogging(verboseEnabled, halVerboseEnabled);
65             }
66         }
67     }
68 
69     /**
70      * Initialize the HostapdHal. Creates the internal IHostapdHal object
71      * and calls its initialize method.
72      *
73      * @return true if the initialization succeeded
74      */
initialize()75     public boolean initialize() {
76         synchronized (mLock) {
77             if (mVerboseLoggingEnabled) {
78                 Log.i(TAG, "Initializing Hostapd Service.");
79             }
80             if (mIHostapd != null) {
81                 Log.wtf(TAG, "Hostapd HAL has already been initialized.");
82                 return false;
83             }
84             mIHostapd = createIHostapdHalMockable();
85             if (mIHostapd == null) {
86                 Log.e(TAG, "Failed to get Hostapd HAL instance");
87                 return false;
88             }
89             mIHostapd.enableVerboseLogging(mVerboseLoggingEnabled, mVerboseHalLoggingEnabled);
90             if (!mIHostapd.initialize()) {
91                 Log.e(TAG, "Fail to init hostapd, Stopping hostapd startup");
92                 mIHostapd = null;
93                 return false;
94             }
95             return true;
96         }
97     }
98 
99     /**
100      * Wrapper function to create the IHostapdHal object. Created to be mockable in unit tests.
101      */
102     @VisibleForTesting
createIHostapdHalMockable()103     protected IHostapdHal createIHostapdHalMockable() {
104         synchronized (mLock) {
105             // Prefer AIDL implementation if service is declared.
106             if (HostapdHalAidlImp.serviceDeclared()) {
107                 Log.i(TAG, "Initializing hostapd using AIDL implementation.");
108                 return new HostapdHalAidlImp(mContext, mEventHandler);
109 
110             } else if (HostapdHalHidlImp.serviceDeclared()) {
111                 Log.i(TAG, "Initializing hostapd using HIDL implementation.");
112                 return new HostapdHalHidlImp(mContext, mEventHandler);
113             }
114             Log.e(TAG, "No HIDL or AIDL service available for hostapd.");
115             return null;
116         }
117     }
118 
119     /**
120      * Returns whether or not the hostapd supports getting the AP info from the callback.
121      */
isApInfoCallbackSupported()122     public boolean isApInfoCallbackSupported() {
123         synchronized (mLock) {
124             String methodStr = "isApInfoCallbackSupported";
125             if (mIHostapd == null) {
126                 return handleNullIHostapd(methodStr);
127             }
128             return mIHostapd.isApInfoCallbackSupported();
129         }
130     }
131 
132     /**
133      * Register the provided callback handler for SoftAp events.
134      * <p>
135      * Note that only one callback can be registered at a time - any registration overrides previous
136      * registrations.
137      *
138      * @param ifaceName Name of the interface.
139      * @param listener Callback listener for AP events.
140      * @return true on success, false on failure.
141      */
registerApCallback(@onNull String ifaceName, @NonNull SoftApHalCallback callback)142     public boolean registerApCallback(@NonNull String ifaceName,
143             @NonNull SoftApHalCallback callback) {
144         synchronized (mLock) {
145             String methodStr = "registerApCallback";
146             if (mIHostapd == null) {
147                 return handleNullIHostapd(methodStr);
148             }
149             return mIHostapd.registerApCallback(ifaceName, callback);
150         }
151     }
152 
153     /**
154      * Add and start a new access point.
155      *
156      * @param ifaceName Name of the interface.
157      * @param config Configuration to use for the AP.
158      * @param isMetered Indicates the network is metered or not.
159      * @param onFailureListener A runnable to be triggered on failure.
160      * @return true on success, false otherwise.
161      */
addAccessPoint(@onNull String ifaceName, @NonNull SoftApConfiguration config, boolean isMetered, @NonNull Runnable onFailureListener)162     public boolean addAccessPoint(@NonNull String ifaceName, @NonNull SoftApConfiguration config,
163                                   boolean isMetered, @NonNull Runnable onFailureListener) {
164         synchronized (mLock) {
165             String methodStr = "addAccessPoint";
166             if (mIHostapd == null) {
167                 return handleNullIHostapd(methodStr);
168             }
169             return mIHostapd.addAccessPoint(ifaceName, config, isMetered, onFailureListener);
170         }
171     }
172 
173     /**
174      * Remove a previously started access point.
175      *
176      * @param ifaceName Name of the interface.
177      * @return true on success, false otherwise.
178      */
removeAccessPoint(@onNull String ifaceName)179     public boolean removeAccessPoint(@NonNull String ifaceName) {
180         synchronized (mLock) {
181             String methodStr = "removeAccessPoint";
182             if (mIHostapd == null) {
183                 return handleNullIHostapd(methodStr);
184             }
185             return mIHostapd.removeAccessPoint(ifaceName);
186         }
187     }
188 
189     /**
190      * Remove a previously connected client.
191      *
192      * @param ifaceName Name of the interface.
193      * @param client Mac Address of the client.
194      * @param reasonCode One of disconnect reason code which defined in {@link WifiManager}.
195      * @return true on success, false otherwise.
196      */
forceClientDisconnect(@onNull String ifaceName, @NonNull MacAddress client, int reasonCode)197     public boolean forceClientDisconnect(@NonNull String ifaceName,
198             @NonNull MacAddress client, int reasonCode) {
199         synchronized (mLock) {
200             String methodStr = "forceClientDisconnect";
201             if (mIHostapd == null) {
202                 return handleNullIHostapd(methodStr);
203             }
204             return mIHostapd.forceClientDisconnect(ifaceName, client, reasonCode);
205         }
206     }
207 
208     /**
209      * Registers a death notification for hostapd.
210      * @return Returns true on success.
211      */
registerDeathHandler(@onNull HostapdDeathEventHandler handler)212     public boolean registerDeathHandler(@NonNull HostapdDeathEventHandler handler) {
213         synchronized (mLock) {
214             String methodStr = "registerDeathHandler";
215             if (mIHostapd == null) {
216                 return handleNullIHostapd(methodStr);
217             }
218             return mIHostapd.registerDeathHandler(handler);
219         }
220     }
221 
222     /**
223      * Deregisters a death notification for hostapd.
224      * @return Returns true on success.
225      */
deregisterDeathHandler()226     public boolean deregisterDeathHandler() {
227         synchronized (mLock) {
228             String methodStr = "deregisterDeathHandler";
229             if (mIHostapd == null) {
230                 return handleNullIHostapd(methodStr);
231             }
232             return mIHostapd.deregisterDeathHandler();
233         }
234     }
235 
236     /**
237      * Signals whether Initialization completed successfully.
238      */
isInitializationStarted()239     public boolean isInitializationStarted() {
240         synchronized (mLock) {
241             String methodStr = "isInitializationStarted";
242             if (mIHostapd == null) {
243                 return handleNullIHostapd(methodStr);
244             }
245             return mIHostapd.isInitializationStarted();
246         }
247     }
248 
249     /**
250      * Signals whether Initialization completed successfully.
251      */
isInitializationComplete()252     public boolean isInitializationComplete() {
253         synchronized (mLock) {
254             String methodStr = "isInitializationComplete";
255             if (mIHostapd == null) {
256                 return handleNullIHostapd(methodStr);
257             }
258             return mIHostapd.isInitializationComplete();
259         }
260     }
261 
262     /**
263      * Start the hostapd daemon.
264      *
265      * @return true on success, false otherwise.
266      */
startDaemon()267     public boolean startDaemon() {
268         synchronized (mLock) {
269             String methodStr = "startDaemon";
270             if (mIHostapd == null) {
271                 return handleNullIHostapd(methodStr);
272             }
273             return mIHostapd.startDaemon();
274         }
275     }
276 
277     /**
278      * Terminate the hostapd daemon & wait for it's death.
279      */
terminate()280     public void terminate() {
281         synchronized (mLock) {
282             String methodStr = "terminate";
283             if (mIHostapd == null) {
284                 handleNullIHostapd(methodStr);
285                 return;
286             }
287             mIHostapd.terminate();
288         }
289     }
290 
handleNullIHostapd(String methodStr)291     private boolean handleNullIHostapd(String methodStr) {
292         Log.e(TAG, "Cannot call " + methodStr + " because mIHostapd is null.");
293         return false;
294     }
295 
dump(PrintWriter pw)296     protected void dump(PrintWriter pw) {
297         synchronized (mLock) {
298             pw.println("Dump of HostapdHal");
299             pw.println("AIDL service declared: " + HostapdHalAidlImp.serviceDeclared());
300             pw.println("HIDL service declared: " + HostapdHalHidlImp.serviceDeclared());
301             boolean initialized = mIHostapd != null;
302             pw.println("Initialized: " + initialized);
303             if (initialized) {
304                 pw.println("Implementation: " + mIHostapd.getClass().getSimpleName());
305                 mIHostapd.dump(pw);
306             }
307         }
308     }
309 
310     /**
311      * Returns whether the hostapd HAL supports reporting the single instance died event.
312      */
isSoftApInstanceDiedHandlerSupported()313     public boolean isSoftApInstanceDiedHandlerSupported() {
314         return (mIHostapd != null) && (mIHostapd instanceof HostapdHalAidlImp);
315     }
316 }
317