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 
19 import android.annotation.NonNull;
20 import android.content.Context;
21 import android.hardware.wifi.hostapd.V1_0.HostapdStatus;
22 import android.hardware.wifi.hostapd.V1_0.HostapdStatusCode;
23 import android.hardware.wifi.hostapd.V1_0.IHostapd;
24 import android.hidl.manager.V1_0.IServiceManager;
25 import android.hidl.manager.V1_0.IServiceNotification;
26 import android.net.wifi.WifiConfiguration;
27 import android.os.Handler;
28 import android.os.HwRemoteBinder;
29 import android.os.Looper;
30 import android.os.RemoteException;
31 import android.util.Log;
32 
33 import com.android.internal.R;
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.server.wifi.WifiNative.HostapdDeathEventHandler;
36 import com.android.server.wifi.util.NativeUtil;
37 
38 import java.util.ArrayList;
39 import java.util.HashMap;
40 import java.util.List;
41 import java.util.NoSuchElementException;
42 
43 import javax.annotation.concurrent.ThreadSafe;
44 
45 /**
46  * To maintain thread-safety, the locking protocol is that every non-static method (regardless of
47  * access level) acquires mLock.
48  */
49 @ThreadSafe
50 public class HostapdHal {
51     private static final String TAG = "HostapdHal";
52     @VisibleForTesting
53     public static final String HAL_INSTANCE_NAME = "default";
54 
55     private final Object mLock = new Object();
56     private boolean mVerboseLoggingEnabled = false;
57     private final Handler mEventHandler;
58     private final boolean mEnableAcs;
59     private final boolean mEnableIeee80211AC;
60     private final List<android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange>
61             mAcsChannelRanges;
62 
63     // Hostapd HAL interface objects
64     private IServiceManager mIServiceManager = null;
65     private IHostapd mIHostapd;
66     private HashMap<String, WifiNative.SoftApListener> mSoftApListeners = new HashMap<>();
67     private HostapdDeathEventHandler mDeathEventHandler;
68     private ServiceManagerDeathRecipient mServiceManagerDeathRecipient;
69     private HostapdDeathRecipient mHostapdDeathRecipient;
70     // Death recipient cookie registered for current supplicant instance.
71     private long mDeathRecipientCookie = 0;
72 
73     private final IServiceNotification mServiceNotificationCallback =
74             new IServiceNotification.Stub() {
75         public void onRegistration(String fqName, String name, boolean preexisting) {
76             synchronized (mLock) {
77                 if (mVerboseLoggingEnabled) {
78                     Log.i(TAG, "IServiceNotification.onRegistration for: " + fqName
79                             + ", " + name + " preexisting=" + preexisting);
80                 }
81                 if (!initHostapdService()) {
82                     Log.e(TAG, "initalizing IHostapd failed.");
83                     hostapdServiceDiedHandler(mDeathRecipientCookie);
84                 } else {
85                     Log.i(TAG, "Completed initialization of IHostapd.");
86                 }
87             }
88         }
89     };
90     private class ServiceManagerDeathRecipient implements HwRemoteBinder.DeathRecipient {
91         @Override
serviceDied(long cookie)92         public void serviceDied(long cookie) {
93             mEventHandler.post(() -> {
94                 synchronized (mLock) {
95                     Log.w(TAG, "IServiceManager died: cookie=" + cookie);
96                     hostapdServiceDiedHandler(mDeathRecipientCookie);
97                     mIServiceManager = null; // Will need to register a new ServiceNotification
98                 }
99             });
100         }
101     }
102     private class HostapdDeathRecipient implements HwRemoteBinder.DeathRecipient {
103         @Override
serviceDied(long cookie)104         public void serviceDied(long cookie) {
105             mEventHandler.post(() -> {
106                 synchronized (mLock) {
107                     Log.w(TAG, "IHostapd/IHostapd died: cookie=" + cookie);
108                     hostapdServiceDiedHandler(cookie);
109                 }
110             });
111         }
112     }
113 
HostapdHal(Context context, Looper looper)114     public HostapdHal(Context context, Looper looper) {
115         mEventHandler = new Handler(looper);
116         mEnableAcs = context.getResources().getBoolean(R.bool.config_wifi_softap_acs_supported);
117         mEnableIeee80211AC =
118                 context.getResources().getBoolean(R.bool.config_wifi_softap_ieee80211ac_supported);
119         mAcsChannelRanges = toAcsChannelRanges(context.getResources().getString(
120                 R.string.config_wifi_softap_acs_supported_channel_list));
121 
122         mServiceManagerDeathRecipient = new ServiceManagerDeathRecipient();
123         mHostapdDeathRecipient = new HostapdDeathRecipient();
124     }
125 
126     /**
127      * Enable/Disable verbose logging.
128      *
129      * @param enable true to enable, false to disable.
130      */
enableVerboseLogging(boolean enable)131     void enableVerboseLogging(boolean enable) {
132         synchronized (mLock) {
133             mVerboseLoggingEnabled = enable;
134         }
135     }
136 
137     /**
138      * Uses the IServiceManager to check if the device is running V1_1 of the HAL from the VINTF for
139      * the device.
140      * @return true if supported, false otherwise.
141      */
isV1_1()142     private boolean isV1_1() {
143         synchronized (mLock) {
144             if (mIServiceManager == null) {
145                 Log.e(TAG, "isV1_1: called but mServiceManager is null!?");
146                 return false;
147             }
148             try {
149                 return (mIServiceManager.getTransport(
150                         android.hardware.wifi.hostapd.V1_1.IHostapd.kInterfaceName,
151                         HAL_INSTANCE_NAME)
152                         != IServiceManager.Transport.EMPTY);
153             } catch (RemoteException e) {
154                 Log.e(TAG, "Exception while operating on IServiceManager: " + e);
155                 handleRemoteException(e, "getTransport");
156                 return false;
157             }
158         }
159     }
160 
161     /**
162      * Link to death for IServiceManager object.
163      * @return true on success, false otherwise.
164      */
linkToServiceManagerDeath()165     private boolean linkToServiceManagerDeath() {
166         synchronized (mLock) {
167             if (mIServiceManager == null) return false;
168             try {
169                 if (!mIServiceManager.linkToDeath(mServiceManagerDeathRecipient, 0)) {
170                     Log.wtf(TAG, "Error on linkToDeath on IServiceManager");
171                     hostapdServiceDiedHandler(mDeathRecipientCookie);
172                     mIServiceManager = null; // Will need to register a new ServiceNotification
173                     return false;
174                 }
175             } catch (RemoteException e) {
176                 Log.e(TAG, "IServiceManager.linkToDeath exception", e);
177                 mIServiceManager = null; // Will need to register a new ServiceNotification
178                 return false;
179             }
180             return true;
181         }
182     }
183 
184     /**
185      * Registers a service notification for the IHostapd service, which triggers intialization of
186      * the IHostapd
187      * @return true if the service notification was successfully registered
188      */
initialize()189     public boolean initialize() {
190         synchronized (mLock) {
191             if (mVerboseLoggingEnabled) {
192                 Log.i(TAG, "Registering IHostapd service ready callback.");
193             }
194             mIHostapd = null;
195             if (mIServiceManager != null) {
196                 // Already have an IServiceManager and serviceNotification registered, don't
197                 // don't register another.
198                 return true;
199             }
200             try {
201                 mIServiceManager = getServiceManagerMockable();
202                 if (mIServiceManager == null) {
203                     Log.e(TAG, "Failed to get HIDL Service Manager");
204                     return false;
205                 }
206                 if (!linkToServiceManagerDeath()) {
207                     return false;
208                 }
209                 /* TODO(b/33639391) : Use the new IHostapd.registerForNotifications() once it
210                    exists */
211                 if (!mIServiceManager.registerForNotifications(
212                         IHostapd.kInterfaceName, "", mServiceNotificationCallback)) {
213                     Log.e(TAG, "Failed to register for notifications to "
214                             + IHostapd.kInterfaceName);
215                     mIServiceManager = null; // Will need to register a new ServiceNotification
216                     return false;
217                 }
218             } catch (RemoteException e) {
219                 Log.e(TAG, "Exception while trying to register a listener for IHostapd service: "
220                         + e);
221                 hostapdServiceDiedHandler(mDeathRecipientCookie);
222                 mIServiceManager = null; // Will need to register a new ServiceNotification
223                 return false;
224             }
225             return true;
226         }
227     }
228 
229     /**
230      * Link to death for IHostapd object.
231      * @return true on success, false otherwise.
232      */
linkToHostapdDeath()233     private boolean linkToHostapdDeath() {
234         synchronized (mLock) {
235             if (mIHostapd == null) return false;
236             try {
237                 if (!mIHostapd.linkToDeath(mHostapdDeathRecipient, ++mDeathRecipientCookie)) {
238                     Log.wtf(TAG, "Error on linkToDeath on IHostapd");
239                     hostapdServiceDiedHandler(mDeathRecipientCookie);
240                     return false;
241                 }
242             } catch (RemoteException e) {
243                 Log.e(TAG, "IHostapd.linkToDeath exception", e);
244                 return false;
245             }
246             return true;
247         }
248     }
249 
registerCallback( android.hardware.wifi.hostapd.V1_1.IHostapdCallback callback)250     private boolean registerCallback(
251             android.hardware.wifi.hostapd.V1_1.IHostapdCallback callback) {
252         synchronized (mLock) {
253             String methodStr = "registerCallback_1_1";
254             try {
255                 android.hardware.wifi.hostapd.V1_1.IHostapd iHostapdV1_1 = getHostapdMockableV1_1();
256                 if (iHostapdV1_1 == null) return false;
257                 HostapdStatus status =  iHostapdV1_1.registerCallback(callback);
258                 return checkStatusAndLogFailure(status, methodStr);
259             } catch (RemoteException e) {
260                 handleRemoteException(e, methodStr);
261                 return false;
262             }
263         }
264     }
265 
266     /**
267      * Initialize the IHostapd object.
268      * @return true on success, false otherwise.
269      */
initHostapdService()270     private boolean initHostapdService() {
271         synchronized (mLock) {
272             try {
273                 mIHostapd = getHostapdMockable();
274             } catch (RemoteException e) {
275                 Log.e(TAG, "IHostapd.getService exception: " + e);
276                 return false;
277             } catch (NoSuchElementException e) {
278                 Log.e(TAG, "IHostapd.getService exception: " + e);
279                 return false;
280             }
281             if (mIHostapd == null) {
282                 Log.e(TAG, "Got null IHostapd service. Stopping hostapd HIDL startup");
283                 return false;
284             }
285             if (!linkToHostapdDeath()) {
286                 mIHostapd = null;
287                 return false;
288             }
289             // Register for callbacks for 1.1 hostapd.
290             if (isV1_1() && !registerCallback(new HostapdCallback())) {
291                 mIHostapd = null;
292                 return false;
293             }
294         }
295         return true;
296     }
297 
298     /**
299      * Add and start a new access point.
300      *
301      * @param ifaceName Name of the interface.
302      * @param config Configuration to use for the AP.
303      * @param listener Callback for AP events.
304      * @return true on success, false otherwise.
305      */
addAccessPoint(@onNull String ifaceName, @NonNull WifiConfiguration config, @NonNull WifiNative.SoftApListener listener)306     public boolean addAccessPoint(@NonNull String ifaceName, @NonNull WifiConfiguration config,
307                                   @NonNull WifiNative.SoftApListener listener) {
308         synchronized (mLock) {
309             final String methodStr = "addAccessPoint";
310             IHostapd.IfaceParams ifaceParams = new IHostapd.IfaceParams();
311             ifaceParams.ifaceName = ifaceName;
312             ifaceParams.hwModeParams.enable80211N = true;
313             ifaceParams.hwModeParams.enable80211AC = mEnableIeee80211AC;
314             try {
315                 ifaceParams.channelParams.band = getBand(config);
316             } catch (IllegalArgumentException e) {
317                 Log.e(TAG, "Unrecognized apBand " + config.apBand);
318                 return false;
319             }
320             if (mEnableAcs) {
321                 ifaceParams.channelParams.enableAcs = true;
322                 ifaceParams.channelParams.acsShouldExcludeDfs = true;
323             } else {
324                 // Downgrade IHostapd.Band.BAND_ANY to IHostapd.Band.BAND_2_4_GHZ if ACS
325                 // is not supported.
326                 // We should remove this workaround once channel selection is moved from
327                 // ApConfigUtil to here.
328                 if (ifaceParams.channelParams.band == IHostapd.Band.BAND_ANY) {
329                     Log.d(TAG, "ACS is not supported on this device, using 2.4 GHz band.");
330                     ifaceParams.channelParams.band = IHostapd.Band.BAND_2_4_GHZ;
331                 }
332                 ifaceParams.channelParams.enableAcs = false;
333                 ifaceParams.channelParams.channel = config.apChannel;
334             }
335 
336             IHostapd.NetworkParams nwParams = new IHostapd.NetworkParams();
337             // TODO(b/67745880) Note that config.SSID is intended to be either a
338             // hex string or "double quoted".
339             // However, it seems that whatever is handing us these configurations does not obey
340             // this convention.
341             nwParams.ssid.addAll(NativeUtil.stringToByteArrayList(config.SSID));
342             nwParams.isHidden = config.hiddenSSID;
343             nwParams.encryptionType = getEncryptionType(config);
344             nwParams.pskPassphrase = (config.preSharedKey != null) ? config.preSharedKey : "";
345             if (!checkHostapdAndLogFailure(methodStr)) return false;
346             try {
347                 HostapdStatus status;
348                 if (isV1_1()) {
349                     android.hardware.wifi.hostapd.V1_1.IHostapd.IfaceParams ifaceParams1_1 =
350                             new android.hardware.wifi.hostapd.V1_1.IHostapd.IfaceParams();
351                     ifaceParams1_1.V1_0 = ifaceParams;
352                     if (mEnableAcs) {
353                         ifaceParams1_1.channelParams.acsChannelRanges.addAll(mAcsChannelRanges);
354                     }
355                     android.hardware.wifi.hostapd.V1_1.IHostapd iHostapdV1_1 =
356                             getHostapdMockableV1_1();
357                     if (iHostapdV1_1 == null) return false;
358                     status = iHostapdV1_1.addAccessPoint_1_1(ifaceParams1_1, nwParams);
359                 } else {
360                     status = mIHostapd.addAccessPoint(ifaceParams, nwParams);
361                 }
362                 if (!checkStatusAndLogFailure(status, methodStr)) {
363                     return false;
364                 }
365                 mSoftApListeners.put(ifaceName, listener);
366                 return true;
367             } catch (RemoteException e) {
368                 handleRemoteException(e, methodStr);
369                 return false;
370             }
371         }
372     }
373 
374     /**
375      * Remove a previously started access point.
376      *
377      * @param ifaceName Name of the interface.
378      * @return true on success, false otherwise.
379      */
removeAccessPoint(@onNull String ifaceName)380     public boolean removeAccessPoint(@NonNull String ifaceName) {
381         synchronized (mLock) {
382             final String methodStr = "removeAccessPoint";
383             if (!checkHostapdAndLogFailure(methodStr)) return false;
384             try {
385                 HostapdStatus status = mIHostapd.removeAccessPoint(ifaceName);
386                 if (!checkStatusAndLogFailure(status, methodStr)) {
387                     return false;
388                 }
389                 mSoftApListeners.remove(ifaceName);
390                 return true;
391             } catch (RemoteException e) {
392                 handleRemoteException(e, methodStr);
393                 return false;
394             }
395         }
396     }
397 
398     /**
399      * Registers a death notification for hostapd.
400      * @return Returns true on success.
401      */
registerDeathHandler(@onNull HostapdDeathEventHandler handler)402     public boolean registerDeathHandler(@NonNull HostapdDeathEventHandler handler) {
403         if (mDeathEventHandler != null) {
404             Log.e(TAG, "Death handler already present");
405         }
406         mDeathEventHandler = handler;
407         return true;
408     }
409 
410     /**
411      * Deregisters a death notification for hostapd.
412      * @return Returns true on success.
413      */
deregisterDeathHandler()414     public boolean deregisterDeathHandler() {
415         if (mDeathEventHandler == null) {
416             Log.e(TAG, "No Death handler present");
417         }
418         mDeathEventHandler = null;
419         return true;
420     }
421 
422     /**
423      * Clear internal state.
424      */
clearState()425     private void clearState() {
426         synchronized (mLock) {
427             mIHostapd = null;
428         }
429     }
430 
431     /**
432      * Handle hostapd death.
433      */
hostapdServiceDiedHandler(long cookie)434     private void hostapdServiceDiedHandler(long cookie) {
435         synchronized (mLock) {
436             if (mDeathRecipientCookie != cookie) {
437                 Log.i(TAG, "Ignoring stale death recipient notification");
438                 return;
439             }
440             clearState();
441             if (mDeathEventHandler != null) {
442                 mDeathEventHandler.onDeath();
443             }
444         }
445     }
446 
447     /**
448      * Signals whether Initialization completed successfully.
449      */
isInitializationStarted()450     public boolean isInitializationStarted() {
451         synchronized (mLock) {
452             return mIServiceManager != null;
453         }
454     }
455 
456     /**
457      * Signals whether Initialization completed successfully.
458      */
isInitializationComplete()459     public boolean isInitializationComplete() {
460         synchronized (mLock) {
461             return mIHostapd != null;
462         }
463     }
464 
465     /**
466      * Start the hostapd daemon.
467      *
468      * @return true on success, false otherwise.
469      */
startDaemon()470     public boolean startDaemon() {
471         synchronized (mLock) {
472             try {
473                 // This should startup hostapd daemon using the lazy start HAL mechanism.
474                 getHostapdMockable();
475             } catch (RemoteException e) {
476                 Log.e(TAG, "Exception while trying to start hostapd: "
477                         + e);
478                 hostapdServiceDiedHandler(mDeathRecipientCookie);
479                 return false;
480             } catch (NoSuchElementException e) {
481                 // We're starting the daemon, so expect |NoSuchElementException|.
482                 Log.d(TAG, "Successfully triggered start of hostapd using HIDL");
483             }
484             return true;
485         }
486     }
487 
488     /**
489      * Terminate the hostapd daemon.
490      */
terminate()491     public void terminate() {
492         synchronized (mLock) {
493             final String methodStr = "terminate";
494             if (!checkHostapdAndLogFailure(methodStr)) return;
495             try {
496                 mIHostapd.terminate();
497             } catch (RemoteException e) {
498                 handleRemoteException(e, methodStr);
499             }
500         }
501     }
502 
503     /**
504      * Wrapper functions to access static HAL methods, created to be mockable in unit tests
505      */
506     @VisibleForTesting
getServiceManagerMockable()507     protected IServiceManager getServiceManagerMockable() throws RemoteException {
508         synchronized (mLock) {
509             return IServiceManager.getService();
510         }
511     }
512 
513     @VisibleForTesting
getHostapdMockable()514     protected IHostapd getHostapdMockable() throws RemoteException {
515         synchronized (mLock) {
516             return IHostapd.getService();
517         }
518     }
519 
520     @VisibleForTesting
getHostapdMockableV1_1()521     protected android.hardware.wifi.hostapd.V1_1.IHostapd getHostapdMockableV1_1()
522             throws RemoteException {
523         synchronized (mLock) {
524             try {
525                 return android.hardware.wifi.hostapd.V1_1.IHostapd.castFrom(mIHostapd);
526             } catch (NoSuchElementException e) {
527                 Log.e(TAG, "Failed to get IHostapd", e);
528                 return null;
529             }
530         }
531     }
532 
getEncryptionType(WifiConfiguration localConfig)533     private static int getEncryptionType(WifiConfiguration localConfig) {
534         int encryptionType;
535         switch (localConfig.getAuthType()) {
536             case WifiConfiguration.KeyMgmt.NONE:
537                 encryptionType = IHostapd.EncryptionType.NONE;
538                 break;
539             case WifiConfiguration.KeyMgmt.WPA_PSK:
540                 encryptionType = IHostapd.EncryptionType.WPA;
541                 break;
542             case WifiConfiguration.KeyMgmt.WPA2_PSK:
543                 encryptionType = IHostapd.EncryptionType.WPA2;
544                 break;
545             default:
546                 // We really shouldn't default to None, but this was how NetworkManagementService
547                 // used to do this.
548                 encryptionType = IHostapd.EncryptionType.NONE;
549                 break;
550         }
551         return encryptionType;
552     }
553 
getBand(WifiConfiguration localConfig)554     private static int getBand(WifiConfiguration localConfig) {
555         int bandType;
556         switch (localConfig.apBand) {
557             case WifiConfiguration.AP_BAND_2GHZ:
558                 bandType = IHostapd.Band.BAND_2_4_GHZ;
559                 break;
560             case WifiConfiguration.AP_BAND_5GHZ:
561                 bandType = IHostapd.Band.BAND_5_GHZ;
562                 break;
563             case WifiConfiguration.AP_BAND_ANY:
564                 bandType = IHostapd.Band.BAND_ANY;
565                 break;
566             default:
567                 throw new IllegalArgumentException();
568         }
569         return bandType;
570     }
571 
572     /**
573      * Convert channel list string like '1-6,11' to list of AcsChannelRanges
574      */
575     private List<android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange>
toAcsChannelRanges(String channelListStr)576             toAcsChannelRanges(String channelListStr) {
577         ArrayList<android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange> acsChannelRanges =
578                 new ArrayList<>();
579         String[] channelRanges = channelListStr.split(",");
580         for (String channelRange : channelRanges) {
581             android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange acsChannelRange =
582                     new android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange();
583             try {
584                 if (channelRange.contains("-")) {
585                     String[] channels  = channelRange.split("-");
586                     if (channels.length != 2) {
587                         Log.e(TAG, "Unrecognized channel range, length is " + channels.length);
588                         continue;
589                     }
590                     int start = Integer.parseInt(channels[0]);
591                     int end = Integer.parseInt(channels[1]);
592                     if (start > end) {
593                         Log.e(TAG, "Invalid channel range, from " + start + " to " + end);
594                         continue;
595                     }
596                     acsChannelRange.start = start;
597                     acsChannelRange.end = end;
598                 } else {
599                     acsChannelRange.start = Integer.parseInt(channelRange);
600                     acsChannelRange.end = acsChannelRange.start;
601                 }
602             } catch (NumberFormatException e) {
603                 // Ignore malformed value
604                 Log.e(TAG, "Malformed channel value detected: " + e);
605                 continue;
606             }
607             acsChannelRanges.add(acsChannelRange);
608         }
609         return acsChannelRanges;
610     }
611 
612     /**
613      * Returns false if Hostapd is null, and logs failure to call methodStr
614      */
checkHostapdAndLogFailure(String methodStr)615     private boolean checkHostapdAndLogFailure(String methodStr) {
616         synchronized (mLock) {
617             if (mIHostapd == null) {
618                 Log.e(TAG, "Can't call " + methodStr + ", IHostapd is null");
619                 return false;
620             }
621             return true;
622         }
623     }
624 
625     /**
626      * Returns true if provided status code is SUCCESS, logs debug message and returns false
627      * otherwise
628      */
checkStatusAndLogFailure(HostapdStatus status, String methodStr)629     private boolean checkStatusAndLogFailure(HostapdStatus status,
630             String methodStr) {
631         synchronized (mLock) {
632             if (status.code != HostapdStatusCode.SUCCESS) {
633                 Log.e(TAG, "IHostapd." + methodStr + " failed: " + status.code
634                         + ", " + status.debugMessage);
635                 return false;
636             } else {
637                 if (mVerboseLoggingEnabled) {
638                     Log.d(TAG, "IHostapd." + methodStr + " succeeded");
639                 }
640                 return true;
641             }
642         }
643     }
644 
handleRemoteException(RemoteException e, String methodStr)645     private void handleRemoteException(RemoteException e, String methodStr) {
646         synchronized (mLock) {
647             hostapdServiceDiedHandler(mDeathRecipientCookie);
648             Log.e(TAG, "IHostapd." + methodStr + " failed with exception", e);
649         }
650     }
651 
652     private class HostapdCallback extends
653             android.hardware.wifi.hostapd.V1_1.IHostapdCallback.Stub {
654         @Override
onFailure(String ifaceName)655         public void onFailure(String ifaceName) {
656             Log.w(TAG, "Failure on iface " + ifaceName);
657             WifiNative.SoftApListener listener = mSoftApListeners.get(ifaceName);
658             if (listener != null) {
659                 listener.onFailure();
660             }
661         }
662     }
663 }
664