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