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