HalDeviceManager.java revision 84fc22eaa83774f25fcd3667655b1ff46677d0b8
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
17package com.android.server.wifi;
18
19import android.hardware.wifi.V1_0.IWifi;
20import android.hardware.wifi.V1_0.IWifiApIface;
21import android.hardware.wifi.V1_0.IWifiChip;
22import android.hardware.wifi.V1_0.IWifiChipEventCallback;
23import android.hardware.wifi.V1_0.IWifiEventCallback;
24import android.hardware.wifi.V1_0.IWifiIface;
25import android.hardware.wifi.V1_0.IWifiNanIface;
26import android.hardware.wifi.V1_0.IWifiP2pIface;
27import android.hardware.wifi.V1_0.IWifiRttController;
28import android.hardware.wifi.V1_0.IWifiStaIface;
29import android.hardware.wifi.V1_0.IfaceType;
30import android.hardware.wifi.V1_0.WifiDebugRingBufferStatus;
31import android.hardware.wifi.V1_0.WifiStatus;
32import android.hardware.wifi.V1_0.WifiStatusCode;
33import android.hidl.manager.V1_0.IServiceManager;
34import android.hidl.manager.V1_0.IServiceNotification;
35import android.os.Handler;
36import android.os.HwRemoteBinder;
37import android.os.Looper;
38import android.os.Message;
39import android.os.RemoteException;
40import android.util.Log;
41import android.util.MutableBoolean;
42import android.util.MutableInt;
43import android.util.SparseArray;
44
45import com.android.internal.annotations.VisibleForTesting;
46
47import java.io.FileDescriptor;
48import java.io.PrintWriter;
49import java.util.ArrayList;
50import java.util.Arrays;
51import java.util.HashMap;
52import java.util.HashSet;
53import java.util.Iterator;
54import java.util.List;
55import java.util.Map;
56import java.util.Set;
57
58/**
59 * Handles device management through the HAL (HIDL) interface.
60 */
61public class HalDeviceManager {
62    private static final String TAG = "HalDeviceManager";
63    private static final boolean DBG = false;
64
65    private static final int START_HAL_RETRY_INTERVAL_MS = 20;
66    // Number of attempts a start() is re-tried. A value of 0 means no retries after a single
67    // attempt.
68    @VisibleForTesting
69    public static final int START_HAL_RETRY_TIMES = 3;
70    @VisibleForTesting
71    public static final String HAL_INSTANCE_NAME = "default";
72
73    // public API
74    public HalDeviceManager() {
75        mInterfaceAvailableForRequestListeners.put(IfaceType.STA, new HashSet<>());
76        mInterfaceAvailableForRequestListeners.put(IfaceType.AP, new HashSet<>());
77        mInterfaceAvailableForRequestListeners.put(IfaceType.P2P, new HashSet<>());
78        mInterfaceAvailableForRequestListeners.put(IfaceType.NAN, new HashSet<>());
79    }
80
81    /**
82     * Actually starts the HalDeviceManager: separate from constructor since may want to phase
83     * at a later time.
84     *
85     * TODO: if decide that no need for separating construction from initialization (e.g. both are
86     * done at injector) then move to constructor.
87     */
88    public void initialize() {
89        initializeInternal();
90    }
91
92    /**
93     * Register a ManagerStatusListener to get information about the status of the manager. Use the
94     * isReady() and isStarted() methods to check status immediately after registration and when
95     * triggered.
96     *
97     * It is safe to re-register the same callback object - duplicates are detected and only a
98     * single copy kept.
99     *
100     * @param listener ManagerStatusListener listener object.
101     * @param looper Looper on which to dispatch listener. Null implies current looper.
102     */
103    public void registerStatusListener(ManagerStatusListener listener, Looper looper) {
104        synchronized (mLock) {
105            if (!mManagerStatusListeners.add(new ManagerStatusListenerProxy(listener,
106                    looper == null ? Looper.myLooper() : looper))) {
107                Log.w(TAG, "registerStatusListener: duplicate registration ignored");
108            }
109        }
110    }
111
112    /**
113     * Returns whether the vendor HAL is supported on this device or not.
114     */
115    public boolean isSupported() {
116        return isSupportedInternal();
117    }
118
119    /**
120     * Returns the current status of the HalDeviceManager: whether or not it is ready to execute
121     * commands. A return of 'false' indicates that the HAL service (IWifi) is not available. Use
122     * the registerStatusListener() to listener for status changes.
123     */
124    public boolean isReady() {
125        return mWifi != null;
126    }
127
128    /**
129     * Returns the current status of Wi-Fi: started (true) or stopped (false).
130     *
131     * Note: direct call to HIDL.
132     */
133    public boolean isStarted() {
134        return isWifiStarted();
135    }
136
137    /**
138     * Attempts to start Wi-Fi (using HIDL). Returns the success (true) or failure (false) or
139     * the start operation. Will also dispatch any registered ManagerStatusCallback.onStart() on
140     * success.
141     *
142     * Note: direct call to HIDL.
143     */
144    public boolean start() {
145        return startWifi();
146    }
147
148    /**
149     * Stops Wi-Fi. Will also dispatch any registeredManagerStatusCallback.onStop().
150     *
151     * Note: direct call to HIDL - failure is not-expected.
152     */
153    public void stop() {
154        stopWifi();
155    }
156
157    /**
158     * HAL device manager status change listener.
159     */
160    public interface ManagerStatusListener {
161        /**
162         * Indicates that the status of the HalDeviceManager has changed. Use isReady() and
163         * isStarted() to obtain status information.
164         */
165        void onStatusChanged();
166    }
167
168    /**
169     * Return the set of supported interface types across all Wi-Fi chips on the device.
170     *
171     * @return A set of IfaceTypes constants (possibly empty, e.g. on error).
172     */
173    Set<Integer> getSupportedIfaceTypes() {
174        return getSupportedIfaceTypesInternal(null);
175    }
176
177    /**
178     * Return the set of supported interface types for the specified Wi-Fi chip.
179     *
180     * @return A set of IfaceTypes constants  (possibly empty, e.g. on error).
181     */
182    Set<Integer> getSupportedIfaceTypes(IWifiChip chip) {
183        return getSupportedIfaceTypesInternal(chip);
184    }
185
186    // interface-specific behavior
187
188    /**
189     * Create a STA interface if possible. Changes chip mode and removes conflicting interfaces if
190     * needed and permitted by priority.
191     *
192     * @param destroyedListener Optional (nullable) listener to call when the allocated interface
193     *                          is removed. Will only be registered and used if an interface is
194     *                          created successfully.
195     * @param looper The looper on which to dispatch the listener. A null value indicates the
196     *               current thread.
197     * @return A newly created interface - or null if the interface could not be created.
198     */
199    public IWifiStaIface createStaIface(InterfaceDestroyedListener destroyedListener,
200            Looper looper) {
201        return (IWifiStaIface) createIface(IfaceType.STA, destroyedListener, looper);
202    }
203
204    /**
205     * Create AP interface if possible (see createStaIface doc).
206     */
207    public IWifiApIface createApIface(InterfaceDestroyedListener destroyedListener,
208            Looper looper) {
209        return (IWifiApIface) createIface(IfaceType.AP, destroyedListener, looper);
210    }
211
212    /**
213     * Create P2P interface if possible (see createStaIface doc).
214     */
215    public IWifiP2pIface createP2pIface(InterfaceDestroyedListener destroyedListener,
216            Looper looper) {
217        return (IWifiP2pIface) createIface(IfaceType.P2P, destroyedListener, looper);
218    }
219
220    /**
221     * Create NAN interface if possible (see createStaIface doc).
222     */
223    public IWifiNanIface createNanIface(InterfaceDestroyedListener destroyedListener,
224            Looper looper) {
225        return (IWifiNanIface) createIface(IfaceType.NAN, destroyedListener, looper);
226    }
227
228    /**
229     * Removes (releases/destroys) the given interface. Will trigger any registered
230     * InterfaceDestroyedListeners and possibly some InterfaceAvailableForRequestListeners if we
231     * can potentially create some other interfaces as a result of removing this interface.
232     */
233    public boolean removeIface(IWifiIface iface) {
234        boolean success = removeIfaceInternal(iface);
235        dispatchAvailableForRequestListeners();
236        return success;
237    }
238
239    /**
240     * Returns the IWifiChip corresponding to the specified interface (or null on error).
241     *
242     * Note: clients must not perform chip mode changes or interface management (create/delete)
243     * operations on IWifiChip directly. However, they can use the IWifiChip interface to perform
244     * other functions - e.g. calling the debug/trace methods.
245     */
246    public IWifiChip getChip(IWifiIface iface) {
247        String name = getName(iface);
248        if (DBG) Log.d(TAG, "getChip: iface(name)=" + name);
249
250        synchronized (mLock) {
251            InterfaceCacheEntry cacheEntry = mInterfaceInfoCache.get(name);
252            if (cacheEntry == null) {
253                Log.e(TAG, "getChip: no entry for iface(name)=" + name);
254                return null;
255            }
256
257            return cacheEntry.chip;
258        }
259    }
260
261    /**
262     * Register an InterfaceDestroyedListener to the specified iface - returns true on success
263     * and false on failure. This listener is in addition to the one registered when the interface
264     * was created - allowing non-creators to monitor interface status.
265     *
266     * Listener called-back on the specified looper - or on the current looper if a null is passed.
267     */
268    public boolean registerDestroyedListener(IWifiIface iface,
269            InterfaceDestroyedListener destroyedListener,
270            Looper looper) {
271        String name = getName(iface);
272        if (DBG) Log.d(TAG, "registerDestroyedListener: iface(name)=" + name);
273
274        synchronized (mLock) {
275            InterfaceCacheEntry cacheEntry = mInterfaceInfoCache.get(name);
276            if (cacheEntry == null) {
277                Log.e(TAG, "registerDestroyedListener: no entry for iface(name)=" + name);
278                return false;
279            }
280
281            return cacheEntry.destroyedListeners.add(
282                    new InterfaceDestroyedListenerProxy(destroyedListener,
283                            looper == null ? Looper.myLooper() : looper));
284        }
285    }
286
287    /**
288     * Register a listener to be called when an interface of the specified type could be requested.
289     * No guarantees are provided (some other entity could request it first). The listener is
290     * active from registration until unregistration - using
291     * unregisterInterfaceAvailableForRequestListener().
292     *
293     * Only a single instance of a listener will be registered (even if the specified looper is
294     * different).
295     *
296     * Note that if it is possible to create the specified interface type at registration time
297     * then the callback will be triggered immediately.
298     *
299     * @param ifaceType The interface type (IfaceType) to be monitored.
300     * @param listener Listener to call when an interface of the requested
301     *                 type could be created
302     * @param looper The looper on which to dispatch the listener. A null value indicates the
303     *               current thread.
304     */
305    public void registerInterfaceAvailableForRequestListener(int ifaceType,
306            InterfaceAvailableForRequestListener listener, Looper looper) {
307        mInterfaceAvailableForRequestListeners.get(ifaceType).add(
308                new InterfaceAvailableForRequestListenerProxy(listener,
309                        looper == null ? Looper.myLooper() : looper));
310
311        WifiChipInfo[] chipInfos = getAllChipInfo();
312        if (chipInfos == null) {
313            Log.e(TAG,
314                    "registerInterfaceAvailableForRequestListener: no chip info found - but "
315                            + "possibly registered pre-started - ignoring");
316            return;
317        }
318        dispatchAvailableForRequestListenersForType(ifaceType, chipInfos);
319    }
320
321    /**
322     * Unregisters a listener registered with registerInterfaceAvailableForRequestListener().
323     */
324    public void unregisterInterfaceAvailableForRequestListener(
325            int ifaceType,
326            InterfaceAvailableForRequestListener listener) {
327        Iterator<InterfaceAvailableForRequestListenerProxy> it =
328                mInterfaceAvailableForRequestListeners.get(ifaceType).iterator();
329        while (it.hasNext()) {
330            if (it.next().mListener == listener) {
331                it.remove();
332                return;
333            }
334        }
335    }
336
337    /**
338     * Return the name of the input interface or null on error.
339     */
340    public static String getName(IWifiIface iface) {
341        if (iface == null) {
342            return "<null>";
343        }
344
345        Mutable<String> nameResp = new Mutable<>();
346        try {
347            iface.getName((WifiStatus status, String name) -> {
348                if (status.code == WifiStatusCode.SUCCESS) {
349                    nameResp.value = name;
350                } else {
351                    Log.e(TAG, "Error on getName: " + statusString(status));
352                }
353            });
354        } catch (RemoteException e) {
355            Log.e(TAG, "Exception on getName: " + e);
356        }
357
358        return nameResp.value;
359    }
360
361    /**
362     * Called when interface is destroyed.
363     */
364    public interface InterfaceDestroyedListener {
365        /**
366         * Called for every interface on which registered when destroyed - whether
367         * destroyed by releaseIface() or through chip mode change or through Wi-Fi
368         * going down.
369         *
370         * Can be registered when the interface is requested with createXxxIface() - will
371         * only be valid if the interface creation was successful - i.e. a non-null was returned.
372         */
373        void onDestroyed();
374    }
375
376    /**
377     * Called when an interface type is possibly available for creation.
378     */
379    public interface InterfaceAvailableForRequestListener {
380        /**
381         * Registered when an interface type could be requested. Registered with
382         * registerInterfaceAvailableForRequestListener() and unregistered with
383         * unregisterInterfaceAvailableForRequestListener().
384         */
385        void onAvailableForRequest();
386    }
387
388    /**
389     * Creates a IWifiRttController corresponding to the input interface. A direct match to the
390     * IWifiChip.createRttController() method.
391     *
392     * Returns the created IWifiRttController or a null on error.
393     */
394    public IWifiRttController createRttController(IWifiIface boundIface) {
395        if (DBG) Log.d(TAG, "createRttController: boundIface(name)=" + getName(boundIface));
396        synchronized (mLock) {
397            if (mWifi == null) {
398                Log.e(TAG, "createRttController: null IWifi -- boundIface(name)="
399                        + getName(boundIface));
400                return null;
401            }
402
403            IWifiChip chip = getChip(boundIface);
404            if (chip == null) {
405                Log.e(TAG, "createRttController: null IWifiChip -- boundIface(name)="
406                        + getName(boundIface));
407                return null;
408            }
409
410            Mutable<IWifiRttController> rttResp = new Mutable<>();
411            try {
412                chip.createRttController(boundIface,
413                        (WifiStatus status, IWifiRttController rtt) -> {
414                            if (status.code == WifiStatusCode.SUCCESS) {
415                                rttResp.value = rtt;
416                            } else {
417                                Log.e(TAG, "IWifiChip.createRttController failed: " + statusString(
418                                        status));
419                            }
420                        });
421            } catch (RemoteException e) {
422                Log.e(TAG, "IWifiChip.createRttController exception: " + e);
423            }
424
425            return rttResp.value;
426        }
427    }
428
429    // internal state
430
431    /* This "PRIORITY" is not for deciding interface elimination (that is controlled by
432     * allowedToDeleteIfaceTypeForRequestedType. This priority is used for:
433     * - Comparing 2 configuration options
434     * - Order of dispatch of available for request listeners
435     */
436    private static final int[] IFACE_TYPES_BY_PRIORITY =
437            {IfaceType.AP, IfaceType.STA, IfaceType.P2P, IfaceType.NAN};
438
439    private final Object mLock = new Object();
440
441    private IServiceManager mServiceManager;
442    private IWifi mWifi;
443    private final WifiEventCallback mWifiEventCallback = new WifiEventCallback();
444    private final Set<ManagerStatusListenerProxy> mManagerStatusListeners = new HashSet<>();
445    private final SparseArray<Set<InterfaceAvailableForRequestListenerProxy>>
446            mInterfaceAvailableForRequestListeners = new SparseArray<>();
447    private final SparseArray<IWifiChipEventCallback.Stub> mDebugCallbacks = new SparseArray<>();
448
449    /*
450     * This is the only place where we cache HIDL information in this manager. Necessary since
451     * we need to keep a list of registered destroyed listeners. Will be validated regularly
452     * in getAllChipInfoAndValidateCache().
453     */
454    private final Map<String, InterfaceCacheEntry> mInterfaceInfoCache = new HashMap<>();
455
456    private class InterfaceCacheEntry {
457        public IWifiChip chip;
458        public int chipId;
459        public String name;
460        public int type;
461        public Set<InterfaceDestroyedListenerProxy> destroyedListeners = new HashSet<>();
462
463        @Override
464        public String toString() {
465            StringBuilder sb = new StringBuilder();
466            sb.append("{name=").append(name).append(", type=").append(type)
467                    .append(", destroyedListeners.size()=").append(destroyedListeners.size())
468                    .append("}");
469            return sb.toString();
470        }
471    }
472
473    private class WifiIfaceInfo {
474        public String name;
475        public IWifiIface iface;
476    }
477
478    private class WifiChipInfo {
479        public IWifiChip chip;
480        public int chipId;
481        public ArrayList<IWifiChip.ChipMode> availableModes;
482        public boolean currentModeIdValid;
483        public int currentModeId;
484        public WifiIfaceInfo[][] ifaces = new WifiIfaceInfo[IFACE_TYPES_BY_PRIORITY.length][];
485
486        @Override
487        public String toString() {
488            StringBuilder sb = new StringBuilder();
489            sb.append("{chipId=").append(chipId).append(", availableModes=").append(availableModes)
490                    .append(", currentModeIdValid=").append(currentModeIdValid)
491                    .append(", currentModeId=").append(currentModeId);
492            for (int type: IFACE_TYPES_BY_PRIORITY) {
493                sb.append(", ifaces[" + type + "].length=").append(ifaces[type].length);
494            }
495            sb.append(")");
496            return sb.toString();
497        }
498    }
499
500    /**
501     * Wrapper function to access the HIDL services. Created to be mockable in unit-tests.
502     */
503    protected IWifi getWifiServiceMockable() {
504        try {
505            return IWifi.getService();
506        } catch (RemoteException e) {
507            Log.e(TAG, "Exception getting IWifi service: " + e);
508            return null;
509        }
510    }
511
512    protected IServiceManager getServiceManagerMockable() {
513        try {
514            return IServiceManager.getService();
515        } catch (RemoteException e) {
516            Log.e(TAG, "Exception getting IServiceManager: " + e);
517            return null;
518        }
519    }
520
521    // internal implementation
522
523    private void initializeInternal() {
524        initIServiceManagerIfNecessary();
525    }
526
527    private void teardownInternal() {
528        managerStatusListenerDispatch();
529        dispatchAllDestroyedListeners();
530        mInterfaceAvailableForRequestListeners.get(IfaceType.STA).clear();
531        mInterfaceAvailableForRequestListeners.get(IfaceType.AP).clear();
532        mInterfaceAvailableForRequestListeners.get(IfaceType.P2P).clear();
533        mInterfaceAvailableForRequestListeners.get(IfaceType.NAN).clear();
534    }
535
536    private final HwRemoteBinder.DeathRecipient mServiceManagerDeathRecipient =
537            cookie -> {
538                Log.wtf(TAG, "IServiceManager died: cookie=" + cookie);
539                synchronized (mLock) {
540                    mServiceManager = null;
541                    // theoretically can call initServiceManager again here - but
542                    // there's no point since most likely system is going to reboot
543                }
544            };
545
546    private final IServiceNotification mServiceNotificationCallback =
547            new IServiceNotification.Stub() {
548                @Override
549                public void onRegistration(String fqName, String name,
550                                           boolean preexisting) {
551                    Log.d(TAG, "IWifi registration notification: fqName=" + fqName
552                            + ", name=" + name + ", preexisting=" + preexisting);
553                    mWifi = null; // get rid of old copy!
554                    initIWifiIfNecessary();
555                    stopWifi(); // just in case
556                }
557            };
558
559    /**
560     * Failures of IServiceManager are most likely system breaking in any case. Behavior here
561     * will be to WTF and continue.
562     */
563    private void initIServiceManagerIfNecessary() {
564        if (DBG) Log.d(TAG, "initIServiceManagerIfNecessary");
565
566        synchronized (mLock) {
567            if (mServiceManager != null) {
568                return;
569            }
570
571            mServiceManager = getServiceManagerMockable();
572            if (mServiceManager == null) {
573                Log.wtf(TAG, "Failed to get IServiceManager instance");
574            } else {
575                try {
576                    if (!mServiceManager.linkToDeath(
577                            mServiceManagerDeathRecipient, /* don't care */ 0)) {
578                        Log.wtf(TAG, "Error on linkToDeath on IServiceManager");
579                        mServiceManager = null;
580                        return;
581                    }
582
583                    if (!mServiceManager.registerForNotifications(IWifi.kInterfaceName, "",
584                            mServiceNotificationCallback)) {
585                        Log.wtf(TAG, "Failed to register a listener for IWifi service");
586                        mServiceManager = null;
587                    }
588                } catch (RemoteException e) {
589                    Log.wtf(TAG, "Exception while operating on IServiceManager: " + e);
590                    mServiceManager = null;
591                }
592            }
593        }
594    }
595
596    /**
597     * Uses the IServiceManager to query if the vendor HAL is present in the VINTF for the device
598     * or not.
599     * @return true if supported, false otherwise.
600     */
601    private boolean isSupportedInternal() {
602        if (DBG) Log.d(TAG, "isSupportedInternal");
603
604        synchronized (mLock) {
605            if (mServiceManager == null) {
606                Log.e(TAG, "isSupported: called but mServiceManager is null!?");
607                return false;
608            }
609            try {
610                return (mServiceManager.getTransport(IWifi.kInterfaceName, HAL_INSTANCE_NAME)
611                        != IServiceManager.Transport.EMPTY);
612            } catch (RemoteException e) {
613                Log.wtf(TAG, "Exception while operating on IServiceManager: " + e);
614                return false;
615            }
616        }
617    }
618
619    private final HwRemoteBinder.DeathRecipient mIWifiDeathRecipient =
620            cookie -> {
621                Log.e(TAG, "IWifi HAL service died! Have a listener for it ... cookie=" + cookie);
622                synchronized (mLock) { // prevents race condition with surrounding method
623                    mWifi = null;
624                    teardownInternal();
625                    // don't restart: wait for registration notification
626                }
627            };
628
629    /**
630     * Initialize IWifi and register death listener and event callback.
631     *
632     * - It is possible that IWifi is not ready - we have a listener on IServiceManager for it.
633     * - It is not expected that any of the registrations will fail. Possible indication that
634     *   service died after we obtained a handle to it.
635     *
636     * Here and elsewhere we assume that death listener will do the right thing!
637    */
638    private void initIWifiIfNecessary() {
639        if (DBG) Log.d(TAG, "initIWifiIfNecessary");
640
641        synchronized (mLock) {
642            if (mWifi != null) {
643                return;
644            }
645
646            try {
647                mWifi = getWifiServiceMockable();
648                if (mWifi == null) {
649                    Log.e(TAG, "IWifi not (yet) available - but have a listener for it ...");
650                    return;
651                }
652
653                if (!mWifi.linkToDeath(mIWifiDeathRecipient, /* don't care */ 0)) {
654                    Log.e(TAG, "Error on linkToDeath on IWifi - will retry later");
655                    return;
656                }
657
658                WifiStatus status = mWifi.registerEventCallback(mWifiEventCallback);
659                if (status.code != WifiStatusCode.SUCCESS) {
660                    Log.e(TAG, "IWifi.registerEventCallback failed: " + statusString(status));
661                    mWifi = null;
662                    return;
663                }
664                managerStatusListenerDispatch();
665            } catch (RemoteException e) {
666                Log.e(TAG, "Exception while operating on IWifi: " + e);
667            }
668        }
669    }
670
671    /**
672     * Registers event listeners on all IWifiChips after a successful start: DEBUG only!
673     *
674     * We don't need the listeners since any callbacks are just confirmation of status codes we
675     * obtain directly from mode changes or interface creation/deletion.
676     *
677     * Relies (to the degree we care) on the service removing all listeners when Wi-Fi is stopped.
678     */
679    private void initIWifiChipDebugListeners() {
680        if (DBG) Log.d(TAG, "initIWifiChipDebugListeners");
681
682        if (!DBG) {
683            return;
684        }
685
686        synchronized (mLock) {
687            try {
688                MutableBoolean statusOk = new MutableBoolean(false);
689                Mutable<ArrayList<Integer>> chipIdsResp = new Mutable<>();
690
691                // get all chip IDs
692                mWifi.getChipIds((WifiStatus status, ArrayList<Integer> chipIds) -> {
693                    statusOk.value = status.code == WifiStatusCode.SUCCESS;
694                    if (statusOk.value) {
695                        chipIdsResp.value = chipIds;
696                    } else {
697                        Log.e(TAG, "getChipIds failed: " + statusString(status));
698                    }
699                });
700                if (!statusOk.value) {
701                    return;
702                }
703
704                if (DBG) Log.d(TAG, "getChipIds=" + chipIdsResp.value);
705                if (chipIdsResp.value.size() == 0) {
706                    Log.e(TAG, "Should have at least 1 chip!");
707                    return;
708                }
709
710                // register a callback for each chip
711                Mutable<IWifiChip> chipResp = new Mutable<>();
712                for (Integer chipId: chipIdsResp.value) {
713                    mWifi.getChip(chipId, (WifiStatus status, IWifiChip chip) -> {
714                        statusOk.value = status.code == WifiStatusCode.SUCCESS;
715                        if (statusOk.value) {
716                            chipResp.value = chip;
717                        } else {
718                            Log.e(TAG, "getChip failed: " + statusString(status));
719                        }
720                    });
721                    if (!statusOk.value) {
722                        continue; // still try next one?
723                    }
724
725                    IWifiChipEventCallback.Stub callback =
726                            new IWifiChipEventCallback.Stub() {
727                                @Override
728                                public void onChipReconfigured(int modeId) throws RemoteException {
729                                    Log.d(TAG, "onChipReconfigured: modeId=" + modeId);
730                                }
731
732                                @Override
733                                public void onChipReconfigureFailure(WifiStatus status)
734                                        throws RemoteException {
735                                    Log.d(TAG, "onChipReconfigureFailure: status=" + statusString(
736                                            status));
737                                }
738
739                                @Override
740                                public void onIfaceAdded(int type, String name)
741                                        throws RemoteException {
742                                    Log.d(TAG, "onIfaceAdded: type=" + type + ", name=" + name);
743                                }
744
745                                @Override
746                                public void onIfaceRemoved(int type, String name)
747                                        throws RemoteException {
748                                    Log.d(TAG, "onIfaceRemoved: type=" + type + ", name=" + name);
749                                }
750
751                                @Override
752                                public void onDebugRingBufferDataAvailable(
753                                        WifiDebugRingBufferStatus status,
754                                        ArrayList<Byte> data) throws RemoteException {
755                                    Log.d(TAG, "onDebugRingBufferDataAvailable");
756                                }
757
758                                @Override
759                                public void onDebugErrorAlert(int errorCode,
760                                        ArrayList<Byte> debugData)
761                                        throws RemoteException {
762                                    Log.d(TAG, "onDebugErrorAlert");
763                                }
764                            };
765                    mDebugCallbacks.put(chipId, callback); // store to prevent GC: needed by HIDL
766                    WifiStatus status = chipResp.value.registerEventCallback(callback);
767                    if (status.code != WifiStatusCode.SUCCESS) {
768                        Log.e(TAG, "registerEventCallback failed: " + statusString(status));
769                        continue; // still try next one?
770                    }
771                }
772            } catch (RemoteException e) {
773                Log.e(TAG, "initIWifiChipDebugListeners: exception: " + e);
774                return;
775            }
776        }
777    }
778
779    /**
780     * Get current information about all the chips in the system: modes, current mode (if any), and
781     * any existing interfaces.
782     *
783     * Intended to be called whenever we need to configure the chips - information is NOT cached (to
784     * reduce the likelihood that we get out-of-sync).
785     */
786    private WifiChipInfo[] getAllChipInfo() {
787        if (DBG) Log.d(TAG, "getAllChipInfo");
788
789        synchronized (mLock) {
790            if (mWifi == null) {
791                Log.e(TAG, "getAllChipInfo: called but mWifi is null!?");
792                return null;
793            }
794
795            try {
796                MutableBoolean statusOk = new MutableBoolean(false);
797                Mutable<ArrayList<Integer>> chipIdsResp = new Mutable<>();
798
799                // get all chip IDs
800                mWifi.getChipIds((WifiStatus status, ArrayList<Integer> chipIds) -> {
801                    statusOk.value = status.code == WifiStatusCode.SUCCESS;
802                    if (statusOk.value) {
803                        chipIdsResp.value = chipIds;
804                    } else {
805                        Log.e(TAG, "getChipIds failed: " + statusString(status));
806                    }
807                });
808                if (!statusOk.value) {
809                    return null;
810                }
811
812                if (DBG) Log.d(TAG, "getChipIds=" + chipIdsResp.value);
813                if (chipIdsResp.value.size() == 0) {
814                    Log.e(TAG, "Should have at least 1 chip!");
815                    return null;
816                }
817
818                int chipInfoIndex = 0;
819                WifiChipInfo[] chipsInfo = new WifiChipInfo[chipIdsResp.value.size()];
820
821                Mutable<IWifiChip> chipResp = new Mutable<>();
822                for (Integer chipId: chipIdsResp.value) {
823                    mWifi.getChip(chipId, (WifiStatus status, IWifiChip chip) -> {
824                        statusOk.value = status.code == WifiStatusCode.SUCCESS;
825                        if (statusOk.value) {
826                            chipResp.value = chip;
827                        } else {
828                            Log.e(TAG, "getChip failed: " + statusString(status));
829                        }
830                    });
831                    if (!statusOk.value) {
832                        return null;
833                    }
834
835                    Mutable<ArrayList<IWifiChip.ChipMode>> availableModesResp = new Mutable<>();
836                    chipResp.value.getAvailableModes(
837                            (WifiStatus status, ArrayList<IWifiChip.ChipMode> modes) -> {
838                                statusOk.value = status.code == WifiStatusCode.SUCCESS;
839                                if (statusOk.value) {
840                                    availableModesResp.value = modes;
841                                } else {
842                                    Log.e(TAG, "getAvailableModes failed: " + statusString(status));
843                                }
844                            });
845                    if (!statusOk.value) {
846                        return null;
847                    }
848
849                    MutableBoolean currentModeValidResp = new MutableBoolean(false);
850                    MutableInt currentModeResp = new MutableInt(0);
851                    chipResp.value.getMode((WifiStatus status, int modeId) -> {
852                        statusOk.value = status.code == WifiStatusCode.SUCCESS;
853                        if (statusOk.value) {
854                            currentModeValidResp.value = true;
855                            currentModeResp.value = modeId;
856                        } else if (status.code == WifiStatusCode.ERROR_NOT_AVAILABLE) {
857                            statusOk.value = true; // valid response
858                        } else {
859                            Log.e(TAG, "getMode failed: " + statusString(status));
860                        }
861                    });
862                    if (!statusOk.value) {
863                        return null;
864                    }
865
866                    Mutable<ArrayList<String>> ifaceNamesResp = new Mutable<>();
867                    MutableInt ifaceIndex = new MutableInt(0);
868
869                    chipResp.value.getStaIfaceNames(
870                            (WifiStatus status, ArrayList<String> ifnames) -> {
871                                statusOk.value = status.code == WifiStatusCode.SUCCESS;
872                                if (statusOk.value) {
873                                    ifaceNamesResp.value = ifnames;
874                                } else {
875                                    Log.e(TAG, "getStaIfaceNames failed: " + statusString(status));
876                                }
877                            });
878                    if (!statusOk.value) {
879                        return null;
880                    }
881
882                    WifiIfaceInfo[] staIfaces = new WifiIfaceInfo[ifaceNamesResp.value.size()];
883                    for (String ifaceName: ifaceNamesResp.value) {
884                        chipResp.value.getStaIface(ifaceName,
885                                (WifiStatus status, IWifiStaIface iface) -> {
886                                    statusOk.value = status.code == WifiStatusCode.SUCCESS;
887                                    if (statusOk.value) {
888                                        WifiIfaceInfo ifaceInfo = new WifiIfaceInfo();
889                                        ifaceInfo.name = ifaceName;
890                                        ifaceInfo.iface = iface;
891                                        staIfaces[ifaceIndex.value++] = ifaceInfo;
892                                    } else {
893                                        Log.e(TAG, "getStaIface failed: " + statusString(status));
894                                    }
895                                });
896                        if (!statusOk.value) {
897                            return null;
898                        }
899                    }
900
901                    ifaceIndex.value = 0;
902                    chipResp.value.getApIfaceNames(
903                            (WifiStatus status, ArrayList<String> ifnames) -> {
904                                statusOk.value = status.code == WifiStatusCode.SUCCESS;
905                                if (statusOk.value) {
906                                    ifaceNamesResp.value = ifnames;
907                                } else {
908                                    Log.e(TAG, "getApIfaceNames failed: " + statusString(status));
909                                }
910                            });
911                    if (!statusOk.value) {
912                        return null;
913                    }
914
915                    WifiIfaceInfo[] apIfaces = new WifiIfaceInfo[ifaceNamesResp.value.size()];
916                    for (String ifaceName: ifaceNamesResp.value) {
917                        chipResp.value.getApIface(ifaceName,
918                                (WifiStatus status, IWifiApIface iface) -> {
919                                    statusOk.value = status.code == WifiStatusCode.SUCCESS;
920                                    if (statusOk.value) {
921                                        WifiIfaceInfo ifaceInfo = new WifiIfaceInfo();
922                                        ifaceInfo.name = ifaceName;
923                                        ifaceInfo.iface = iface;
924                                        apIfaces[ifaceIndex.value++] = ifaceInfo;
925                                    } else {
926                                        Log.e(TAG, "getApIface failed: " + statusString(status));
927                                    }
928                                });
929                        if (!statusOk.value) {
930                            return null;
931                        }
932                    }
933
934                    ifaceIndex.value = 0;
935                    chipResp.value.getP2pIfaceNames(
936                            (WifiStatus status, ArrayList<String> ifnames) -> {
937                                statusOk.value = status.code == WifiStatusCode.SUCCESS;
938                                if (statusOk.value) {
939                                    ifaceNamesResp.value = ifnames;
940                                } else {
941                                    Log.e(TAG, "getP2pIfaceNames failed: " + statusString(status));
942                                }
943                            });
944                    if (!statusOk.value) {
945                        return null;
946                    }
947
948                    WifiIfaceInfo[] p2pIfaces = new WifiIfaceInfo[ifaceNamesResp.value.size()];
949                    for (String ifaceName: ifaceNamesResp.value) {
950                        chipResp.value.getP2pIface(ifaceName,
951                                (WifiStatus status, IWifiP2pIface iface) -> {
952                                    statusOk.value = status.code == WifiStatusCode.SUCCESS;
953                                    if (statusOk.value) {
954                                        WifiIfaceInfo ifaceInfo = new WifiIfaceInfo();
955                                        ifaceInfo.name = ifaceName;
956                                        ifaceInfo.iface = iface;
957                                        p2pIfaces[ifaceIndex.value++] = ifaceInfo;
958                                    } else {
959                                        Log.e(TAG, "getP2pIface failed: " + statusString(status));
960                                    }
961                                });
962                        if (!statusOk.value) {
963                            return null;
964                        }
965                    }
966
967                    ifaceIndex.value = 0;
968                    chipResp.value.getNanIfaceNames(
969                            (WifiStatus status, ArrayList<String> ifnames) -> {
970                                statusOk.value = status.code == WifiStatusCode.SUCCESS;
971                                if (statusOk.value) {
972                                    ifaceNamesResp.value = ifnames;
973                                } else {
974                                    Log.e(TAG, "getNanIfaceNames failed: " + statusString(status));
975                                }
976                            });
977                    if (!statusOk.value) {
978                        return null;
979                    }
980
981                    WifiIfaceInfo[] nanIfaces = new WifiIfaceInfo[ifaceNamesResp.value.size()];
982                    for (String ifaceName: ifaceNamesResp.value) {
983                        chipResp.value.getNanIface(ifaceName,
984                                (WifiStatus status, IWifiNanIface iface) -> {
985                                    statusOk.value = status.code == WifiStatusCode.SUCCESS;
986                                    if (statusOk.value) {
987                                        WifiIfaceInfo ifaceInfo = new WifiIfaceInfo();
988                                        ifaceInfo.name = ifaceName;
989                                        ifaceInfo.iface = iface;
990                                        nanIfaces[ifaceIndex.value++] = ifaceInfo;
991                                    } else {
992                                        Log.e(TAG, "getNanIface failed: " + statusString(status));
993                                    }
994                                });
995                        if (!statusOk.value) {
996                            return null;
997                        }
998                    }
999
1000                    WifiChipInfo chipInfo = new WifiChipInfo();
1001                    chipsInfo[chipInfoIndex++] = chipInfo;
1002
1003                    chipInfo.chip = chipResp.value;
1004                    chipInfo.chipId = chipId;
1005                    chipInfo.availableModes = availableModesResp.value;
1006                    chipInfo.currentModeIdValid = currentModeValidResp.value;
1007                    chipInfo.currentModeId = currentModeResp.value;
1008                    chipInfo.ifaces[IfaceType.STA] = staIfaces;
1009                    chipInfo.ifaces[IfaceType.AP] = apIfaces;
1010                    chipInfo.ifaces[IfaceType.P2P] = p2pIfaces;
1011                    chipInfo.ifaces[IfaceType.NAN] = nanIfaces;
1012                }
1013
1014                return chipsInfo;
1015            } catch (RemoteException e) {
1016                Log.e(TAG, "getAllChipInfoAndValidateCache exception: " + e);
1017            }
1018        }
1019
1020        return null;
1021    }
1022
1023    /**
1024     * Checks the local state of this object (the cached state) against the input 'chipInfos'
1025     * state (which is a live representation of the Wi-Fi firmware status - read through the HAL).
1026     * Returns 'true' if there are no discrepancies - 'false' otherwise.
1027     *
1028     * A discrepancy is if any local state contains references to a chip or interface which are not
1029     * found on the information read from the chip.
1030     */
1031    private boolean validateInterfaceCache(WifiChipInfo[] chipInfos) {
1032        if (DBG) Log.d(TAG, "validateInterfaceCache");
1033
1034        synchronized (mLock) {
1035            for (InterfaceCacheEntry entry: mInterfaceInfoCache.values()) {
1036                // search for chip
1037                WifiChipInfo matchingChipInfo = null;
1038                for (WifiChipInfo ci: chipInfos) {
1039                    if (ci.chipId == entry.chipId) {
1040                        matchingChipInfo = ci;
1041                        break;
1042                    }
1043                }
1044                if (matchingChipInfo == null) {
1045                    Log.e(TAG, "validateInterfaceCache: no chip found for " + entry);
1046                    return false;
1047                }
1048
1049                // search for interface
1050                WifiIfaceInfo[] ifaceInfoList = matchingChipInfo.ifaces[entry.type];
1051                if (ifaceInfoList == null) {
1052                    Log.e(TAG, "validateInterfaceCache: invalid type on entry " + entry);
1053                    return false;
1054                }
1055
1056                boolean matchFound = false;
1057                for (WifiIfaceInfo ifaceInfo: ifaceInfoList) {
1058                    if (ifaceInfo.name.equals(entry.name)) {
1059                        matchFound = true;
1060                        break;
1061                    }
1062                }
1063                if (!matchFound) {
1064                    Log.e(TAG, "validateInterfaceCache: no interface found for " + entry);
1065                    return false;
1066                }
1067            }
1068        }
1069
1070        return true;
1071    }
1072
1073    private boolean isWifiStarted() {
1074        if (DBG) Log.d(TAG, "isWifiStart");
1075
1076        synchronized (mLock) {
1077            try {
1078                if (mWifi == null) {
1079                    Log.w(TAG, "isWifiStarted called but mWifi is null!?");
1080                    return false;
1081                } else {
1082                    return mWifi.isStarted();
1083                }
1084            } catch (RemoteException e) {
1085                Log.e(TAG, "isWifiStarted exception: " + e);
1086                return false;
1087            }
1088        }
1089    }
1090
1091    private boolean startWifi() {
1092        if (DBG) Log.d(TAG, "startWifi");
1093
1094        synchronized (mLock) {
1095            try {
1096                if (mWifi == null) {
1097                    Log.w(TAG, "startWifi called but mWifi is null!?");
1098                    return false;
1099                } else {
1100                    int triedCount = 0;
1101                    while (triedCount <= START_HAL_RETRY_TIMES) {
1102                        WifiStatus status = mWifi.start();
1103                        if (status.code == WifiStatusCode.SUCCESS) {
1104                            initIWifiChipDebugListeners();
1105                            managerStatusListenerDispatch();
1106                            if (triedCount != 0) {
1107                                Log.d(TAG, "start IWifi succeeded after trying "
1108                                         + triedCount + " times");
1109                            }
1110                            return true;
1111                        } else if (status.code == WifiStatusCode.ERROR_NOT_AVAILABLE) {
1112                            // Should retry. Hal might still be stopping.
1113                            Log.e(TAG, "Cannot start IWifi: " + statusString(status)
1114                                    + ", Retrying...");
1115                            try {
1116                                Thread.sleep(START_HAL_RETRY_INTERVAL_MS);
1117                            } catch (InterruptedException ignore) {
1118                                // no-op
1119                            }
1120                            triedCount++;
1121                        } else {
1122                            // Should not retry on other failures.
1123                            Log.e(TAG, "Cannot start IWifi: " + statusString(status));
1124                            return false;
1125                        }
1126                    }
1127                    Log.e(TAG, "Cannot start IWifi after trying " + triedCount + " times");
1128                    return false;
1129                }
1130            } catch (RemoteException e) {
1131                Log.e(TAG, "startWifi exception: " + e);
1132                return false;
1133            }
1134        }
1135    }
1136
1137    private void stopWifi() {
1138        if (DBG) Log.d(TAG, "stopWifi");
1139
1140        synchronized (mLock) {
1141            try {
1142                if (mWifi == null) {
1143                    Log.w(TAG, "stopWifi called but mWifi is null!?");
1144                } else {
1145                    WifiStatus status = mWifi.stop();
1146                    if (status.code != WifiStatusCode.SUCCESS) {
1147                        Log.e(TAG, "Cannot stop IWifi: " + statusString(status));
1148                    }
1149
1150                    // even on failure since WTF??
1151                    teardownInternal();
1152                }
1153            } catch (RemoteException e) {
1154                Log.e(TAG, "stopWifi exception: " + e);
1155            }
1156        }
1157    }
1158
1159    private class WifiEventCallback extends IWifiEventCallback.Stub {
1160        @Override
1161        public void onStart() throws RemoteException {
1162            if (DBG) Log.d(TAG, "IWifiEventCallback.onStart");
1163            // NOP: only happens in reaction to my calls - will handle directly
1164        }
1165
1166        @Override
1167        public void onStop() throws RemoteException {
1168            if (DBG) Log.d(TAG, "IWifiEventCallback.onStop");
1169            // NOP: only happens in reaction to my calls - will handle directly
1170        }
1171
1172        @Override
1173        public void onFailure(WifiStatus status) throws RemoteException {
1174            Log.e(TAG, "IWifiEventCallback.onFailure: " + statusString(status));
1175            teardownInternal();
1176
1177            // No need to do anything else: listeners may (will) re-start Wi-Fi
1178        }
1179    }
1180
1181    private void managerStatusListenerDispatch() {
1182        synchronized (mLock) {
1183            for (ManagerStatusListenerProxy cb : mManagerStatusListeners) {
1184                cb.trigger();
1185            }
1186        }
1187    }
1188
1189    private class ManagerStatusListenerProxy  extends
1190            ListenerProxy<ManagerStatusListener> {
1191        ManagerStatusListenerProxy(ManagerStatusListener statusListener,
1192                Looper looper) {
1193            super(statusListener, looper, "ManagerStatusListenerProxy");
1194        }
1195
1196        @Override
1197        protected void action() {
1198            mListener.onStatusChanged();
1199        }
1200    }
1201
1202    Set<Integer> getSupportedIfaceTypesInternal(IWifiChip chip) {
1203        Set<Integer> results = new HashSet<>();
1204
1205        WifiChipInfo[] chipInfos = getAllChipInfo();
1206        if (chipInfos == null) {
1207            Log.e(TAG, "getSupportedIfaceTypesInternal: no chip info found");
1208            return results;
1209        }
1210
1211        MutableInt chipIdIfProvided = new MutableInt(0); // NOT using 0 as a magic value
1212        if (chip != null) {
1213            MutableBoolean statusOk = new MutableBoolean(false);
1214            try {
1215                chip.getId((WifiStatus status, int id) -> {
1216                    if (status.code == WifiStatusCode.SUCCESS) {
1217                        chipIdIfProvided.value = id;
1218                        statusOk.value = true;
1219                    } else {
1220                        Log.e(TAG, "getSupportedIfaceTypesInternal: IWifiChip.getId() error: "
1221                                + statusString(status));
1222                        statusOk.value = false;
1223                    }
1224                });
1225            } catch (RemoteException e) {
1226                Log.e(TAG, "getSupportedIfaceTypesInternal IWifiChip.getId() exception: " + e);
1227                return results;
1228            }
1229            if (!statusOk.value) {
1230                return results;
1231            }
1232        }
1233
1234        for (WifiChipInfo wci: chipInfos) {
1235            if (chip != null && wci.chipId != chipIdIfProvided.value) {
1236                continue;
1237            }
1238
1239            for (IWifiChip.ChipMode cm: wci.availableModes) {
1240                for (IWifiChip.ChipIfaceCombination cic: cm.availableCombinations) {
1241                    for (IWifiChip.ChipIfaceCombinationLimit cicl: cic.limits) {
1242                        for (int type: cicl.types) {
1243                            results.add(type);
1244                        }
1245                    }
1246                }
1247            }
1248        }
1249
1250        return results;
1251    }
1252
1253    private IWifiIface createIface(int ifaceType, InterfaceDestroyedListener destroyedListener,
1254            Looper looper) {
1255        if (DBG) Log.d(TAG, "createIface: ifaceType=" + ifaceType);
1256
1257        synchronized (mLock) {
1258            WifiChipInfo[] chipInfos = getAllChipInfo();
1259            if (chipInfos == null) {
1260                Log.e(TAG, "createIface: no chip info found");
1261                stopWifi(); // major error: shutting down
1262                return null;
1263            }
1264
1265            if (!validateInterfaceCache(chipInfos)) {
1266                Log.e(TAG, "createIface: local cache is invalid!");
1267                stopWifi(); // major error: shutting down
1268                return null;
1269            }
1270
1271            IWifiIface iface = createIfaceIfPossible(chipInfos, ifaceType, destroyedListener,
1272                    looper);
1273            if (iface != null) { // means that some configuration has changed
1274                if (!dispatchAvailableForRequestListeners()) {
1275                    return null; // catastrophic failure - shut down
1276                }
1277            }
1278
1279            return iface;
1280        }
1281    }
1282
1283    private IWifiIface createIfaceIfPossible(WifiChipInfo[] chipInfos, int ifaceType,
1284            InterfaceDestroyedListener destroyedListener, Looper looper) {
1285        if (DBG) {
1286            Log.d(TAG, "createIfaceIfPossible: chipInfos=" + Arrays.deepToString(chipInfos)
1287                    + ", ifaceType=" + ifaceType);
1288        }
1289        synchronized (mLock) {
1290            IfaceCreationData bestIfaceCreationProposal = null;
1291            for (WifiChipInfo chipInfo: chipInfos) {
1292                for (IWifiChip.ChipMode chipMode: chipInfo.availableModes) {
1293                    for (IWifiChip.ChipIfaceCombination chipIfaceCombo : chipMode
1294                            .availableCombinations) {
1295                        int[][] expandedIfaceCombos = expandIfaceCombos(chipIfaceCombo);
1296                        if (DBG) {
1297                            Log.d(TAG, chipIfaceCombo + " expands to "
1298                                    + Arrays.deepToString(expandedIfaceCombos));
1299                        }
1300
1301                        for (int[] expandedIfaceCombo: expandedIfaceCombos) {
1302                            IfaceCreationData currentProposal = canIfaceComboSupportRequest(
1303                                    chipInfo, chipMode, expandedIfaceCombo, ifaceType);
1304                            if (compareIfaceCreationData(currentProposal,
1305                                    bestIfaceCreationProposal)) {
1306                                if (DBG) Log.d(TAG, "new proposal accepted");
1307                                bestIfaceCreationProposal = currentProposal;
1308                            }
1309                        }
1310                    }
1311                }
1312            }
1313
1314            if (bestIfaceCreationProposal != null) {
1315                IWifiIface iface = executeChipReconfiguration(bestIfaceCreationProposal, ifaceType);
1316                if (iface != null) {
1317                    InterfaceCacheEntry cacheEntry = new InterfaceCacheEntry();
1318
1319                    cacheEntry.chip = bestIfaceCreationProposal.chipInfo.chip;
1320                    cacheEntry.chipId = bestIfaceCreationProposal.chipInfo.chipId;
1321                    cacheEntry.name = getName(iface);
1322                    cacheEntry.type = ifaceType;
1323                    if (destroyedListener != null) {
1324                        cacheEntry.destroyedListeners.add(
1325                                new InterfaceDestroyedListenerProxy(destroyedListener,
1326                                        looper == null ? Looper.myLooper() : looper));
1327                    }
1328
1329                    if (DBG) Log.d(TAG, "createIfaceIfPossible: added cacheEntry=" + cacheEntry);
1330                    mInterfaceInfoCache.put(cacheEntry.name, cacheEntry);
1331                    return iface;
1332                }
1333            }
1334        }
1335
1336        return null;
1337    }
1338
1339    // similar to createIfaceIfPossible - but simpler code: not looking for best option just
1340    // for any option (so terminates on first one).
1341    private boolean isItPossibleToCreateIface(WifiChipInfo[] chipInfos, int ifaceType) {
1342        if (DBG) {
1343            Log.d(TAG, "isItPossibleToCreateIface: chipInfos=" + Arrays.deepToString(chipInfos)
1344                    + ", ifaceType=" + ifaceType);
1345        }
1346
1347        for (WifiChipInfo chipInfo: chipInfos) {
1348            for (IWifiChip.ChipMode chipMode: chipInfo.availableModes) {
1349                for (IWifiChip.ChipIfaceCombination chipIfaceCombo : chipMode
1350                        .availableCombinations) {
1351                    int[][] expandedIfaceCombos = expandIfaceCombos(chipIfaceCombo);
1352                    if (DBG) {
1353                        Log.d(TAG, chipIfaceCombo + " expands to "
1354                                + Arrays.deepToString(expandedIfaceCombos));
1355                    }
1356
1357                    for (int[] expandedIfaceCombo: expandedIfaceCombos) {
1358                        if (canIfaceComboSupportRequest(chipInfo, chipMode, expandedIfaceCombo,
1359                                ifaceType) != null) {
1360                            return true;
1361                        }
1362                    }
1363                }
1364            }
1365        }
1366
1367        return false;
1368    }
1369
1370    /**
1371     * Expands (or provides an alternative representation) of the ChipIfaceCombination as all
1372     * possible combinations of interface.
1373     *
1374     * Returns [# of combinations][4 (IfaceType)]
1375     *
1376     * Note: there could be duplicates - allow (inefficient but ...).
1377     * TODO: optimize by using a Set as opposed to a []: will remove duplicates. Will need to
1378     * provide correct hashes.
1379     */
1380    private int[][] expandIfaceCombos(IWifiChip.ChipIfaceCombination chipIfaceCombo) {
1381        int numOfCombos = 1;
1382        for (IWifiChip.ChipIfaceCombinationLimit limit: chipIfaceCombo.limits) {
1383            for (int i = 0; i < limit.maxIfaces; ++i) {
1384                numOfCombos *= limit.types.size();
1385            }
1386        }
1387
1388        int[][] expandedIfaceCombos = new int[numOfCombos][IFACE_TYPES_BY_PRIORITY.length];
1389
1390        int span = numOfCombos; // span of an individual type (or sub-tree size)
1391        for (IWifiChip.ChipIfaceCombinationLimit limit: chipIfaceCombo.limits) {
1392            for (int i = 0; i < limit.maxIfaces; ++i) {
1393                span /= limit.types.size();
1394                for (int k = 0; k < numOfCombos; ++k) {
1395                    expandedIfaceCombos[k][limit.types.get((k / span) % limit.types.size())]++;
1396                }
1397            }
1398        }
1399
1400        return expandedIfaceCombos;
1401    }
1402
1403    private class IfaceCreationData {
1404        public WifiChipInfo chipInfo;
1405        public int chipModeId;
1406        public List<WifiIfaceInfo> interfacesToBeRemovedFirst;
1407
1408        @Override
1409        public String toString() {
1410            StringBuilder sb = new StringBuilder();
1411            sb.append("{chipInfo=").append(chipInfo).append(", chipModeId=").append(chipModeId)
1412                    .append(", interfacesToBeRemovedFirst=").append(interfacesToBeRemovedFirst)
1413                    .append(")");
1414            return sb.toString();
1415        }
1416    }
1417
1418    /**
1419     * Checks whether the input chip-iface-combo can support the requested interface type: if not
1420     * then returns null, if yes then returns information containing the list of interfaces which
1421     * would have to be removed first before the requested interface can be created.
1422     *
1423     * Note: the list of interfaces to be removed is EMPTY if a chip mode change is required - in
1424     * that case ALL the interfaces on the current chip have to be removed first.
1425     *
1426     * Response determined based on:
1427     * - Mode configuration: i.e. could the mode support the interface type in principle
1428     * - Priority information: i.e. are we 'allowed' to remove interfaces in order to create the
1429     *   requested interface
1430     */
1431    private IfaceCreationData canIfaceComboSupportRequest(WifiChipInfo chipInfo,
1432            IWifiChip.ChipMode chipMode, int[] chipIfaceCombo, int ifaceType) {
1433        if (DBG) {
1434            Log.d(TAG, "canIfaceComboSupportRequest: chipInfo=" + chipInfo + ", chipMode="
1435                    + chipMode + ", chipIfaceCombo=" + chipIfaceCombo + ", ifaceType=" + ifaceType);
1436        }
1437
1438        // short-circuit: does the chipIfaceCombo even support the requested type?
1439        if (chipIfaceCombo[ifaceType] == 0) {
1440            if (DBG) Log.d(TAG, "Requested type not supported by combo");
1441            return null;
1442        }
1443
1444        boolean isChipModeChangeProposed =
1445                chipInfo.currentModeIdValid && chipInfo.currentModeId != chipMode.id;
1446
1447        // short-circuit: can't change chip-mode if an existing interface on this chip has a higher
1448        // priority than the requested interface
1449        if (isChipModeChangeProposed) {
1450            for (int type: IFACE_TYPES_BY_PRIORITY) {
1451                if (chipInfo.ifaces[type].length != 0) {
1452                    if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType)) {
1453                        if (DBG) {
1454                            Log.d(TAG, "Couldn't delete existing type " + type
1455                                    + " interfaces for requested type");
1456                        }
1457                        return null;
1458                    }
1459                }
1460            }
1461
1462            // but if priority allows the mode change then we're good to go
1463            IfaceCreationData ifaceCreationData = new IfaceCreationData();
1464            ifaceCreationData.chipInfo = chipInfo;
1465            ifaceCreationData.chipModeId = chipMode.id;
1466
1467            return ifaceCreationData;
1468        }
1469
1470        // possibly supported
1471        List<WifiIfaceInfo> interfacesToBeRemovedFirst = new ArrayList<>();
1472
1473        for (int type: IFACE_TYPES_BY_PRIORITY) {
1474            int tooManyInterfaces = chipInfo.ifaces[type].length - chipIfaceCombo[type];
1475
1476            // need to count the requested interface as well
1477            if (type == ifaceType) {
1478                tooManyInterfaces += 1;
1479            }
1480
1481            if (tooManyInterfaces > 0) { // may need to delete some
1482                if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType)) {
1483                    if (DBG) {
1484                        Log.d(TAG, "Would need to delete some higher priority interfaces");
1485                    }
1486                    return null;
1487                }
1488
1489                // arbitrarily pick the first interfaces to delete
1490                for (int i = 0; i < tooManyInterfaces; ++i) {
1491                    interfacesToBeRemovedFirst.add(chipInfo.ifaces[type][i]);
1492                }
1493            }
1494        }
1495
1496        IfaceCreationData ifaceCreationData = new IfaceCreationData();
1497        ifaceCreationData.chipInfo = chipInfo;
1498        ifaceCreationData.chipModeId = chipMode.id;
1499        ifaceCreationData.interfacesToBeRemovedFirst = interfacesToBeRemovedFirst;
1500
1501        return ifaceCreationData;
1502    }
1503
1504    /**
1505     * Compares two options to create an interface and determines which is the 'best'. Returns
1506     * true if proposal 1 (val1) is better, other false.
1507     *
1508     * Note: both proposals are 'acceptable' bases on priority criteria.
1509     *
1510     * Criteria:
1511     * - Proposal is better if it means removing fewer high priority interfaces
1512     */
1513    private boolean compareIfaceCreationData(IfaceCreationData val1, IfaceCreationData val2) {
1514        if (DBG) Log.d(TAG, "compareIfaceCreationData: val1=" + val1 + ", val2=" + val2);
1515
1516        // deal with trivial case of one or the other being null
1517        if (val1 == null) {
1518            return false;
1519        } else if (val2 == null) {
1520            return true;
1521        }
1522
1523        for (int type: IFACE_TYPES_BY_PRIORITY) {
1524            // # of interfaces to be deleted: the list or all interfaces of the type if mode change
1525            int numIfacesToDelete1 = 0;
1526            if (val1.chipInfo.currentModeIdValid
1527                    && val1.chipInfo.currentModeId != val1.chipModeId) {
1528                numIfacesToDelete1 = val1.chipInfo.ifaces[type].length;
1529            } else {
1530                numIfacesToDelete1 = val1.interfacesToBeRemovedFirst.size();
1531            }
1532
1533            int numIfacesToDelete2 = 0;
1534            if (val2.chipInfo.currentModeIdValid
1535                    && val2.chipInfo.currentModeId != val2.chipModeId) {
1536                numIfacesToDelete2 = val2.chipInfo.ifaces[type].length;
1537            } else {
1538                numIfacesToDelete2 = val2.interfacesToBeRemovedFirst.size();
1539            }
1540
1541            if (numIfacesToDelete1 < numIfacesToDelete2) {
1542                if (DBG) {
1543                    Log.d(TAG, "decision based on type=" + type + ": " + numIfacesToDelete1
1544                            + " < " + numIfacesToDelete2);
1545                }
1546                return true;
1547            }
1548        }
1549
1550        // arbitrary - flip a coin
1551        if (DBG) Log.d(TAG, "proposals identical - flip a coin");
1552        return false;
1553    }
1554
1555    /**
1556     * Returns true if we're allowed to delete the existing interface type for the requested
1557     * interface type.
1558     *
1559     * Rules:
1560     * 1. Request for AP or STA will destroy any other interface (except see #4)
1561     * 2. Request for P2P will destroy NAN-only
1562     * 3. Request for NAN will not destroy any interface
1563     * --
1564     * 4. No interface will be destroyed for a requested interface of the same type
1565     */
1566    private boolean allowedToDeleteIfaceTypeForRequestedType(int existingIfaceType,
1567            int requestedIfaceType) {
1568        // rule 4
1569        if (existingIfaceType == requestedIfaceType) {
1570            return false;
1571        }
1572
1573        // rule 3
1574        if (requestedIfaceType == IfaceType.NAN) {
1575            return false;
1576        }
1577
1578        // rule 2
1579        if (requestedIfaceType == IfaceType.P2P) {
1580            return existingIfaceType == IfaceType.NAN;
1581        }
1582
1583        // rule 1, the requestIfaceType is either AP or STA
1584        return true;
1585    }
1586
1587    /**
1588     * Performs chip reconfiguration per the input:
1589     * - Removes the specified interfaces
1590     * - Reconfigures the chip to the new chip mode (if necessary)
1591     * - Creates the new interface
1592     *
1593     * Returns the newly created interface or a null on any error.
1594     */
1595    private IWifiIface executeChipReconfiguration(IfaceCreationData ifaceCreationData,
1596            int ifaceType) {
1597        if (DBG) {
1598            Log.d(TAG, "executeChipReconfiguration: ifaceCreationData=" + ifaceCreationData
1599                    + ", ifaceType=" + ifaceType);
1600        }
1601        synchronized (mLock) {
1602            try {
1603                // is this a mode change?
1604                boolean isModeConfigNeeded = !ifaceCreationData.chipInfo.currentModeIdValid
1605                        || ifaceCreationData.chipInfo.currentModeId != ifaceCreationData.chipModeId;
1606                if (DBG) Log.d(TAG, "isModeConfigNeeded=" + isModeConfigNeeded);
1607
1608                // first delete interfaces/change modes
1609                if (isModeConfigNeeded) {
1610                    // remove all interfaces pre mode-change
1611                    // TODO: is this necessary? note that even if we don't want to explicitly
1612                    // remove the interfaces we do need to call the onDeleted callbacks - which
1613                    // this does
1614                    for (WifiIfaceInfo[] ifaceInfos: ifaceCreationData.chipInfo.ifaces) {
1615                        for (WifiIfaceInfo ifaceInfo: ifaceInfos) {
1616                            removeIfaceInternal(ifaceInfo.iface); // ignore return value
1617                        }
1618                    }
1619
1620                    WifiStatus status = ifaceCreationData.chipInfo.chip.configureChip(
1621                            ifaceCreationData.chipModeId);
1622                    if (status.code != WifiStatusCode.SUCCESS) {
1623                        Log.e(TAG, "executeChipReconfiguration: configureChip error: "
1624                                + statusString(status));
1625                        return null;
1626                    }
1627                } else {
1628                    // remove all interfaces on the delete list
1629                    for (WifiIfaceInfo ifaceInfo: ifaceCreationData.interfacesToBeRemovedFirst) {
1630                        removeIfaceInternal(ifaceInfo.iface); // ignore return value
1631                    }
1632                }
1633
1634                // create new interface
1635                Mutable<WifiStatus> statusResp = new Mutable<>();
1636                Mutable<IWifiIface> ifaceResp = new Mutable<>();
1637                switch (ifaceType) {
1638                    case IfaceType.STA:
1639                        ifaceCreationData.chipInfo.chip.createStaIface(
1640                                (WifiStatus status, IWifiStaIface iface) -> {
1641                                    statusResp.value = status;
1642                                    ifaceResp.value = iface;
1643                                });
1644                        break;
1645                    case IfaceType.AP:
1646                        ifaceCreationData.chipInfo.chip.createApIface(
1647                                (WifiStatus status, IWifiApIface iface) -> {
1648                                    statusResp.value = status;
1649                                    ifaceResp.value = iface;
1650                                });
1651                        break;
1652                    case IfaceType.P2P:
1653                        ifaceCreationData.chipInfo.chip.createP2pIface(
1654                                (WifiStatus status, IWifiP2pIface iface) -> {
1655                                    statusResp.value = status;
1656                                    ifaceResp.value = iface;
1657                                });
1658                        break;
1659                    case IfaceType.NAN:
1660                        ifaceCreationData.chipInfo.chip.createNanIface(
1661                                (WifiStatus status, IWifiNanIface iface) -> {
1662                                    statusResp.value = status;
1663                                    ifaceResp.value = iface;
1664                                });
1665                        break;
1666                }
1667
1668                if (statusResp.value.code != WifiStatusCode.SUCCESS) {
1669                    Log.e(TAG, "executeChipReconfiguration: failed to create interface ifaceType="
1670                            + ifaceType + ": " + statusString(statusResp.value));
1671                    return null;
1672                }
1673
1674                return ifaceResp.value;
1675            } catch (RemoteException e) {
1676                Log.e(TAG, "executeChipReconfiguration exception: " + e);
1677                return null;
1678            }
1679        }
1680    }
1681
1682    private boolean removeIfaceInternal(IWifiIface iface) {
1683        String name = getName(iface);
1684        if (DBG) Log.d(TAG, "removeIfaceInternal: iface(name)=" + name);
1685
1686        synchronized (mLock) {
1687            if (mWifi == null) {
1688                Log.e(TAG, "removeIfaceInternal: null IWifi -- iface(name)=" + name);
1689                return false;
1690            }
1691
1692            IWifiChip chip = getChip(iface);
1693            if (chip == null) {
1694                Log.e(TAG, "removeIfaceInternal: null IWifiChip -- iface(name)=" + name);
1695                return false;
1696            }
1697
1698            if (name == null) {
1699                Log.e(TAG, "removeIfaceInternal: can't get name");
1700                return false;
1701            }
1702
1703            int type = getType(iface);
1704            if (type == -1) {
1705                Log.e(TAG, "removeIfaceInternal: can't get type -- iface(name)=" + name);
1706                return false;
1707            }
1708
1709            WifiStatus status = null;
1710            try {
1711                switch (type) {
1712                    case IfaceType.STA:
1713                        status = chip.removeStaIface(name);
1714                        break;
1715                    case IfaceType.AP:
1716                        status = chip.removeApIface(name);
1717                        break;
1718                    case IfaceType.P2P:
1719                        status = chip.removeP2pIface(name);
1720                        break;
1721                    case IfaceType.NAN:
1722                        status = chip.removeNanIface(name);
1723                        break;
1724                    default:
1725                        Log.wtf(TAG, "removeIfaceInternal: invalid type=" + type);
1726                        return false;
1727                }
1728            } catch (RemoteException e) {
1729                Log.e(TAG, "IWifiChip.removeXxxIface exception: " + e);
1730            }
1731
1732            // dispatch listeners no matter what status
1733            dispatchDestroyedListeners(name);
1734
1735            if (status != null && status.code == WifiStatusCode.SUCCESS) {
1736                return true;
1737            } else {
1738                Log.e(TAG, "IWifiChip.removeXxxIface failed: " + statusString(status));
1739                return false;
1740            }
1741        }
1742    }
1743
1744    // dispatch all available for request listeners of the specified type AND clean-out the list:
1745    // listeners are called once at most!
1746    private boolean dispatchAvailableForRequestListeners() {
1747        if (DBG) Log.d(TAG, "dispatchAvailableForRequestListeners");
1748
1749        synchronized (mLock) {
1750            WifiChipInfo[] chipInfos = getAllChipInfo();
1751            if (chipInfos == null) {
1752                Log.e(TAG, "dispatchAvailableForRequestListeners: no chip info found");
1753                stopWifi(); // major error: shutting down
1754                return false;
1755            }
1756            if (DBG) {
1757                Log.d(TAG, "dispatchAvailableForRequestListeners: chipInfos="
1758                        + Arrays.deepToString(chipInfos));
1759            }
1760
1761            for (int ifaceType : IFACE_TYPES_BY_PRIORITY) {
1762                dispatchAvailableForRequestListenersForType(ifaceType, chipInfos);
1763            }
1764        }
1765
1766        return true;
1767    }
1768
1769    private void dispatchAvailableForRequestListenersForType(int ifaceType,
1770            WifiChipInfo[] chipInfos) {
1771        if (DBG) Log.d(TAG, "dispatchAvailableForRequestListenersForType: ifaceType=" + ifaceType);
1772
1773        Set<InterfaceAvailableForRequestListenerProxy> listeners =
1774                mInterfaceAvailableForRequestListeners.get(ifaceType);
1775
1776        if (listeners.size() == 0) {
1777            return;
1778        }
1779
1780        if (!isItPossibleToCreateIface(chipInfos, ifaceType)) {
1781            if (DBG) Log.d(TAG, "Creating interface type isn't possible: ifaceType=" + ifaceType);
1782            return;
1783        }
1784
1785        if (DBG) Log.d(TAG, "It is possible to create the interface type: ifaceType=" + ifaceType);
1786        for (InterfaceAvailableForRequestListenerProxy listener : listeners) {
1787            listener.trigger();
1788        }
1789    }
1790
1791    // dispatch all destroyed listeners registered for the specified interface AND remove the
1792    // cache entry
1793    private void dispatchDestroyedListeners(String name) {
1794        if (DBG) Log.d(TAG, "dispatchDestroyedListeners: iface(name)=" + name);
1795
1796        synchronized (mLock) {
1797            InterfaceCacheEntry entry = mInterfaceInfoCache.get(name);
1798            if (entry == null) {
1799                Log.e(TAG, "dispatchDestroyedListeners: no cache entry for iface(name)=" + name);
1800                return;
1801            }
1802
1803            for (InterfaceDestroyedListenerProxy listener : entry.destroyedListeners) {
1804                listener.trigger();
1805            }
1806            entry.destroyedListeners.clear(); // for insurance (though cache entry is removed)
1807            mInterfaceInfoCache.remove(name);
1808        }
1809    }
1810
1811    // dispatch all destroyed listeners registered to all interfaces
1812    private void dispatchAllDestroyedListeners() {
1813        if (DBG) Log.d(TAG, "dispatchAllDestroyedListeners");
1814
1815        synchronized (mLock) {
1816            Iterator<Map.Entry<String, InterfaceCacheEntry>> it =
1817                    mInterfaceInfoCache.entrySet().iterator();
1818            while (it.hasNext()) {
1819                InterfaceCacheEntry entry = it.next().getValue();
1820                for (InterfaceDestroyedListenerProxy listener : entry.destroyedListeners) {
1821                    listener.trigger();
1822                }
1823                entry.destroyedListeners.clear(); // for insurance (though cache entry is removed)
1824                it.remove();
1825            }
1826        }
1827    }
1828
1829    private abstract class ListenerProxy<LISTENER>  {
1830        private static final int LISTENER_TRIGGERED = 0;
1831
1832        protected LISTENER mListener;
1833        private Handler mHandler;
1834
1835        // override equals & hash to make sure that the container HashSet is unique with respect to
1836        // the contained listener
1837        @Override
1838        public boolean equals(Object obj) {
1839            return mListener == ((ListenerProxy<LISTENER>) obj).mListener;
1840        }
1841
1842        @Override
1843        public int hashCode() {
1844            return mListener.hashCode();
1845        }
1846
1847        void trigger() {
1848            mHandler.sendMessage(mHandler.obtainMessage(LISTENER_TRIGGERED));
1849        }
1850
1851        protected abstract void action();
1852
1853        ListenerProxy(LISTENER listener, Looper looper, String tag) {
1854            mListener = listener;
1855            mHandler = new Handler(looper) {
1856                @Override
1857                public void handleMessage(Message msg) {
1858                    if (DBG) {
1859                        Log.d(tag, "ListenerProxy.handleMessage: what=" + msg.what);
1860                    }
1861                    switch (msg.what) {
1862                        case LISTENER_TRIGGERED:
1863                            action();
1864                            break;
1865                        default:
1866                            Log.e(tag, "ListenerProxy.handleMessage: unknown message what="
1867                                    + msg.what);
1868                    }
1869                }
1870            };
1871        }
1872    }
1873
1874    private class InterfaceDestroyedListenerProxy extends
1875            ListenerProxy<InterfaceDestroyedListener> {
1876        InterfaceDestroyedListenerProxy(InterfaceDestroyedListener destroyedListener,
1877                Looper looper) {
1878            super(destroyedListener, looper, "InterfaceDestroyedListenerProxy");
1879        }
1880
1881        @Override
1882        protected void action() {
1883            mListener.onDestroyed();
1884        }
1885    }
1886
1887    private class InterfaceAvailableForRequestListenerProxy extends
1888            ListenerProxy<InterfaceAvailableForRequestListener> {
1889        InterfaceAvailableForRequestListenerProxy(
1890                InterfaceAvailableForRequestListener destroyedListener, Looper looper) {
1891            super(destroyedListener, looper, "InterfaceAvailableForRequestListenerProxy");
1892        }
1893
1894        @Override
1895        protected void action() {
1896            mListener.onAvailableForRequest();
1897        }
1898    }
1899
1900    // general utilities
1901
1902    private static String statusString(WifiStatus status) {
1903        if (status == null) {
1904            return "status=null";
1905        }
1906        StringBuilder sb = new StringBuilder();
1907        sb.append(status.code).append(" (").append(status.description).append(")");
1908        return sb.toString();
1909    }
1910
1911    // Will return -1 for invalid results! Otherwise will return one of the 4 valid values.
1912    private static int getType(IWifiIface iface) {
1913        MutableInt typeResp = new MutableInt(-1);
1914        try {
1915            iface.getType((WifiStatus status, int type) -> {
1916                if (status.code == WifiStatusCode.SUCCESS) {
1917                    typeResp.value = type;
1918                } else {
1919                    Log.e(TAG, "Error on getType: " + statusString(status));
1920                }
1921            });
1922        } catch (RemoteException e) {
1923            Log.e(TAG, "Exception on getType: " + e);
1924        }
1925
1926        return typeResp.value;
1927    }
1928
1929    private static class Mutable<E> {
1930        public E value;
1931
1932        Mutable() {
1933            value = null;
1934        }
1935
1936        Mutable(E value) {
1937            this.value = value;
1938        }
1939    }
1940
1941    /**
1942     * Dump the internal state of the class.
1943     */
1944    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1945        pw.println("HalDeviceManager:");
1946        pw.println("  mServiceManager: " + mServiceManager);
1947        pw.println("  mWifi: " + mWifi);
1948        pw.println("  mManagerStatusListeners: " + mManagerStatusListeners);
1949        pw.println("  mInterfaceAvailableForRequestListeners: "
1950                + mInterfaceAvailableForRequestListeners);
1951        pw.println("  mInterfaceInfoCache: " + mInterfaceInfoCache);
1952    }
1953}
1954