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