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