SupplicantP2pIfaceHal.java revision b8b3fb8228a1f90106bad8c59ce006b81ef7921c
1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.wifi;
18
19import android.hardware.wifi.supplicant.V1_0.ISupplicant;
20import android.hardware.wifi.supplicant.V1_0.ISupplicantIface;
21import android.hardware.wifi.supplicant.V1_0.ISupplicantNetwork;
22import android.hardware.wifi.supplicant.V1_0.ISupplicantP2pIface;
23import android.hardware.wifi.supplicant.V1_0.ISupplicantP2pIfaceCallback;
24import android.hardware.wifi.supplicant.V1_0.ISupplicantP2pNetwork;
25import android.hardware.wifi.supplicant.V1_0.IfaceType;
26import android.hardware.wifi.supplicant.V1_0.SupplicantStatus;
27import android.hardware.wifi.supplicant.V1_0.SupplicantStatusCode;
28import android.hidl.manager.V1_0.IServiceManager;
29import android.hidl.manager.V1_0.IServiceNotification;
30import android.net.wifi.WpsInfo;
31import android.net.wifi.p2p.WifiP2pConfig;
32import android.net.wifi.p2p.WifiP2pDevice;
33import android.net.wifi.p2p.WifiP2pGroup;
34import android.net.wifi.p2p.WifiP2pGroupList;
35import android.net.wifi.p2p.WifiP2pManager;
36import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
37import android.os.HwRemoteBinder;
38import android.os.RemoteException;
39import android.text.TextUtils;
40import android.util.Log;
41
42import com.android.internal.util.ArrayUtils;
43import com.android.server.wifi.util.NativeUtil;
44
45import java.nio.ByteBuffer;
46import java.nio.ByteOrder;
47import java.util.ArrayList;
48import java.util.List;
49import java.util.regex.Matcher;
50import java.util.regex.Pattern;
51
52/**
53 * Native calls sending requests to the P2P Hals, and callbacks for receiving P2P events
54 *
55 * {@hide}
56 */
57public class SupplicantP2pIfaceHal {
58    private static final boolean DBG = true;
59    private static final String TAG = "SupplicantP2pIfaceHal";
60    private static final int RESULT_NOT_VALID = -1;
61    private static final int DEFAULT_GROUP_OWNER_INTENT = 6;
62    /**
63     * Regex pattern for extracting the wps device type bytes.
64     * Matches a strings like the following: "<categ>-<OUI>-<subcateg>";
65     */
66    private static final Pattern WPS_DEVICE_TYPE_PATTERN =
67            Pattern.compile("^(\\d{1,2})-([0-9a-fA-F]{8})-(\\d{1,2})$");
68
69    private Object mLock = new Object();
70
71    // Supplicant HAL HIDL interface objects
72    private IServiceManager mIServiceManager = null;
73    private ISupplicant mISupplicant = null;
74    private ISupplicantIface mHidlSupplicantIface = null;
75    private ISupplicantP2pIface mISupplicantP2pIface = null;
76    private final IServiceNotification mServiceNotificationCallback =
77            new IServiceNotification.Stub() {
78        public void onRegistration(String fqName, String name, boolean preexisting) {
79            synchronized (mLock) {
80                if (DBG) {
81                    Log.i(TAG, "IServiceNotification.onRegistration for: " + fqName
82                            + ", " + name + " preexisting=" + preexisting);
83                }
84                if (!initSupplicantService() || !initSupplicantP2pIface()) {
85                    Log.e(TAG, "initalizing ISupplicantIfaces failed.");
86                    supplicantServiceDiedHandler();
87                } else {
88                    Log.i(TAG, "Completed initialization of ISupplicant interfaces.");
89                }
90            }
91        }
92    };
93    private final HwRemoteBinder.DeathRecipient mServiceManagerDeathRecipient =
94            cookie -> {
95                Log.w(TAG, "IServiceManager died: cookie=" + cookie);
96                synchronized (mLock) {
97                    supplicantServiceDiedHandler();
98                    mIServiceManager = null; // Will need to register a new ServiceNotification
99                }
100            };
101    private final HwRemoteBinder.DeathRecipient mSupplicantDeathRecipient =
102            cookie -> {
103                Log.w(TAG, "ISupplicant/ISupplicantStaIface died: cookie=" + cookie);
104                synchronized (mLock) {
105                    supplicantServiceDiedHandler();
106                }
107            };
108
109    private final WifiMonitor mMonitor;
110    private SupplicantP2pIfaceCallback mCallback = null;
111
112    public SupplicantP2pIfaceHal(WifiMonitor monitor) {
113        mMonitor = monitor;
114    }
115
116    private boolean linkToServiceManagerDeath() {
117        if (mIServiceManager == null) return false;
118        try {
119            if (!mIServiceManager.linkToDeath(mServiceManagerDeathRecipient, 0)) {
120                Log.wtf(TAG, "Error on linkToDeath on IServiceManager");
121                supplicantServiceDiedHandler();
122                mIServiceManager = null; // Will need to register a new ServiceNotification
123                return false;
124            }
125        } catch (RemoteException e) {
126            Log.e(TAG, "IServiceManager.linkToDeath exception", e);
127            return false;
128        }
129        return true;
130    }
131
132    /**
133     * Registers a service notification for the ISupplicant service, which triggers intialization of
134     * the ISupplicantP2pIface
135     * @return true if the service notification was successfully registered
136     */
137    public boolean initialize() {
138        if (DBG) Log.i(TAG, "Registering ISupplicant service ready callback.");
139        synchronized (mLock) {
140            if (mIServiceManager != null) {
141                Log.i(TAG, "Supplicant HAL already initialized.");
142                // Already have an IServiceManager and serviceNotification registered, don't
143                // don't register another.
144                return true;
145            }
146            mISupplicant = null;
147            mISupplicantP2pIface = null;
148            try {
149                mIServiceManager = getServiceManagerMockable();
150                if (mIServiceManager == null) {
151                    Log.e(TAG, "Failed to get HIDL Service Manager");
152                    return false;
153                }
154                if (!linkToServiceManagerDeath()) {
155                    return false;
156                }
157                /* TODO(b/33639391) : Use the new ISupplicant.registerForNotifications() once it
158                   exists */
159                if (!mIServiceManager.registerForNotifications(
160                        ISupplicant.kInterfaceName, "", mServiceNotificationCallback)) {
161                    Log.e(TAG, "Failed to register for notifications to "
162                            + ISupplicant.kInterfaceName);
163                    mIServiceManager = null; // Will need to register a new ServiceNotification
164                    return false;
165                }
166
167                // Successful completion by the end of the 'try' block. This will prevent reporting
168                // proper initialization after exception is caught.
169                return true;
170            } catch (RemoteException e) {
171                Log.e(TAG, "Exception while trying to register a listener for ISupplicant service: "
172                        + e);
173                supplicantServiceDiedHandler();
174            }
175            return false;
176        }
177    }
178
179    private boolean linkToSupplicantDeath() {
180        if (mISupplicant == null) return false;
181        try {
182            if (!mISupplicant.linkToDeath(mSupplicantDeathRecipient, 0)) {
183                Log.wtf(TAG, "Error on linkToDeath on ISupplicant");
184                supplicantServiceDiedHandler();
185                return false;
186            }
187        } catch (RemoteException e) {
188            Log.e(TAG, "ISupplicant.linkToDeath exception", e);
189            return false;
190        }
191        return true;
192    }
193
194    private boolean initSupplicantService() {
195        synchronized (mLock) {
196            try {
197                mISupplicant = getSupplicantMockable();
198            } catch (RemoteException e) {
199                Log.e(TAG, "ISupplicant.getService exception: " + e);
200                return false;
201            }
202            if (mISupplicant == null) {
203                Log.e(TAG, "Got null ISupplicant service. Stopping supplicant HIDL startup");
204                return false;
205            }
206            if (!linkToSupplicantDeath()) {
207                return false;
208            }
209        }
210        return true;
211    }
212
213    private boolean linkToSupplicantP2pIfaceDeath() {
214        if (mISupplicantP2pIface == null) return false;
215        try {
216            if (!mISupplicantP2pIface.linkToDeath(mSupplicantDeathRecipient, 0)) {
217                Log.wtf(TAG, "Error on linkToDeath on ISupplicantP2pIface");
218                supplicantServiceDiedHandler();
219                return false;
220            }
221        } catch (RemoteException e) {
222            Log.e(TAG, "ISupplicantP2pIface.linkToDeath exception", e);
223            return false;
224        }
225        return true;
226    }
227
228    private boolean initSupplicantP2pIface() {
229        synchronized (mLock) {
230            /** List all supplicant Ifaces */
231            final ArrayList<ISupplicant.IfaceInfo> supplicantIfaces = new ArrayList();
232            try {
233                mISupplicant.listInterfaces((SupplicantStatus status,
234                        ArrayList<ISupplicant.IfaceInfo> ifaces) -> {
235                    if (status.code != SupplicantStatusCode.SUCCESS) {
236                        Log.e(TAG, "Getting Supplicant Interfaces failed: " + status.code);
237                        return;
238                    }
239                    supplicantIfaces.addAll(ifaces);
240                });
241            } catch (RemoteException e) {
242                Log.e(TAG, "ISupplicant.listInterfaces exception: " + e);
243                return false;
244            }
245            if (supplicantIfaces.size() == 0) {
246                Log.e(TAG, "Got zero HIDL supplicant ifaces. Stopping supplicant HIDL startup.");
247                return false;
248            }
249            SupplicantResult<ISupplicantIface> supplicantIface =
250                    new SupplicantResult("getInterface()");
251            for (ISupplicant.IfaceInfo ifaceInfo : supplicantIfaces) {
252                if (ifaceInfo.type == IfaceType.P2P) {
253                    try {
254                        mISupplicant.getInterface(ifaceInfo,
255                                (SupplicantStatus status, ISupplicantIface iface) -> {
256                                if (status.code != SupplicantStatusCode.SUCCESS) {
257                                    Log.e(TAG, "Failed to get ISupplicantIface " + status.code);
258                                    return;
259                                }
260                                supplicantIface.setResult(status, iface);
261                            });
262                    } catch (RemoteException e) {
263                        Log.e(TAG, "ISupplicant.getInterface exception: " + e);
264                        return false;
265                    }
266                    break;
267                }
268            }
269
270            if (supplicantIface.getResult() == null) {
271                Log.e(TAG, "initSupplicantP2pIface got null iface");
272                return false;
273            }
274            mISupplicantP2pIface = getP2pIfaceMockable(supplicantIface.getResult());
275            if (!linkToSupplicantP2pIfaceDeath()) {
276                return false;
277            }
278        }
279
280        if (mISupplicantP2pIface != null && mMonitor != null) {
281            // TODO(ender): Get rid of hard-coded interface name, which is
282            // assumed to be the group interface name in several other classes
283            // ("p2p0" should probably become getName()).
284            mCallback = new SupplicantP2pIfaceCallback("p2p0", mMonitor);
285            if (!registerCallback(mCallback)) {
286                Log.e(TAG, "Callback registration failed. Initialization incomplete.");
287                return false;
288            }
289        }
290
291        return true;
292    }
293
294    private void supplicantServiceDiedHandler() {
295        synchronized (mLock) {
296            mISupplicant = null;
297            mISupplicantP2pIface = null;
298        }
299    }
300
301
302    /**
303     * Signals whether Initialization completed successfully.
304     */
305    public boolean isInitializationStarted() {
306        return mIServiceManager != null;
307    }
308
309    /**
310     * Signals whether Initialization completed successfully. Only necessary for testing, is not
311     * needed to guard calls etc.
312     */
313    public boolean isInitializationComplete() {
314        return mISupplicantP2pIface != null;
315    }
316
317    /**
318     * Wrapper functions to access static HAL methods, created to be mockable in unit tests
319     */
320    protected IServiceManager getServiceManagerMockable() throws RemoteException {
321        return IServiceManager.getService();
322    }
323
324    protected ISupplicant getSupplicantMockable() throws RemoteException {
325        return ISupplicant.getService();
326    }
327
328    protected ISupplicantP2pIface getP2pIfaceMockable(ISupplicantIface iface) {
329        return ISupplicantP2pIface.asInterface(iface.asBinder());
330    }
331
332    protected ISupplicantP2pNetwork getP2pNetworkMockable(ISupplicantNetwork network) {
333        return ISupplicantP2pNetwork.asInterface(network.asBinder());
334    }
335
336    protected static void logd(String s) {
337        if (DBG) Log.d(TAG, s);
338    }
339
340    protected static void logCompletion(String operation, SupplicantStatus status) {
341        if (status == null) {
342            Log.w(TAG, operation + " failed: no status code returned.");
343        } else if (status.code == SupplicantStatusCode.SUCCESS) {
344            logd(operation + " completed successfully.");
345        } else {
346            Log.w(TAG, operation + " failed: " + status.code + " (" + status.debugMessage + ")");
347        }
348    }
349
350
351    /**
352     * Returns false if SupplicantP2pIface is null, and logs failure to call methodStr
353     */
354    private boolean checkSupplicantP2pIfaceAndLogFailure(String method) {
355        if (mISupplicantP2pIface == null) {
356            Log.e(TAG, "Can't call " + method + ": ISupplicantP2pIface is null");
357            return false;
358        }
359        return true;
360    }
361
362    private int wpsInfoToConfigMethod(int info) {
363        switch (info) {
364            case WpsInfo.PBC:
365                return ISupplicantP2pIface.WpsProvisionMethod.PBC;
366
367            case WpsInfo.DISPLAY:
368                return ISupplicantP2pIface.WpsProvisionMethod.DISPLAY;
369
370            case WpsInfo.KEYPAD:
371            case WpsInfo.LABEL:
372                return ISupplicantP2pIface.WpsProvisionMethod.KEYPAD;
373
374            default:
375                Log.e(TAG, "Unsupported WPS provision method: " + info);
376                return RESULT_NOT_VALID;
377        }
378    }
379
380    /**
381     * Retrieves the name of the network interface.
382     *
383     * @return name Name of the network interface, e.g., wlan0
384     */
385    public String getName() {
386        synchronized (mLock) {
387            if (!checkSupplicantP2pIfaceAndLogFailure("getName")) return null;
388            SupplicantResult<String> result = new SupplicantResult("getName()");
389
390            try {
391                mISupplicantP2pIface.getName(
392                        (SupplicantStatus status, String name) -> {
393                            result.setResult(status, name);
394                        });
395            } catch (RemoteException e) {
396                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
397                supplicantServiceDiedHandler();
398            }
399            return result.getResult();
400        }
401    }
402
403
404    /**
405     * Register for callbacks from this interface.
406     *
407     * These callbacks are invoked for events that are specific to this interface.
408     * Registration of multiple callback objects is supported. These objects must
409     * be automatically deleted when the corresponding client process is dead or
410     * if this interface is removed.
411     *
412     * @param callback An instance of the |ISupplicantP2pIfaceCallback| HIDL
413     *        interface object.
414     * @return boolean value indicating whether operation was successful.
415     */
416    public boolean registerCallback(ISupplicantP2pIfaceCallback receiver) {
417        synchronized (mLock) {
418            if (!checkSupplicantP2pIfaceAndLogFailure("registerCallback")) return false;
419            SupplicantResult<Void> result = new SupplicantResult("registerCallback()");
420            try {
421                result.setResult(mISupplicantP2pIface.registerCallback(receiver));
422            } catch (RemoteException e) {
423                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
424                supplicantServiceDiedHandler();
425            }
426            return result.isSuccess();
427        }
428    }
429
430
431    /**
432     * Initiate a P2P service discovery with a (optional) timeout.
433     *
434     * @param timeout Max time to be spent is peforming discovery.
435     *        Set to 0 to indefinely continue discovery untill and explicit
436     *        |stopFind| is sent.
437     * @return boolean value indicating whether operation was successful.
438     */
439    public boolean find(int timeout) {
440        synchronized (mLock) {
441            if (!checkSupplicantP2pIfaceAndLogFailure("find")) return false;
442
443            if (timeout < 0) {
444                Log.e(TAG, "Invalid timeout value: " + timeout);
445                return false;
446            }
447            SupplicantResult<Void> result = new SupplicantResult("find(" + timeout + ")");
448            try {
449                result.setResult(mISupplicantP2pIface.find(timeout));
450            } catch (RemoteException e) {
451                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
452                supplicantServiceDiedHandler();
453            }
454            return result.isSuccess();
455        }
456    }
457
458
459    /**
460     * Stop an ongoing P2P service discovery.
461     *
462     * @return boolean value indicating whether operation was successful.
463     */
464    public boolean stopFind() {
465        synchronized (mLock) {
466            if (!checkSupplicantP2pIfaceAndLogFailure("stopFind")) return false;
467            SupplicantResult<Void> result = new SupplicantResult("stopFind()");
468            try {
469                result.setResult(mISupplicantP2pIface.stopFind());
470            } catch (RemoteException e) {
471                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
472                supplicantServiceDiedHandler();
473            }
474            return result.isSuccess();
475        }
476    }
477
478
479    /**
480     * Flush P2P peer table and state.
481     *
482     * @return boolean value indicating whether operation was successful.
483     */
484    public boolean flush() {
485        synchronized (mLock) {
486            if (!checkSupplicantP2pIfaceAndLogFailure("flush")) return false;
487            SupplicantResult<Void> result = new SupplicantResult("flush()");
488            try {
489                result.setResult(mISupplicantP2pIface.flush());
490            } catch (RemoteException e) {
491                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
492                supplicantServiceDiedHandler();
493            }
494            return result.isSuccess();
495        }
496    }
497
498
499    /**
500     * This command can be used to flush all services from the
501     * device.
502     *
503     * @return boolean value indicating whether operation was successful.
504     */
505    public boolean serviceFlush() {
506        synchronized (mLock) {
507            if (!checkSupplicantP2pIfaceAndLogFailure("serviceFlush")) return false;
508            SupplicantResult<Void> result = new SupplicantResult("serviceFlush()");
509            try {
510                result.setResult(mISupplicantP2pIface.flushServices());
511            } catch (RemoteException e) {
512                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
513                supplicantServiceDiedHandler();
514            }
515            return result.isSuccess();
516        }
517    }
518
519
520    /**
521     * Turn on/off power save mode for the interface.
522     *
523     * @param groupIfName Group interface name to use.
524     * @param enable Indicate if power save is to be turned on/off.
525     *
526     * @return boolean value indicating whether operation was successful.
527     */
528    public boolean setPowerSave(String groupIfName, boolean enable) {
529        synchronized (mLock) {
530            if (!checkSupplicantP2pIfaceAndLogFailure("setPowerSave")) return false;
531            SupplicantResult<Void> result = new SupplicantResult(
532                    "setPowerSave(" + groupIfName + ", " + enable + ")");
533            try {
534                result.setResult(mISupplicantP2pIface.setPowerSave(groupIfName, enable));
535            } catch (RemoteException e) {
536                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
537                supplicantServiceDiedHandler();
538            }
539            return result.isSuccess();
540        }
541    }
542
543
544    /**
545     * Set the Maximum idle time in seconds for P2P groups.
546     * This value controls how long a P2P group is maintained after there
547     * is no other members in the group. As a group owner, this means no
548     * associated stations in the group. As a P2P client, this means no
549     * group owner seen in scan results.
550     *
551     * @param groupIfName Group interface name to use.
552     * @param timeoutInSec Timeout value in seconds.
553     *
554     * @return boolean value indicating whether operation was successful.
555     */
556    public boolean setGroupIdle(String groupIfName, int timeoutInSec) {
557        synchronized (mLock) {
558            if (!checkSupplicantP2pIfaceAndLogFailure("setGroupIdle")) return false;
559            // Basic checking here. Leave actual parameter validation to supplicant.
560            if (timeoutInSec < 0) {
561                Log.e(TAG, "Invalid group timeout value " + timeoutInSec);
562                return false;
563            }
564
565            SupplicantResult<Void> result = new SupplicantResult(
566                    "setGroupIdle(" + groupIfName + ", " + timeoutInSec+ ")");
567            try {
568                result.setResult(mISupplicantP2pIface.setGroupIdle(groupIfName, timeoutInSec));
569            } catch (RemoteException e) {
570                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
571                supplicantServiceDiedHandler();
572            }
573            return result.isSuccess();
574        }
575    }
576
577
578    /**
579     * Set the postfix to be used for P2P SSID's.
580     *
581     * @param postfix String to be appended to SSID.
582     *
583     * @return boolean value indicating whether operation was successful.
584     */
585    public boolean setSsidPostfix(String postfix) {
586        synchronized (mLock) {
587            if (!checkSupplicantP2pIfaceAndLogFailure("setSsidPostfix")) return false;
588            // Basic checking here. Leave actual parameter validation to supplicant.
589            if (postfix == null) {
590                Log.e(TAG, "Invalid SSID postfix value (null).");
591                return false;
592            }
593
594            SupplicantResult<Void> result = new SupplicantResult("setSsidPostfix(" + postfix + ")");
595            try {
596                result.setResult(mISupplicantP2pIface.setSsidPostfix(
597                        NativeUtil.decodeSsid("\"" + postfix + "\"")));
598            } catch (RemoteException e) {
599                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
600                supplicantServiceDiedHandler();
601            } catch (Exception e) {
602                Log.e(TAG, "Could not decode SSID.", e);
603                return false;
604            }
605
606            return result.isSuccess();
607        }
608    }
609
610
611    /**
612     * Start P2P group formation with a discovered P2P peer. This includes
613     * optional group owner negotiation, group interface setup, provisioning,
614     * and establishing data connection.
615     *
616     * @param config Configuration to use to connect to remote device.
617     * @param joinExistingGroup Indicates that this is a command to join an
618     *        existing group as a client. It skips the group owner negotiation
619     *        part. This must send a Provision Discovery Request message to the
620     *        target group owner before associating for WPS provisioning.
621     *
622     * @return String containing generated pin, if selected provision method
623     *        uses PIN.
624     */
625    public String connect(WifiP2pConfig config, boolean joinExistingGroup) {
626        if (config == null) return null;
627        synchronized (mLock) {
628            if (!checkSupplicantP2pIfaceAndLogFailure("setSsidPostfix")) return null;
629
630            if (config == null) {
631                Log.e(TAG, "Could not connect: null config.");
632                return null;
633            }
634
635            if (config.deviceAddress == null) {
636                Log.e(TAG, "Could not parse null mac address.");
637                return null;
638            }
639
640            byte[] peerAddress = null;
641            try {
642                peerAddress = NativeUtil.macAddressToByteArray(config.deviceAddress);
643            } catch (Exception e) {
644                Log.e(TAG, "Could not parse peer mac address.", e);
645                return null;
646            }
647
648            int provisionMethod = wpsInfoToConfigMethod(config.wps.setup);
649            if (provisionMethod == RESULT_NOT_VALID) {
650                Log.e(TAG, "Invalid WPS config method: " + config.wps.setup);
651                return null;
652            }
653
654            // NOTE: preSelectedPin cannot be null, otherwise hal would crash.
655            String preSelectedPin = "";
656            if (provisionMethod == ISupplicantP2pIface.WpsProvisionMethod.DISPLAY) {
657                preSelectedPin = config.wps.pin;
658                if (preSelectedPin == null) {
659                    Log.e(TAG, "PIN must be supplied when provision method is DISPLAY.");
660                    return null;
661                }
662            }
663
664            boolean persistent = (config.netId == WifiP2pGroup.PERSISTENT_NET_ID);
665
666            int goIntent = 0;
667            if (!joinExistingGroup) {
668                int groupOwnerIntent = config.groupOwnerIntent;
669                if (groupOwnerIntent < 0 || groupOwnerIntent > 15) {
670                    groupOwnerIntent = DEFAULT_GROUP_OWNER_INTENT;
671                }
672            }
673
674            SupplicantResult<String> result = new SupplicantResult(
675                    "connect(" + config.deviceAddress + ")");
676            try {
677                mISupplicantP2pIface.connect(
678                        peerAddress, provisionMethod, preSelectedPin, joinExistingGroup,
679                        persistent, goIntent,
680                        (SupplicantStatus status, String generatedPin) -> {
681                            result.setResult(status, generatedPin);
682                        });
683            } catch (RemoteException e) {
684                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
685                supplicantServiceDiedHandler();
686            }
687            return result.getResult();
688        }
689    }
690
691    /**
692     * Cancel an ongoing P2P group formation and joining-a-group related
693     * operation. This operation unauthorizes the specific peer device (if any
694     * had been authorized to start group formation), stops P2P find (if in
695     * progress), stops pending operations for join-a-group, and removes the
696     * P2P group interface (if one was used) that is in the WPS provisioning
697     * step. If the WPS provisioning step has been completed, the group is not
698     * terminated.
699     *
700     * @return boolean value indicating whether operation was successful.
701     */
702    public boolean cancelConnect() {
703        synchronized (mLock) {
704            if (!checkSupplicantP2pIfaceAndLogFailure("cancelConnect")) return false;
705            SupplicantResult<Void> result = new SupplicantResult("cancelConnect()");
706            try {
707                result.setResult(mISupplicantP2pIface.cancelConnect());
708            } catch (RemoteException e) {
709                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
710                supplicantServiceDiedHandler();
711            }
712            return result.isSuccess();
713        }
714    }
715
716
717    /**
718     * Send P2P provision discovery request to the specified peer. The
719     * parameters for this command are the P2P device address of the peer and the
720     * desired configuration method.
721     *
722     * @param config Config class describing peer setup.
723     *
724     * @return boolean value indicating whether operation was successful.
725     */
726    public boolean provisionDiscovery(WifiP2pConfig config) {
727        if (config == null) return false;
728        synchronized (mLock) {
729            if (!checkSupplicantP2pIfaceAndLogFailure("provisionDiscovery")) return false;
730
731            int targetMethod = wpsInfoToConfigMethod(config.wps.setup);
732            if (targetMethod == -1) {
733                Log.e(TAG, "Unrecognized WPS configuration method: " + config.wps.setup);
734                return false;
735            }
736
737            if (config.deviceAddress == null) {
738                Log.e(TAG, "Cannot parse null mac address.");
739                return false;
740            }
741            byte[] macAddress = null;
742            try {
743                macAddress = NativeUtil.macAddressToByteArray(config.deviceAddress);
744            } catch (Exception e) {
745                Log.e(TAG, "Could not parse peer mac address.", e);
746                return false;
747            }
748
749            SupplicantResult<Void> result = new SupplicantResult(
750                    "provisionDiscovery(" + config.deviceAddress + ", " + config.wps.setup + ")");
751            try {
752                result.setResult(mISupplicantP2pIface.provisionDiscovery(macAddress, targetMethod));
753            } catch (RemoteException e) {
754                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
755                supplicantServiceDiedHandler();
756            }
757
758            return result.isSuccess();
759        }
760    }
761
762
763    /**
764     * Invite a device to a persistent group.
765     * If the peer device is the group owner of the persistent group, the peer
766     * parameter is not needed. Otherwise it is used to specify which
767     * device to invite. |goDeviceAddress| parameter may be used to override
768     * the group owner device address for Invitation Request should it not be
769     * known for some reason (this should not be needed in most cases).
770     *
771     * @param group Group object to use.
772     * @param peerAddress MAC address of the device to invite.
773     *
774     * @return boolean value indicating whether operation was successful.
775     */
776    public boolean invite(WifiP2pGroup group, String peerAddress) {
777        if (TextUtils.isEmpty(peerAddress)) return false;
778        synchronized (mLock) {
779            if (!checkSupplicantP2pIfaceAndLogFailure("invite")) return false;
780            if (group == null) {
781                Log.e(TAG, "Cannot invite to null group.");
782                return false;
783            }
784
785            if (group.getOwner() == null) {
786                Log.e(TAG, "Cannot invite to group with null owner.");
787                return false;
788            }
789
790            if (group.getOwner().deviceAddress == null) {
791                Log.e(TAG, "Group owner has no mac address.");
792                return false;
793            }
794
795            byte[] ownerMacAddress = null;
796            try {
797                ownerMacAddress = NativeUtil.macAddressToByteArray(group.getOwner().deviceAddress);
798            } catch (Exception e) {
799                Log.e(TAG, "Group owner mac address parse error.", e);
800                return false;
801            }
802
803            if (peerAddress == null) {
804                Log.e(TAG, "Cannot parse peer mac address.");
805                return false;
806            }
807
808            byte[] peerMacAddress;
809            try {
810                peerMacAddress = NativeUtil.macAddressToByteArray(peerAddress);
811            } catch (Exception e) {
812                Log.e(TAG, "Peer mac address parse error.", e);
813                return false;
814            }
815
816            SupplicantResult<Void> result = new SupplicantResult(
817                    "invite(" + group.getInterface() + ", " +
818                    group.getOwner().deviceAddress + ", " + peerAddress + ")");
819            try {
820                result.setResult(mISupplicantP2pIface.invite(
821                        group.getInterface(), ownerMacAddress, peerMacAddress));
822            } catch (RemoteException e) {
823                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
824                supplicantServiceDiedHandler();
825            }
826            return result.isSuccess();
827        }
828    }
829
830
831    /**
832     * Reject connection attempt from a peer (specified with a device
833     * address). This is a mechanism to reject a pending group owner negotiation
834     * with a peer and request to automatically block any further connection or
835     * discovery of the peer.
836     *
837     * @param peerAddress MAC address of the device to reject.
838     *
839     * @return boolean value indicating whether operation was successful.
840     */
841    public boolean reject(String peerAddress) {
842        synchronized (mLock) {
843            if (!checkSupplicantP2pIfaceAndLogFailure("reject")) return false;
844
845            if (peerAddress == null) {
846                Log.e(TAG, "Cannot parse rejected peer's mac address.");
847                return false;
848            }
849            byte[] macAddress = null;
850            try {
851                macAddress = NativeUtil.macAddressToByteArray(peerAddress);
852            } catch (Exception e) {
853                Log.e(TAG, "Could not parse peer mac address.", e);
854                return false;
855            }
856
857            SupplicantResult<Void> result =
858                    new SupplicantResult("reject(" + peerAddress + ")");
859            try {
860                result.setResult(mISupplicantP2pIface.reject(macAddress));
861            } catch (RemoteException e) {
862                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
863                supplicantServiceDiedHandler();
864            }
865
866            return result.isSuccess();
867        }
868    }
869
870
871    /**
872     * Gets the MAC address of the device.
873     *
874     * @return MAC address of the device.
875     */
876    public String getDeviceAddress() {
877        synchronized (mLock) {
878            if (!checkSupplicantP2pIfaceAndLogFailure("getDeviceAddress")) return null;
879            SupplicantResult<String> result = new SupplicantResult("getDeviceAddress()");
880            try {
881                mISupplicantP2pIface.getDeviceAddress((SupplicantStatus status, byte[] address) -> {
882                    String parsedAddress = null;
883                    try {
884                        parsedAddress = NativeUtil.macAddressFromByteArray(address);
885                    } catch (Exception e) {
886                        Log.e(TAG, "Could not process reported address.", e);
887                    }
888                    result.setResult(status, parsedAddress);
889                });
890            } catch (RemoteException e) {
891                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
892                supplicantServiceDiedHandler();
893                return null;
894            }
895
896            return result.getResult();
897        }
898    }
899
900
901    /**
902     * Gets the operational SSID of the device.
903     *
904     * @param address MAC address of the peer.
905     *
906     * @return SSID of the device.
907     */
908    public String getSsid(String address) {
909        synchronized (mLock) {
910            if (!checkSupplicantP2pIfaceAndLogFailure("getSsid")) return null;
911
912            if (address == null) {
913                Log.e(TAG, "Cannot parse peer mac address.");
914                return null;
915            }
916            byte[] macAddress = null;
917            try {
918                macAddress = NativeUtil.macAddressToByteArray(address);
919            } catch (Exception e) {
920                Log.e(TAG, "Could not parse mac address.", e);
921                return null;
922            }
923
924            SupplicantResult<String> result =
925                    new SupplicantResult("getSsid(" + address + ")");
926            try {
927                mISupplicantP2pIface.getSsid(
928                        macAddress, (SupplicantStatus status, ArrayList<Byte> ssid) -> {
929                            String ssidString = null;
930                            if (ssid != null) {
931                                try {
932                                    ssidString = NativeUtil.encodeSsid(ssid);
933                                } catch (Exception e) {
934                                    Log.e(TAG, "Could not encode SSID.", e);
935                                }
936                            }
937                            result.setResult(status, ssidString);
938                        });
939            } catch (RemoteException e) {
940                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
941                supplicantServiceDiedHandler();
942                return null;
943            }
944
945            return result.getResult();
946        }
947    }
948
949
950    /**
951     * Reinvoke a device from a persistent group.
952     *
953     * @param networkId Used to specify the persistent group.
954     * @param peerAddress MAC address of the device to reinvoke.
955     *
956     * @return true, if operation was successful.
957     */
958    public boolean reinvoke(int networkId, String peerAddress) {
959        if (TextUtils.isEmpty(peerAddress) || networkId < 0) return false;
960        synchronized (mLock) {
961            if (!checkSupplicantP2pIfaceAndLogFailure("reinvoke")) return false;
962            if (peerAddress == null) {
963                Log.e(TAG, "Cannot parse peer mac address.");
964                return false;
965            }
966            byte[] macAddress = null;
967            try {
968                macAddress = NativeUtil.macAddressToByteArray(peerAddress);
969            } catch (Exception e) {
970                Log.e(TAG, "Could not parse mac address.", e);
971                return false;
972            }
973
974            SupplicantResult<Void> result = new SupplicantResult(
975                    "reinvoke(" + networkId + ", " + peerAddress + ")");
976            try {
977                result.setResult(mISupplicantP2pIface.reinvoke(networkId, macAddress));
978            } catch (RemoteException e) {
979                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
980                supplicantServiceDiedHandler();
981            }
982
983            return result.isSuccess();
984        }
985    }
986
987
988    /**
989     * Set up a P2P group owner manually (i.e., without group owner
990     * negotiation with a specific peer). This is also known as autonomous
991     * group owner.
992     *
993     * @param networkId Used to specify the restart of a persistent group.
994     * @param isPersistent Used to request a persistent group to be formed.
995     *
996     * @return true, if operation was successful.
997     */
998    public boolean groupAdd(int networkId, boolean isPersistent) {
999        synchronized (mLock) {
1000            if (!checkSupplicantP2pIfaceAndLogFailure("groupAdd")) return false;
1001            SupplicantResult<Void> result =
1002                    new SupplicantResult("groupAdd(" + networkId + ", " + isPersistent + ")");
1003            try {
1004                result.setResult(mISupplicantP2pIface.addGroup(isPersistent, networkId));
1005            } catch (RemoteException e) {
1006                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1007                supplicantServiceDiedHandler();
1008            }
1009            return result.isSuccess();
1010        }
1011    }
1012
1013    /**
1014     * Set up a P2P group owner manually.
1015     * This is a helper method that invokes groupAdd(networkId, isPersistent) internally.
1016     *
1017     * @param isPersistent Used to request a persistent group to be formed.
1018     *
1019     * @return true, if operation was successful.
1020     */
1021    public boolean groupAdd(boolean isPersistent) {
1022        // Supplicant expects networkId to be -1 if not supplied.
1023        return groupAdd(-1, isPersistent);
1024    }
1025
1026
1027    /**
1028     * Terminate a P2P group. If a new virtual network interface was used for
1029     * the group, it must also be removed. The network interface name of the
1030     * group interface is used as a parameter for this command.
1031     *
1032     * @param groupName Group interface name to use.
1033     *
1034     * @return true, if operation was successful.
1035     */
1036    public boolean groupRemove(String groupName) {
1037        if (TextUtils.isEmpty(groupName)) return false;
1038        synchronized (mLock) {
1039            if (!checkSupplicantP2pIfaceAndLogFailure("groupRemove")) return false;
1040            SupplicantResult<Void> result = new SupplicantResult("groupRemove(" + groupName + ")");
1041            try {
1042                result.setResult(mISupplicantP2pIface.removeGroup(groupName));
1043            } catch (RemoteException e) {
1044                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1045                supplicantServiceDiedHandler();
1046            }
1047            return result.isSuccess();
1048        }
1049    }
1050
1051
1052    /**
1053     * Gets the capability of the group which the device is a
1054     * member of.
1055     *
1056     * @param peerAddress MAC address of the peer.
1057     *
1058     * @return combination of |GroupCapabilityMask| values.
1059     */
1060    public int getGroupCapability(String peerAddress) {
1061        synchronized (mLock) {
1062            if (!checkSupplicantP2pIfaceAndLogFailure("getGroupCapability")) {
1063                return RESULT_NOT_VALID;
1064            }
1065
1066            if (peerAddress == null) {
1067                Log.e(TAG, "Cannot parse peer mac address.");
1068                return RESULT_NOT_VALID;
1069            }
1070            byte[] macAddress = null;
1071            try {
1072                macAddress = NativeUtil.macAddressToByteArray(peerAddress);
1073            } catch (Exception e) {
1074                Log.e(TAG, "Could not parse group address.", e);
1075                return RESULT_NOT_VALID;
1076            }
1077
1078            SupplicantResult<Integer> capability = new SupplicantResult(
1079                    "getGroupCapability(" + peerAddress + ")");
1080            try {
1081                mISupplicantP2pIface.getGroupCapability(
1082                        macAddress, (SupplicantStatus status, int cap) -> {
1083                            capability.setResult(status, cap);
1084                        });
1085            } catch (RemoteException e) {
1086                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1087                supplicantServiceDiedHandler();
1088            }
1089
1090            if (!capability.isSuccess()) {
1091                return RESULT_NOT_VALID;
1092            }
1093
1094            return capability.getResult();
1095        }
1096    }
1097
1098
1099    /**
1100     * Configure Extended Listen Timing.
1101     *
1102     * If enabled, listen state must be entered every |intervalInMillis| for at
1103     * least |periodInMillis|. Both values have acceptable range of 1-65535
1104     * (with interval obviously having to be larger than or equal to duration).
1105     * If the P2P module is not idle at the time the Extended Listen Timing
1106     * timeout occurs, the Listen State operation must be skipped.
1107     *
1108     * @param enable Enables or disables listening.
1109     * @param periodInMillis Period in milliseconds.
1110     * @param intervalInMillis Interval in milliseconds.
1111     *
1112     * @return true, if operation was successful.
1113     */
1114    public boolean configureExtListen(boolean enable, int periodInMillis, int intervalInMillis) {
1115        if (enable && intervalInMillis < periodInMillis) {
1116            return false;
1117        }
1118        synchronized (mLock) {
1119            if (!checkSupplicantP2pIfaceAndLogFailure("configureExtListen")) return false;
1120
1121            // If listening is disabled, wpa supplicant expects zeroes.
1122            if (!enable) {
1123                periodInMillis = 0;
1124                intervalInMillis = 0;
1125            }
1126
1127            // Verify that the integers are not negative. Leave actual parameter validation to
1128            // supplicant.
1129            if (periodInMillis < 0 || intervalInMillis < 0) {
1130                Log.e(TAG, "Invalid parameters supplied to configureExtListen: " +
1131                        periodInMillis + ", " + intervalInMillis);
1132                return false;
1133            }
1134
1135            SupplicantResult<Void> result = new SupplicantResult(
1136                    "configureExtListen(" + periodInMillis + ", " + intervalInMillis + ")");
1137            try {
1138                result.setResult(
1139                        mISupplicantP2pIface.configureExtListen(periodInMillis, intervalInMillis));
1140            } catch (RemoteException e) {
1141                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1142                supplicantServiceDiedHandler();
1143            }
1144
1145            return result.isSuccess();
1146        }
1147    }
1148
1149
1150    /**
1151     * Set P2P Listen channel.
1152     *
1153     * When specifying a social channel on the 2.4 GHz band (1/6/11) there is no
1154     * need to specify the operating class since it defaults to 81. When
1155     * specifying a social channel on the 60 GHz band (2), specify the 60 GHz
1156     * operating class (180).
1157     *
1158     * @param channel Wifi channel. eg, 1, 6, 11.
1159     * @param operatingClass Operating Class indicates the channel set of the AP
1160     *        indicated by this BSSID
1161     *
1162     * @return true, if operation was successful.
1163     */
1164    public boolean setListenChannel(int channel, int operatingClass) {
1165        synchronized (mLock) {
1166            if (!checkSupplicantP2pIfaceAndLogFailure("setListenChannel")) return false;
1167            // Verify that the integers are not negative. Leave actual parameter validation to
1168            // supplicant.
1169            if (channel < 0 || operatingClass < 0) {
1170                Log.e(TAG, "Invalid values supplied to setListenChannel: " +
1171                        channel + ", " + operatingClass);
1172                return false;
1173            }
1174
1175            SupplicantResult<Void> result = new SupplicantResult(
1176                    "setListenChannel(" + channel + ", " + operatingClass + ")");
1177            try {
1178                result.setResult(mISupplicantP2pIface.setListenChannel(channel, operatingClass));
1179            } catch (RemoteException e) {
1180                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1181                supplicantServiceDiedHandler();
1182            }
1183            return result.isSuccess();
1184        }
1185    }
1186
1187
1188    /**
1189     * This command can be used to add a upnp/bonjour service.
1190     *
1191     * @param servInfo List of service queries.
1192     *
1193     * @return true, if operation was successful.
1194     */
1195    public boolean serviceAdd(WifiP2pServiceInfo servInfo) {
1196        synchronized (mLock) {
1197            if (!checkSupplicantP2pIfaceAndLogFailure("serviceAdd")) return false;
1198
1199            if (servInfo == null) {
1200                Log.e(TAG, "Null service info passed.");
1201                return false;
1202            }
1203
1204            for (String s : servInfo.getSupplicantQueryList()) {
1205                if (s == null) {
1206                    Log.e(TAG, "Invalid service description (null).");
1207                    return false;
1208                }
1209
1210                String[] data = s.split(" ");
1211                if (data.length < 3) {
1212                    Log.e(TAG, "Service specification invalid: " + s);
1213                    return false;
1214                }
1215
1216                SupplicantResult<Void> result = null;
1217                try {
1218                    if ("upnp".equals(data[0])) {
1219                        int version = 0;
1220                        try {
1221                            version = Integer.parseInt(data[1]);
1222                        } catch (NumberFormatException e) {
1223                            Log.e(TAG, "UPnP Service specification invalid: " + s, e);
1224                            return false;
1225                        }
1226
1227                        result = new SupplicantResult(
1228                                "addUpnpService(" + data[1] + ", " + data[2] + ")");
1229                        result.setResult(mISupplicantP2pIface.addUpnpService(version, data[2]));
1230                    } else if ("bonjour".equals(data[0])) {
1231                        if (data[1] != null && data[2] != null) {
1232                            ArrayList<Byte> request = null;
1233                            ArrayList<Byte> response = null;
1234                            try {
1235                                request = NativeUtil.byteArrayToArrayList(
1236                                        NativeUtil.hexStringToByteArray(data[1]));
1237                                response = NativeUtil.byteArrayToArrayList(
1238                                        NativeUtil.hexStringToByteArray(data[2]));
1239                            } catch (Exception e) {
1240                                Log.e(TAG, "Invalid bonjour service description.");
1241                                return false;
1242                            }
1243                            result = new SupplicantResult(
1244                                    "addBonjourService(" + data[1] + ", " + data[2] + ")");
1245                            result.setResult(
1246                                    mISupplicantP2pIface.addBonjourService(request, response));
1247                        }
1248                    } else {
1249                        return false;
1250                    }
1251                } catch (RemoteException e) {
1252                    Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1253                    supplicantServiceDiedHandler();
1254                }
1255
1256                if (result == null || !result.isSuccess()) return false;
1257            }
1258
1259            return true;
1260        }
1261    }
1262
1263
1264    /**
1265     * This command can be used to remove a upnp/bonjour service.
1266     *
1267     * @param servInfo List of service queries.
1268     *
1269     * @return true, if operation was successful.
1270     */
1271    public boolean serviceRemove(WifiP2pServiceInfo servInfo) {
1272        synchronized (mLock) {
1273            if (!checkSupplicantP2pIfaceAndLogFailure("serviceRemove")) return false;
1274
1275            if (servInfo == null) {
1276                Log.e(TAG, "Null service info passed.");
1277                return false;
1278            }
1279
1280            for (String s : servInfo.getSupplicantQueryList()) {
1281                if (s == null) {
1282                    Log.e(TAG, "Invalid service description (null).");
1283                    return false;
1284                }
1285
1286                String[] data = s.split(" ");
1287                if (data.length < 3) {
1288                    Log.e(TAG, "Service specification invalid: " + s);
1289                    return false;
1290                }
1291
1292                SupplicantResult<Void> result = null;
1293                try {
1294                    if ("upnp".equals(data[0])) {
1295                        int version = 0;
1296                        try {
1297                            version = Integer.parseInt(data[1]);
1298                        } catch (NumberFormatException e) {
1299                            Log.e(TAG, "UPnP Service specification invalid: " + s, e);
1300                            return false;
1301                        }
1302                        result = new SupplicantResult(
1303                                "removeUpnpService(" + data[1] + ", " + data[2] + ")");
1304                        result.setResult(mISupplicantP2pIface.removeUpnpService(version, data[2]));
1305                    } else if ("bonjour".equals(data[0])) {
1306                        if (data[1] != null) {
1307                            ArrayList<Byte> request = null;
1308                            try {
1309                                request = NativeUtil.byteArrayToArrayList(
1310                                    NativeUtil.hexStringToByteArray(data[1]));
1311                            } catch (Exception e) {
1312                                Log.e(TAG, "Invalid bonjour service description.");
1313                                return false;
1314                            }
1315                            result = new SupplicantResult("removeBonjourService(" + data[1] + ")");
1316                            result.setResult(mISupplicantP2pIface.removeBonjourService(request));
1317                        }
1318                    } else {
1319                        Log.e(TAG, "Unknown / unsupported P2P service requested: " + data[0]);
1320                        return false;
1321                    }
1322                } catch (RemoteException e) {
1323                    Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1324                    supplicantServiceDiedHandler();
1325                }
1326
1327                if (result == null || !result.isSuccess()) return false;
1328            }
1329
1330            return true;
1331        }
1332    }
1333
1334
1335    /**
1336     * Schedule a P2P service discovery request. The parameters for this command
1337     * are the device address of the peer device (or 00:00:00:00:00:00 for
1338     * wildcard query that is sent to every discovered P2P peer that supports
1339     * service discovery) and P2P Service Query TLV(s) as hexdump.
1340     *
1341     * @param peerAddress MAC address of the device to discover.
1342     * @param query Hex dump of the query data.
1343     * @return identifier Identifier for the request. Can be used to cancel the
1344     *         request.
1345     */
1346    public String requestServiceDiscovery(String peerAddress, String query) {
1347        synchronized (mLock) {
1348            if (!checkSupplicantP2pIfaceAndLogFailure("requestServiceDiscovery")) return null;
1349
1350            if (peerAddress == null) {
1351                Log.e(TAG, "Cannot parse peer mac address.");
1352                return null;
1353            }
1354            byte[] macAddress = null;
1355            try {
1356                macAddress = NativeUtil.macAddressToByteArray(peerAddress);
1357            } catch (Exception e) {
1358                Log.e(TAG, "Could not process peer MAC address.", e);
1359                return null;
1360            }
1361
1362            if (query == null) {
1363                Log.e(TAG, "Cannot parse service discovery query: " + query);
1364                return null;
1365            }
1366            ArrayList<Byte> binQuery = null;
1367            try {
1368                binQuery = NativeUtil.byteArrayToArrayList(NativeUtil.hexStringToByteArray(query));
1369            } catch (Exception e) {
1370                Log.e(TAG, "Could not parse service query.", e);
1371                return null;
1372            }
1373
1374            SupplicantResult<Long> result = new SupplicantResult(
1375                    "requestServiceDiscovery(" + peerAddress + ", " + query + ")");
1376            try {
1377                mISupplicantP2pIface.requestServiceDiscovery(
1378                        macAddress, binQuery,
1379                        (SupplicantStatus status, long identifier) -> {
1380                            result.setResult(status, new Long(identifier));
1381                        });
1382            } catch (RemoteException e) {
1383                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1384                supplicantServiceDiedHandler();
1385            }
1386
1387            Long value = result.getResult();
1388            if (value == null) return null;
1389            return value.toString();
1390        }
1391    }
1392
1393
1394    /**
1395     * Cancel a previous service discovery request.
1396     *
1397     * @param identifier Identifier for the request to cancel.
1398     * @return true, if operation was successful.
1399     */
1400    public boolean cancelServiceDiscovery(String identifier) {
1401        synchronized (mLock) {
1402            if (!checkSupplicantP2pIfaceAndLogFailure("cancelServiceDiscovery")) return false;
1403            if (identifier == null) {
1404                Log.e(TAG, "cancelServiceDiscovery requires a valid tag.");
1405                return false;
1406            }
1407
1408            int id = 0;
1409            try {
1410                id = Integer.parseInt(identifier);
1411            } catch (NumberFormatException e) {
1412                Log.e(TAG, "Service discovery identifier invalid: " + identifier, e);
1413                return false;
1414            }
1415
1416            SupplicantResult<Void> result = new SupplicantResult(
1417                    "cancelServiceDiscovery(" + identifier + ")");
1418            try {
1419                result.setResult(mISupplicantP2pIface.cancelServiceDiscovery(id));
1420            } catch (RemoteException e) {
1421                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1422                supplicantServiceDiedHandler();
1423            }
1424
1425            return result.isSuccess();
1426        }
1427    }
1428
1429
1430    /**
1431     * Send driver command to set Miracast mode.
1432     *
1433     * @param mode Mode of Miracast.
1434     * @return true, if operation was successful.
1435     */
1436    public boolean setMiracastMode(int mode) {
1437        synchronized (mLock) {
1438            if (!checkSupplicantP2pIfaceAndLogFailure("setMiracastMode")) return false;
1439            byte targetMode = ISupplicantP2pIface.MiracastMode.DISABLED;
1440
1441            switch (mode) {
1442                case WifiP2pManager.MIRACAST_SOURCE:
1443                    targetMode = ISupplicantP2pIface.MiracastMode.SOURCE;
1444                    break;
1445
1446                case WifiP2pManager.MIRACAST_SINK:
1447                    targetMode = ISupplicantP2pIface.MiracastMode.SINK;
1448                    break;
1449            }
1450
1451            SupplicantResult<Void> result = new SupplicantResult(
1452                    "setMiracastMode(" + mode + ")");
1453            try {
1454                result.setResult(mISupplicantP2pIface.setMiracastMode(targetMode));
1455            } catch (RemoteException e) {
1456                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1457                supplicantServiceDiedHandler();
1458            }
1459
1460            return result.isSuccess();
1461        }
1462    }
1463
1464
1465    /**
1466     * Initiate WPS Push Button setup.
1467     * The PBC operation requires that a button is also pressed at the
1468     * AP/Registrar at about the same time (2 minute window).
1469     *
1470     * @param groupIfName Group interface name to use.
1471     * @param bssid BSSID of the AP. Use zero'ed bssid to indicate wildcard.
1472     * @return true, if operation was successful.
1473     */
1474    public boolean startWpsPbc(String groupIfName, String bssid) {
1475        if (TextUtils.isEmpty(groupIfName) || TextUtils.isEmpty(bssid)) return false;
1476        synchronized (mLock) {
1477            if (!checkSupplicantP2pIfaceAndLogFailure("startWpsPbc")) return false;
1478            if (groupIfName == null) {
1479                Log.e(TAG, "Group name required when requesting WPS PBC.");
1480                return false;
1481            }
1482
1483            // Null values should be fine, since bssid can be empty.
1484            byte[] macAddress = null;
1485            if (bssid != null) {
1486                try {
1487                    macAddress = NativeUtil.macAddressToByteArray(bssid);
1488                } catch (Exception e) {
1489                    Log.e(TAG, "Could not parse BSSID.", e);
1490                    return false;
1491                }
1492            }
1493
1494            SupplicantResult<Void> result = new SupplicantResult(
1495                    "startWpsPbc(" + groupIfName + ", " + bssid + ")");
1496            try {
1497                result.setResult(mISupplicantP2pIface.startWpsPbc(groupIfName, macAddress));
1498            } catch (RemoteException e) {
1499                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1500                supplicantServiceDiedHandler();
1501            }
1502
1503            return result.isSuccess();
1504        }
1505    }
1506
1507
1508    /**
1509     * Initiate WPS Pin Keypad setup.
1510     *
1511     * @param groupIfName Group interface name to use.
1512     * @param pin 8 digit pin to be used.
1513     * @return true, if operation was successful.
1514     */
1515    public boolean startWpsPinKeypad(String groupIfName, String pin) {
1516        if (TextUtils.isEmpty(groupIfName) || TextUtils.isEmpty(pin)) return false;
1517        synchronized (mLock) {
1518            if (!checkSupplicantP2pIfaceAndLogFailure("startWpsPinKeypad")) return false;
1519            if (groupIfName == null) {
1520                Log.e(TAG, "Group name required when requesting WPS KEYPAD.");
1521                return false;
1522            }
1523            if (pin == null) {
1524                Log.e(TAG, "PIN required when requesting WPS KEYPAD.");
1525                return false;
1526            }
1527
1528            SupplicantResult<Void> result = new SupplicantResult(
1529                    "startWpsPinKeypad(" + groupIfName + ", " + pin + ")");
1530            try {
1531                result.setResult(mISupplicantP2pIface.startWpsPinKeypad(groupIfName, pin));
1532            } catch (RemoteException e) {
1533                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1534                supplicantServiceDiedHandler();
1535            }
1536
1537            return result.isSuccess();
1538        }
1539    }
1540
1541
1542    /**
1543     * Initiate WPS Pin Display setup.
1544     *
1545     * @param groupIfName Group interface name to use.
1546     * @param bssid BSSID of the AP. Use zero'ed bssid to indicate wildcard.
1547     * @return generated pin if operation was successful, null otherwise.
1548     */
1549    public String startWpsPinDisplay(String groupIfName, String bssid) {
1550        if (TextUtils.isEmpty(groupIfName) || TextUtils.isEmpty(bssid)) return null;
1551        synchronized (mLock) {
1552            if (!checkSupplicantP2pIfaceAndLogFailure("startWpsPinDisplay")) return null;
1553            if (groupIfName == null) {
1554                Log.e(TAG, "Group name required when requesting WPS KEYPAD.");
1555                return null;
1556            }
1557
1558            // Null values should be fine, since bssid can be empty.
1559            byte[] macAddress = null;
1560            if (bssid != null) {
1561                try {
1562                    macAddress = NativeUtil.macAddressToByteArray(bssid);
1563                } catch (Exception e) {
1564                    Log.e(TAG, "Could not parse BSSID.", e);
1565                    return null;
1566                }
1567            }
1568
1569            SupplicantResult<String> result = new SupplicantResult(
1570                    "startWpsPinDisplay(" + groupIfName + ", " + bssid + ")");
1571            try {
1572                mISupplicantP2pIface.startWpsPinDisplay(
1573                        groupIfName, macAddress,
1574                        (SupplicantStatus status, String generatedPin) -> {
1575                            result.setResult(status, generatedPin);
1576                        });
1577            } catch (RemoteException e) {
1578                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1579                supplicantServiceDiedHandler();
1580            }
1581
1582            return result.getResult();
1583        }
1584    }
1585
1586
1587    /**
1588     * Cancel any ongoing WPS operations.
1589     *
1590     * @param groupIfName Group interface name to use.
1591     * @return true, if operation was successful.
1592     */
1593    public boolean cancelWps(String groupIfName) {
1594        synchronized (mLock) {
1595            if (!checkSupplicantP2pIfaceAndLogFailure("cancelWps")) return false;
1596            if (groupIfName == null) {
1597                Log.e(TAG, "Group name required when requesting WPS KEYPAD.");
1598                return false;
1599            }
1600
1601            SupplicantResult<Void> result = new SupplicantResult(
1602                    "cancelWps(" + groupIfName + ")");
1603            try {
1604                result.setResult(mISupplicantP2pIface.cancelWps(groupIfName));
1605            } catch (RemoteException e) {
1606                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1607                supplicantServiceDiedHandler();
1608            }
1609
1610            return result.isSuccess();
1611        }
1612    }
1613
1614
1615    /**
1616     * Enable/Disable Wifi Display.
1617     *
1618     * @param enable true to enable, false to disable.
1619     * @return true, if operation was successful.
1620     */
1621    public boolean enableWfd(boolean enable) {
1622        synchronized (mLock) {
1623            if (!checkSupplicantP2pIfaceAndLogFailure("enableWfd")) return false;
1624
1625            SupplicantResult<Void> result = new SupplicantResult(
1626                    "enableWfd(" + enable + ")");
1627            try {
1628                result.setResult(mISupplicantP2pIface.enableWfd(enable));
1629            } catch (RemoteException e) {
1630                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1631                supplicantServiceDiedHandler();
1632            }
1633
1634            return result.isSuccess();
1635        }
1636    }
1637
1638
1639    /**
1640     * Set Wifi Display device info.
1641     *
1642     * @param info WFD device info as described in section 5.1.2 of WFD technical
1643     *        specification v1.0.0.
1644     * @return true, if operation was successful.
1645     */
1646    public boolean setWfdDeviceInfo(String info) {
1647        synchronized (mLock) {
1648            if (!checkSupplicantP2pIfaceAndLogFailure("setWfdDeviceInfo")) return false;
1649
1650            if (info == null) {
1651                Log.e(TAG, "Cannot parse null WFD info string.");
1652                return false;
1653            }
1654            byte[] wfdInfo = null;
1655            try {
1656                wfdInfo = NativeUtil.hexStringToByteArray(info);
1657            } catch (Exception e) {
1658                Log.e(TAG, "Could not parse WFD Device Info string.");
1659                return false;
1660            }
1661
1662            SupplicantResult<Void> result = new SupplicantResult(
1663                    "setWfdDeviceInfo(" + info + ")");
1664            try {
1665                result.setResult(mISupplicantP2pIface.setWfdDeviceInfo(wfdInfo));
1666            } catch (RemoteException e) {
1667                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1668                supplicantServiceDiedHandler();
1669            }
1670
1671            return result.isSuccess();
1672        }
1673    }
1674
1675    /**
1676     * Remove network with provided id.
1677     *
1678     * @param networkId Id of the network to lookup.
1679     * @return true, if operation was successful.
1680     */
1681    public boolean removeNetwork(int networkId) {
1682        synchronized (mLock) {
1683            if (!checkSupplicantP2pIfaceAndLogFailure("removeNetwork")) return false;
1684
1685            SupplicantResult<Void> result = new SupplicantResult(
1686                    "removeNetwork(" + networkId + ")");
1687            try {
1688                result.setResult(mISupplicantP2pIface.removeNetwork(networkId));
1689            } catch (RemoteException e) {
1690                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1691                supplicantServiceDiedHandler();
1692            }
1693
1694            return result.isSuccess();
1695        }
1696    }
1697
1698    /**
1699     * List the networks saved in wpa_supplicant.
1700     *
1701     * @return List of network ids.
1702     */
1703    private List<Integer> listNetworks() {
1704        synchronized (mLock) {
1705            if (!checkSupplicantP2pIfaceAndLogFailure("listNetworks")) return null;
1706            SupplicantResult<ArrayList> result = new SupplicantResult("listNetworks()");
1707            try {
1708                mISupplicantP2pIface.listNetworks(
1709                        (SupplicantStatus status, ArrayList<Integer> networkIds) -> {
1710                            result.setResult(status, networkIds);
1711                        });
1712            } catch (RemoteException e) {
1713                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1714                supplicantServiceDiedHandler();
1715            }
1716            return result.getResult();
1717        }
1718    }
1719
1720    /**
1721     * Get the supplicant P2p network object for the specified network ID.
1722     *
1723     * @param networkId Id of the network to lookup.
1724     * @return ISupplicantP2pNetwork instance on success, null on failure.
1725     */
1726    private ISupplicantP2pNetwork getNetwork(int networkId) {
1727        synchronized (mLock) {
1728            if (!checkSupplicantP2pIfaceAndLogFailure("getNetwork")) return null;
1729            SupplicantResult<ISupplicantNetwork> result =
1730                    new SupplicantResult("getNetwork(" + networkId + ")");
1731            try {
1732                mISupplicantP2pIface.getNetwork(
1733                        networkId,
1734                        (SupplicantStatus status, ISupplicantNetwork network) -> {
1735                            result.setResult(status, network);
1736                        });
1737            } catch (RemoteException e) {
1738                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1739                supplicantServiceDiedHandler();
1740            }
1741            if (result.getResult() == null) {
1742                Log.e(TAG, "getNetwork got null network");
1743                return null;
1744            }
1745            return getP2pNetworkMockable(result.getResult());
1746        }
1747    }
1748
1749    /**
1750     * Populate list of available networks or update existing list.
1751     *
1752     * @return true, if list has been modified.
1753     */
1754    public boolean loadGroups(WifiP2pGroupList groups) {
1755        synchronized (mLock) {
1756            if (!checkSupplicantP2pIfaceAndLogFailure("loadGroups")) return false;
1757            List<Integer> networkIds = listNetworks();
1758            if (networkIds == null || networkIds.isEmpty()) {
1759                return false;
1760            }
1761            for (Integer networkId : networkIds) {
1762                ISupplicantP2pNetwork network = getNetwork(networkId);
1763                if (network == null) {
1764                    Log.e(TAG, "Failed to retrieve network object for " + networkId);
1765                    continue;
1766                }
1767                SupplicantResult<Boolean> resultIsCurrent =
1768                        new SupplicantResult("isCurrent(" + networkId + ")");
1769                try {
1770                    network.isCurrent(
1771                            (SupplicantStatus status, boolean isCurrent) -> {
1772                                resultIsCurrent.setResult(status, isCurrent);
1773                            });
1774                } catch (RemoteException e) {
1775                    Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1776                    supplicantServiceDiedHandler();
1777                }
1778                if (!resultIsCurrent.isSuccess() || !resultIsCurrent.getResult()) {
1779                    Log.i(TAG, "Skipping non current network");
1780                    continue;
1781                }
1782
1783                WifiP2pGroup group = new WifiP2pGroup();
1784                group.setNetworkId(networkId);
1785
1786                // Now get the ssid, bssid and other flags for this network.
1787                SupplicantResult<ArrayList> resultSsid =
1788                        new SupplicantResult("getSsid(" + networkId + ")");
1789                try {
1790                    network.getSsid(
1791                            (SupplicantStatus status, ArrayList<Byte> ssid) -> {
1792                                resultSsid.setResult(status, ssid);
1793                            });
1794                } catch (RemoteException e) {
1795                    Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1796                    supplicantServiceDiedHandler();
1797                }
1798                if (resultSsid.isSuccess() && resultSsid.getResult() != null
1799                        && !resultSsid.getResult().isEmpty()) {
1800                    group.setNetworkName(NativeUtil.encodeSsid(resultSsid.getResult()));
1801                }
1802
1803                SupplicantResult<byte[]> resultBssid =
1804                        new SupplicantResult("getBssid(" + networkId + ")");
1805                try {
1806                    network.getBssid(
1807                            (SupplicantStatus status, byte[] bssid) -> {
1808                                resultBssid.setResult(status, bssid);
1809                            });
1810                } catch (RemoteException e) {
1811                    Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1812                    supplicantServiceDiedHandler();
1813                }
1814                if (resultBssid.isSuccess() && !ArrayUtils.isEmpty(resultBssid.getResult())) {
1815                    WifiP2pDevice device = new WifiP2pDevice();
1816                    device.deviceAddress =
1817                            NativeUtil.macAddressFromByteArray(resultBssid.getResult());
1818                    group.setOwner(device);
1819                }
1820
1821                SupplicantResult<Boolean> resultIsGo =
1822                        new SupplicantResult("isGo(" + networkId + ")");
1823                try {
1824                    network.isGo(
1825                            (SupplicantStatus status, boolean isGo) -> {
1826                                resultIsGo.setResult(status, isGo);
1827                            });
1828                } catch (RemoteException e) {
1829                    Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1830                    supplicantServiceDiedHandler();
1831                }
1832                if (resultIsGo.isSuccess()) {
1833                    group.setIsGroupOwner(resultIsGo.getResult());
1834                }
1835                groups.add(group);
1836            }
1837        }
1838        return true;
1839    }
1840
1841    /**
1842     * Set WPS device name.
1843     *
1844     * @param name String to be set.
1845     * @return true if request is sent successfully, false otherwise.
1846     */
1847    public boolean setWpsDeviceName(String name) {
1848        if (name == null) {
1849            return false;
1850        }
1851        synchronized (mLock) {
1852            if (!checkSupplicantP2pIfaceAndLogFailure("setWpsDeviceName")) return false;
1853            SupplicantResult<Void> result = new SupplicantResult(
1854                    "setWpsDeviceName(" + name + ")");
1855            try {
1856                result.setResult(mISupplicantP2pIface.setWpsDeviceName(name));
1857            } catch (RemoteException e) {
1858                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1859                supplicantServiceDiedHandler();
1860            }
1861            return result.isSuccess();
1862        }
1863    }
1864
1865    /**
1866     * Set WPS device type.
1867     *
1868     * @param typeStr Type specified as a string. Used format: <categ>-<OUI>-<subcateg>
1869     * @return true if request is sent successfully, false otherwise.
1870     */
1871    public boolean setWpsDeviceType(String typeStr) {
1872        Matcher match = WPS_DEVICE_TYPE_PATTERN.matcher(typeStr);
1873        if (!match.find() || match.groupCount() != 3) {
1874            Log.e(TAG, "Malformed WPS device type " + typeStr);
1875            return false;
1876        }
1877        short categ = Short.parseShort(match.group(1));
1878        byte[] oui = NativeUtil.hexStringToByteArray(match.group(2));
1879        short subCateg = Short.parseShort(match.group(3));
1880
1881        byte[] bytes = new byte[8];
1882        ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
1883        byteBuffer.putShort(categ);
1884        byteBuffer.put(oui);
1885        byteBuffer.putShort(subCateg);
1886        synchronized (mLock) {
1887            if (!checkSupplicantP2pIfaceAndLogFailure("setWpsDeviceType")) return false;
1888            SupplicantResult<Void> result = new SupplicantResult(
1889                    "setWpsDeviceType(" + typeStr + ")");
1890            try {
1891                result.setResult(mISupplicantP2pIface.setWpsDeviceType(bytes));
1892            } catch (RemoteException e) {
1893                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1894                supplicantServiceDiedHandler();
1895            }
1896            return result.isSuccess();
1897        }
1898    }
1899
1900    /**
1901     * Get NFC handover request message.
1902     *
1903     * @return select message if created successfully, null otherwise.
1904     */
1905    public String getNfcHandoverRequest() {
1906        synchronized (mLock) {
1907            if (!checkSupplicantP2pIfaceAndLogFailure("getNfcHandoverRequest")) return null;
1908            SupplicantResult<ArrayList> result = new SupplicantResult(
1909                    "getNfcHandoverRequest()");
1910            try {
1911                mISupplicantP2pIface.createNfcHandoverRequestMessage(
1912                        (SupplicantStatus status, ArrayList<Byte> message) -> {
1913                            result.setResult(status, message);
1914                        });
1915            } catch (RemoteException e) {
1916                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1917                supplicantServiceDiedHandler();
1918            }
1919            if (!result.isSuccess()) {
1920                return null;
1921
1922            }
1923            return NativeUtil.hexStringFromByteArray(
1924                    NativeUtil.byteArrayFromArrayList(result.getResult()));
1925        }
1926    }
1927
1928    /**
1929     * Get NFC handover select message.
1930     *
1931     * @return select message if created successfully, null otherwise.
1932     */
1933    public String getNfcHandoverSelect() {
1934        synchronized (mLock) {
1935            if (!checkSupplicantP2pIfaceAndLogFailure("getNfcHandoverSelect")) return null;
1936            SupplicantResult<ArrayList> result = new SupplicantResult(
1937                    "getNfcHandoverSelect()");
1938            try {
1939                mISupplicantP2pIface.createNfcHandoverSelectMessage(
1940                        (SupplicantStatus status, ArrayList<Byte> message) -> {
1941                            result.setResult(status, message);
1942                        });
1943            } catch (RemoteException e) {
1944                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1945                supplicantServiceDiedHandler();
1946            }
1947            if (!result.isSuccess()) {
1948                return null;
1949
1950            }
1951            return NativeUtil.hexStringFromByteArray(
1952                    NativeUtil.byteArrayFromArrayList(result.getResult()));
1953        }
1954    }
1955
1956    /**
1957     * Report NFC handover select message.
1958     *
1959     * @return true if reported successfully, false otherwise.
1960     */
1961    public boolean initiatorReportNfcHandover(String selectMessage) {
1962        if (selectMessage == null) return false;
1963        synchronized (mLock) {
1964            if (!checkSupplicantP2pIfaceAndLogFailure("initiatorReportNfcHandover")) return false;
1965            SupplicantResult<Void> result = new SupplicantResult(
1966                    "initiatorReportNfcHandover(" + selectMessage + ")");
1967            try {
1968                result.setResult(mISupplicantP2pIface.reportNfcHandoverInitiation(
1969                        NativeUtil.byteArrayToArrayList(NativeUtil.hexStringToByteArray(
1970                            selectMessage))));
1971            } catch (RemoteException e) {
1972                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1973                supplicantServiceDiedHandler();
1974            }
1975            return result.isSuccess();
1976        }
1977    }
1978
1979    /**
1980     * Report NFC handover request message.
1981     *
1982     * @return true if reported successfully, false otherwise.
1983     */
1984    public boolean responderReportNfcHandover(String requestMessage) {
1985        if (requestMessage == null) return false;
1986        synchronized (mLock) {
1987            if (!checkSupplicantP2pIfaceAndLogFailure("responderReportNfcHandover")) return false;
1988            SupplicantResult<Void> result = new SupplicantResult(
1989                    "responderReportNfcHandover(" + requestMessage + ")");
1990            try {
1991                result.setResult(mISupplicantP2pIface.reportNfcHandoverResponse(
1992                        NativeUtil.byteArrayToArrayList(NativeUtil.hexStringToByteArray(
1993                            requestMessage))));
1994            } catch (RemoteException e) {
1995                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
1996                supplicantServiceDiedHandler();
1997            }
1998            return result.isSuccess();
1999        }
2000    }
2001    /** Container class allowing propagation of status and/or value
2002     * from callbacks.
2003     *
2004     * Primary purpose is to allow callback lambdas to provide results
2005     * to parent methods.
2006     */
2007    private static class SupplicantResult<E> {
2008        private String mMethodName;
2009        private SupplicantStatus mStatus;
2010        private E mValue;
2011
2012        public SupplicantResult(String methodName) {
2013            mMethodName = methodName;
2014            mStatus = null;
2015            mValue = null;
2016            logd("entering " + mMethodName);
2017        }
2018
2019        public void setResult(SupplicantStatus status, E value) {
2020            logCompletion(mMethodName, status);
2021            logd("leaving " + mMethodName + " with result = " + value);
2022            mStatus = status;
2023            mValue = value;
2024        }
2025
2026        public void setResult(SupplicantStatus status) {
2027            logCompletion(mMethodName, status);
2028            logd("leaving " + mMethodName);
2029            mStatus = status;
2030        }
2031
2032        public boolean isSuccess() {
2033            return (mStatus != null && mStatus.code == SupplicantStatusCode.SUCCESS);
2034        }
2035
2036        public E getResult() {
2037            return (isSuccess() ? mValue : null);
2038        }
2039    }
2040}
2041