• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 
17 package com.google.snippet.wifi.direct;
18 
19 import android.Manifest;
20 import android.app.Instrumentation;
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.pm.PackageManager;
26 import android.net.NetworkInfo;
27 import android.net.wifi.p2p.WifiP2pConfig;
28 import android.net.wifi.p2p.WifiP2pDevice;
29 import android.net.wifi.p2p.WifiP2pDeviceList;
30 import android.net.wifi.p2p.WifiP2pGroup;
31 import android.net.wifi.p2p.WifiP2pGroupList;
32 import android.net.wifi.p2p.WifiP2pInfo;
33 import android.net.wifi.p2p.WifiP2pManager;
34 import android.net.wifi.p2p.nsd.WifiP2pDnsSdServiceInfo;
35 import android.net.wifi.p2p.nsd.WifiP2pDnsSdServiceRequest;
36 import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
37 import android.net.wifi.p2p.nsd.WifiP2pServiceRequest;
38 import android.net.wifi.p2p.nsd.WifiP2pUpnpServiceInfo;
39 import android.net.wifi.p2p.nsd.WifiP2pUpnpServiceRequest;
40 import android.os.Bundle;
41 import android.widget.Button;
42 
43 import androidx.annotation.NonNull;
44 import androidx.test.core.app.ApplicationProvider;
45 import androidx.test.platform.app.InstrumentationRegistry;
46 import androidx.test.uiautomator.By;
47 import androidx.test.uiautomator.UiDevice;
48 import androidx.test.uiautomator.UiObject2;
49 import androidx.test.uiautomator.Until;
50 
51 import com.google.android.mobly.snippet.Snippet;
52 import com.google.android.mobly.snippet.event.EventCache;
53 import com.google.android.mobly.snippet.event.SnippetEvent;
54 import com.google.android.mobly.snippet.rpc.AsyncRpc;
55 import com.google.android.mobly.snippet.rpc.Rpc;
56 import com.google.android.mobly.snippet.rpc.RpcDefault;
57 import com.google.android.mobly.snippet.rpc.RpcOptional;
58 import com.google.android.mobly.snippet.util.Log;
59 
60 import org.json.JSONArray;
61 import org.json.JSONObject;
62 
63 import java.util.ArrayList;
64 import java.util.HashMap;
65 import java.util.Iterator;
66 import java.util.List;
67 import java.util.Map;
68 import java.util.Objects;
69 import java.util.UUID;
70 import java.util.concurrent.LinkedBlockingDeque;
71 import java.util.concurrent.TimeUnit;
72 import java.util.concurrent.TimeoutException;
73 import java.util.regex.Pattern;
74 
75 
76 /** Snippet class for WifiP2pManager. */
77 public class WifiP2pManagerSnippet implements Snippet {
78     private static final String TAG = "WifiP2pManagerSnippet";
79     private static final int TIMEOUT_SHORT_MS = 10000;
80     private static final int UI_ACTION_SHORT_TIMEOUT_MS = 5000;
81     private static final int UI_ACTION_LONG_TIMEOUT_MS = 30000;
82     private static final String EVENT_KEY_CALLBACK_NAME = "callbackName";
83     private static final String EVENT_KEY_REASON = "reason";
84     private static final String EVENT_KEY_P2P_DEVICE = "p2pDevice";
85     private static final String EVENT_KEY_P2P_INFO = "p2pInfo";
86     private static final String EVENT_KEY_P2P_GROUP = "p2pGroup";
87     private static final String EVENT_KEY_PEER_LIST = "peerList";
88     private static final String EVENT_KEY_SERVICE_LIST = "serviceList";
89     private static final String EVENT_KEY_INSTANCE_NAME = "instanceName";
90     private static final String EVENT_KEY_REGISTRATION_TYPE = "registrationType";
91     private static final String EVENT_KEY_SOURCE_DEVICE = "sourceDevice";
92     private static final String EVENT_KEY_FULL_DOMAIN_NAME = "fullDomainName";
93     private static final String EVENT_KEY_TXT_RECORD_MAP = "txtRecordMap";
94     private static final String EVENT_KEY_TIMESTAMP_MS = "timestampMs";
95     private static final String ACTION_LISTENER_ON_SUCCESS = "onSuccess";
96     public static final String ACTION_LISTENER_ON_FAILURE = "onFailure";
97 
98     private final Context mContext;
99     private final IntentFilter mIntentFilter;
100     private final WifiP2pManager mP2pManager;
101 
102     private Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
103     private UiDevice mUiDevice = UiDevice.getInstance(mInstrumentation);
104 
105     private final HashMap<Integer, WifiP2pManager.Channel> mChannels = new HashMap<>();
106     private WifiP2pStateChangedReceiver mStateChangedReceiver = null;
107 
108     private int mServiceRequestCnt = 0;
109     private int mChannelCnt = -1;
110 
111     private final Map<Integer, WifiP2pServiceRequest> mServiceRequests;
112 
113 
114     private static class WifiP2pManagerException extends Exception {
WifiP2pManagerException(String message)115         WifiP2pManagerException(String message) {
116             super(message);
117         }
118     }
119 
WifiP2pManagerSnippet()120     public WifiP2pManagerSnippet() {
121         Log.d("Elevating permission require to enable support for privileged operation in "
122                 + "Android Q+");
123         mInstrumentation.getUiAutomation().adoptShellPermissionIdentity();
124 
125         mContext = ApplicationProvider.getApplicationContext();
126 
127         checkPermissions(mContext, Manifest.permission.ACCESS_WIFI_STATE,
128                 Manifest.permission.CHANGE_WIFI_STATE, Manifest.permission.ACCESS_FINE_LOCATION,
129                 Manifest.permission.NEARBY_WIFI_DEVICES);
130 
131         mP2pManager = mContext.getSystemService(WifiP2pManager.class);
132 
133         mIntentFilter = new IntentFilter();
134         mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
135         mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
136         mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
137         mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
138 
139         mServiceRequests = new HashMap<Integer, WifiP2pServiceRequest>();
140     }
141 
142     /**
143      * Initialize the application with the Wi-Fi P2P framework and registers necessary receivers.
144      *
145      * @param callbackId The callback ID assigned by Mobly
146      * @return The ID of the initialized channel. Use this ID to specify which channel to
147      *         operate on in future operations.
148      * @throws WifiP2pManagerException If the Wi-Fi P2P has already been initialized.
149      */
150     @AsyncRpc(description = "Register the application with the Wi-Fi framework.")
wifiP2pInitialize(String callbackId)151     public int wifiP2pInitialize(String callbackId) throws WifiP2pManagerException {
152         if (mStateChangedReceiver != null) {
153             throw new WifiP2pManagerException("WifiP2pManager has already been initialized. "
154                     + "Please call `p2pClose()` close the current connection.");
155         }
156         if (mChannelCnt != -1) {
157             throw new WifiP2pManagerException("Please call `p2pClose()` to close the current "
158                     + "connection before initializing a new one.");
159         }
160         checkP2pManager();
161         // Initialize the first channel. This channel will be used by default if an Wi-Fi P2P RPC
162         // method is called without a channel ID.
163         mStateChangedReceiver = new WifiP2pStateChangedReceiver(callbackId);
164         mContext.registerReceiver(mStateChangedReceiver, mIntentFilter,
165                 Context.RECEIVER_NOT_EXPORTED);
166         WifiP2pManager.Channel channel =
167                 mP2pManager.initialize(mContext, mContext.getMainLooper(), null);
168         mChannelCnt += 1;
169         mChannels.put(mChannelCnt, channel);
170         return mChannelCnt;
171     }
172 
173     /**
174      * Initialize an extra Wi-Fi P2P channel. This is for multi-channel tests.
175      *
176      * @return The id of the new channel.
177      */
178     @Rpc(description = "Initialize an extra Wi-Fi P2P channel. This is needed when you need to "
179             + "test with multiple channels.")
wifiP2pInitExtraChannel()180     public int wifiP2pInitExtraChannel() {
181         if (mChannelCnt == -1) {
182             throw new IllegalStateException("Main channel has not been initialized. Please call "
183                     + "`wifiP2pInitialize` first.");
184         }
185         WifiP2pManager.Channel channel =
186                 mP2pManager.initialize(mContext, mContext.getMainLooper(), null);
187         mChannelCnt += 1;
188         mChannels.put(mChannelCnt, channel);
189         return mChannelCnt;
190     }
191 
192     /**
193      * Request the device information in the form of WifiP2pDevice.
194      *
195      * @param callbackId The callback ID assigned by Mobly.
196      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
197      * @throws WifiP2pManagerException If got invalid channel ID.
198      */
199     @AsyncRpc(description = "Request the device information in the form of WifiP2pDevice.")
wifiP2pRequestDeviceInfo(String callbackId, @RpcDefault(value = "0") Integer channelId)200     public void wifiP2pRequestDeviceInfo(String callbackId,
201             @RpcDefault(value = "0") Integer channelId)
202             throws WifiP2pManagerException {
203         WifiP2pManager.Channel channel = getChannel(channelId);
204         mP2pManager.requestDeviceInfo(channel, new DeviceInfoListener(callbackId));
205     }
206 
207     /**
208      * Request the connection information in the form of WifiP2pDevice.
209      *
210      * @param callbackId The callback ID assigned by Mobly.
211      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
212      */
213     @AsyncRpc(description = "Request the connection information in the form of WifiP2pDevice.")
wifiP2pRequestConnectionInfo(String callbackId, @RpcDefault(value = "0") Integer channelId)214     public void wifiP2pRequestConnectionInfo(String callbackId,
215             @RpcDefault(value = "0") Integer channelId)
216             throws WifiP2pManagerException {
217         WifiP2pManager.Channel channel = getChannel(channelId);
218         mP2pManager.requestConnectionInfo(channel,
219             new WifiP2pConnectionInfoListener(callbackId));
220     }
221 
222     /**
223      * Initiate peer discovery. A discovery process involves scanning for available Wi-Fi peers for
224      * the purpose of establishing a connection.
225      *
226      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
227      * @throws Throwable If the P2P operation failed or timed out, or got invalid channel ID.
228      */
229     @Rpc(description = "Initiate peer discovery. A discovery process involves scanning for "
230             + "available Wi-Fi peers for the purpose of establishing a connection.")
wifiP2pDiscoverPeers(@pcDefaultvalue = "0") Integer channelId)231     public void wifiP2pDiscoverPeers(@RpcDefault(value = "0") Integer channelId) throws Throwable {
232         WifiP2pManager.Channel channel = getChannel(channelId);
233         String callbackId = UUID.randomUUID().toString();
234         mP2pManager.discoverPeers(channel, new ActionListener(callbackId));
235         verifyActionListenerSucceed(callbackId);
236     }
237 
238     /**
239      * Request peers that are discovered for wifi p2p.
240      *
241      * @param callbackId The callback ID assigned by Mobly.
242      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
243      * @throws Throwable If the P2P operation failed or timed out, or got invalid channel ID.
244      */
245     @AsyncRpc(description = "Request peers that are discovered for wifi p2p.")
wifiP2pRequestPeers(String callbackId, @RpcDefault(value = "0") Integer channelId)246     public void wifiP2pRequestPeers(String callbackId, @RpcDefault(value = "0") Integer channelId)
247             throws Throwable {
248         WifiP2pManager.Channel channel = getChannel(channelId);
249         mP2pManager.requestPeers(channel, new PeerListListener(callbackId));
250     }
251 
252     /**
253      * Cancel any ongoing p2p group negotiation.
254      *
255      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
256      * @return The event posted by the callback methods of {@link ActionListener}.
257      * @throws Throwable If the P2P operation failed or timed out, or got invalid channel ID.
258      */
259     @Rpc(description = "Cancel any ongoing p2p group negotiation.")
wifiP2pCancelConnect(@pcDefaultvalue = "0") Integer channelId)260     public Bundle wifiP2pCancelConnect(@RpcDefault(value = "0") Integer channelId)
261             throws Throwable {
262         WifiP2pManager.Channel channel = getChannel(channelId);
263         String callbackId = UUID.randomUUID().toString();
264         mP2pManager.cancelConnect(channel, new ActionListener((callbackId)));
265         return waitActionListenerResult(callbackId);
266     }
267 
268     /**
269      * Stop current ongoing peer discovery.
270      *
271      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
272      * @return The event posted by the callback methods of {@link ActionListener}.
273      * @throws Throwable If the P2P operation failed or timed out, or got invalid channel ID.
274      */
275     @Rpc(description = "Stop current ongoing peer discovery.")
wifiP2pStopPeerDiscovery(@pcDefaultvalue = "0") Integer channelId)276     public Bundle wifiP2pStopPeerDiscovery(@RpcDefault(value = "0") Integer channelId)
277             throws Throwable {
278         WifiP2pManager.Channel channel = getChannel(channelId);
279         String callbackId = UUID.randomUUID().toString();
280         mP2pManager.stopPeerDiscovery(channel, new ActionListener(callbackId));
281         return waitActionListenerResult(callbackId);
282     }
283 
284     /**
285      * Create a p2p group with the current device as the group owner.
286      *
287      * @param  wifiP2pConfig The configuration for the p2p group.
288      * @param  channelId The ID of the channel for Wi-Fi P2P to operate on.
289      * @throws Throwable If the P2P operation failed or timed out, or got invalid channel ID.
290      */
291     @Rpc(description = "Create a p2p group with the current device as the group owner.")
wifiP2pCreateGroup( @pcOptional JSONObject wifiP2pConfig, @RpcDefault(value = "0") Integer channelId )292     public void wifiP2pCreateGroup(
293             @RpcOptional JSONObject wifiP2pConfig,
294             @RpcDefault(value = "0") Integer channelId
295     ) throws Throwable {
296         WifiP2pManager.Channel channel = getChannel(channelId);
297         String callbackId = UUID.randomUUID().toString();
298         ActionListener actionListener = new ActionListener(callbackId);
299         WifiP2pConfig config = null;
300         if (wifiP2pConfig != null) {
301             config = JsonDeserializer.jsonToWifiP2pConfig(wifiP2pConfig);
302         }
303         Log.d("Creating wifi p2p group with config: " + String.valueOf(config));
304         mP2pManager.createGroup(channel, config, actionListener);
305         verifyActionListenerSucceed(callbackId);
306     }
307 
308     /**
309      * Start a p2p connection to a device with the specified configuration.
310      *
311      * @param wifiP2pConfig The configuration for the p2p connection.
312      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
313      * @throws Throwable If the P2P operation failed or timed out, or got invalid channel ID.
314      */
315     @Rpc(description = "Start a p2p connection to a device with the specified configuration.")
wifiP2pConnect( JSONObject wifiP2pConfig, @RpcDefault(value = "0") Integer channelId)316     public void wifiP2pConnect(
317             JSONObject wifiP2pConfig,
318             @RpcDefault(value = "0") Integer channelId) throws Throwable {
319         WifiP2pManager.Channel channel = getChannel(channelId);
320         String callbackId = UUID.randomUUID().toString();
321         WifiP2pConfig config = JsonDeserializer.jsonToWifiP2pConfig(wifiP2pConfig);
322         Log.d("Connecting p2p group with config: " + String.valueOf(config));
323         mP2pManager.connect(channel, config, new ActionListener(callbackId));
324         verifyActionListenerSucceed(callbackId);
325     }
326 
327     /**
328      * Accept p2p connection invitation through clicking on UI.
329      *
330      * @param deviceName The name of the device to connect.
331      * @throws WifiP2pManagerException If failed to accept invitation through UI.
332      */
333     @Rpc(description = "Accept p2p connection invitation through clicking on UI.")
wifiP2pAcceptInvitation(String deviceName)334     public void wifiP2pAcceptInvitation(String deviceName) throws WifiP2pManagerException {
335         if (!mUiDevice.wait(Until.hasObject(By.text("Invitation to connect")),
336                 UI_ACTION_LONG_TIMEOUT_MS)) {
337             throw new WifiP2pManagerException(
338                     "Expected connect invitation did not occur within timeout.");
339         }
340         if (!mUiDevice.wait(Until.hasObject(By.text(deviceName)), UI_ACTION_SHORT_TIMEOUT_MS)) {
341             throw new WifiP2pManagerException(
342                     "The connect invitation is not triggered by expected peer device.");
343         }
344         Pattern pattern = Pattern.compile("(ACCEPT|OK|Accept)");
345         if (!mUiDevice.wait(Until.hasObject(By.text(pattern).clazz(Button.class)),
346                 UI_ACTION_SHORT_TIMEOUT_MS)) {
347             throw new WifiP2pManagerException("Accept button did not occur within timeout.");
348         }
349         UiObject2 acceptButton = mUiDevice.findObject(By.text(pattern).clazz(Button.class));
350         if (acceptButton == null) {
351             throw new WifiP2pManagerException(
352                     "There's no accept button for the connect invitation.");
353         }
354         acceptButton.click();
355     }
356 
357     /**
358      * Get p2p connect PIN code after calling {@link #wifiP2pConnect(JSONObject,Integer)} with
359      * WPS PIN.
360      *
361      * @param deviceName The name of the device to connect.
362      * @return The generated PIN as a String.
363      * @throws Throwable If failed to get PIN code.
364      */
365     @Rpc(description = "Get p2p connect PIN code after calling wifiP2pConnect with WPS PIN.")
wifiP2pGetPinCode(String deviceName)366     public String wifiP2pGetPinCode(String deviceName) throws Throwable {
367         // Wait for the 'Invitation sent' dialog to appear
368         if (!mUiDevice.wait(Until.hasObject(By.text("Invitation sent")),
369                 UI_ACTION_LONG_TIMEOUT_MS)) {
370             throw new WifiP2pManagerException(
371                     "Invitation sent dialog did not appear within timeout.");
372         }
373         if (!mUiDevice.wait(Until.hasObject(By.text(deviceName)), UI_ACTION_SHORT_TIMEOUT_MS)) {
374             throw new WifiP2pManagerException(
375                     "The connect invitation is not triggered by expected peer device.");
376         }
377         // Find the UI lement with text='PIN:'
378         UiObject2 pinLabel = mUiDevice.findObject(By.text("PIN:"));
379         if (pinLabel == null) {
380             throw new WifiP2pManagerException("PIN label not found.");
381         }
382         // Get the sibling UI element that contains the PIN code. Use regex pattern "\d+" as PIN
383         // code must be composed entirely of numbers.
384         Pattern pattern = Pattern.compile("\\d+");
385         UiObject2 pinValue = pinLabel.getParent().findObject(By.text(pattern));
386         if (pinValue == null) {
387             throw new WifiP2pManagerException("Failed to find Pin code UI element.");
388         }
389         String pinCode = pinValue.getText();
390         Log.d("Retrieved PIN code: " + pinCode);
391         // Click 'OK' to close the PIN code alert
392         UiObject2 okButton = mUiDevice.findObject(By.text("OK").clazz(Button.class));
393         if (okButton == null) {
394             throw new WifiP2pManagerException(
395                     "OK button not found in the p2p connection invitation pop-up window.");
396         }
397         okButton.click();
398         Log.d("Closed the p2p connect invitation pop-up window.");
399         return pinCode;
400     }
401 
402     /**
403      * Get p2p connect PIN code after calling {@link #wifiP2pConnect(JSONObject,Integer)} with
404      * WPS PIN.
405      *
406      * @param deviceName The name of the device to connect.
407      * @return The generated PIN as a String.
408      * @throws Throwable If failed to get PIN code.
409      */
410     @Rpc(description = "Get p2p connect PIN code after calling wifiP2pConnect with WPS PIN.")
wifiP2pGetKeypadPinCode(String deviceName)411     public String wifiP2pGetKeypadPinCode(String deviceName) throws Throwable {
412         // Wait for the 'Invitation sent' dialog to appear
413         if (!mUiDevice.wait(Until.hasObject(By.text("Invitation to connect")),
414                 UI_ACTION_LONG_TIMEOUT_MS)) {
415             throw new WifiP2pManagerException(
416                     "Invitation sent dialog did not appear within timeout.");
417         }
418         if (!mUiDevice.wait(Until.hasObject(By.text(deviceName)), UI_ACTION_SHORT_TIMEOUT_MS)) {
419             throw new WifiP2pManagerException(
420                     "The connect invitation is not triggered by expected peer device.");
421         }
422         // Find the UI lement with text='PIN:'
423         UiObject2 pinLabel = mUiDevice.findObject(By.text("PIN:"));
424         if (pinLabel == null) {
425             throw new WifiP2pManagerException("PIN label not found.");
426         }
427         Log.d("pinLabel = " + pinLabel);
428         // Get the sibling UI element that contains the PIN code. Use regex pattern "\d+" as PIN
429         // code must be composed entirely of numbers.
430         Pattern pattern = Pattern.compile("\\d+");
431         UiObject2 pinValue = pinLabel.getParent().findObject(By.text(pattern));
432         if (pinValue == null) {
433             throw new WifiP2pManagerException("Failed to find Pin code UI element.");
434         }
435         String pinCode = pinValue.getText();
436         Log.d("Retrieved PIN code: " + pinCode);
437         // Click 'OK' to close the PIN code alert
438         UiObject2 okButton = mUiDevice.findObject(By.text("Accept").clazz(Button.class));
439         if (okButton == null) {
440             throw new WifiP2pManagerException(
441                     "OK button not found in the p2p connection invitation pop-up window.");
442         }
443         okButton.click();
444         Log.d("Closed the p2p connect invitation pop-up window.");
445         return pinCode;
446     }
447 
448     /**
449      * Enters the given PIN code to accept a P2P connection invitation.
450      *
451      * @param pinCode    The PIN to enter.
452      * @param deviceName The name of the device that initiated the connection.
453      * @throws WifiP2pManagerException If the PIN entry field is not found within timeout.
454      */
455     @Rpc(description = "Enter the PIN code to accept a P2P connection invitation.")
wifiP2pEnterPin(String pinCode, String deviceName)456     public void wifiP2pEnterPin(String pinCode, String deviceName) throws WifiP2pManagerException {
457         // Wait for the 'Invitation to connect' dialog to appear
458         if (!mUiDevice.wait(Until.hasObject(By.textContains("Invitation to connect")),
459                 UI_ACTION_LONG_TIMEOUT_MS)) {
460             throw new WifiP2pManagerException(
461                     "Invitation to connect dialog did not appear within timeout.");
462         }
463         if (!mUiDevice.wait(Until.hasObject(By.text(deviceName)), UI_ACTION_SHORT_TIMEOUT_MS)) {
464             throw new WifiP2pManagerException(
465                     "The connect invitation is not triggered by expected peer device.");
466         }
467         // Find the PIN entry field
468         UiObject2 pinEntryField = mUiDevice.findObject(By.focused(true));
469         if (pinEntryField == null) {
470             throw new WifiP2pManagerException("PIN entry field not found.");
471         }
472         // Enter the PIN code
473         pinEntryField.setText(pinCode);
474         Log.d("Entered PIN code: " + pinCode);
475         // Accept the invitation
476         Pattern acceptPattern = Pattern.compile("(ACCEPT|OK|Accept)", Pattern.CASE_INSENSITIVE);
477         UiObject2 acceptButton = mUiDevice.findObject(By.clazz(Button.class).text(acceptPattern));
478         if (acceptButton == null) {
479             throw new WifiP2pManagerException(
480                     "Failed to find accept button for p2p connect invitation.");
481         }
482         acceptButton.click();
483         Log.d("Accepted the connection.");
484     }
485 
486     /**
487      * Remove the current p2p group.
488      *
489      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
490      * @return The event posted by the callback methods of {@link ActionListener}.
491      * @throws Throwable If the P2P operation failed or timed out, or got invalid channel ID.
492      */
493     @Rpc(description = "Remove the current p2p group.")
wifiP2pRemoveGroup(@pcDefaultvalue = "0") Integer channelId)494     public Bundle wifiP2pRemoveGroup(@RpcDefault(value = "0") Integer channelId) throws Throwable {
495         WifiP2pManager.Channel channel = getChannel(channelId);
496         String callbackId = UUID.randomUUID().toString();
497         mP2pManager.removeGroup(channel, new ActionListener(callbackId));
498         return waitActionListenerResult(callbackId);
499     }
500 
501     /**
502      * Request the number of persistent p2p group.
503      *
504      * @param callbackId The callback ID assigned by Mobly.
505      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
506      * @throws Throwable If this failed to request persistent group info, or got invalid channel ID.
507      */
508     @AsyncRpc(description = "Request the number of persistent p2p group")
wifiP2pRequestPersistentGroupInfo( String callbackId, @RpcDefault(value = "0") Integer channelId)509     public void wifiP2pRequestPersistentGroupInfo(
510             String callbackId,
511             @RpcDefault(value = "0") Integer channelId) throws Throwable {
512         WifiP2pManager.Channel channel = getChannel(channelId);
513         mP2pManager.requestPersistentGroupInfo(channel,
514                 new PersistentGroupInfoListener(callbackId));
515     }
516 
517     /**
518      * Delete the persistent p2p group with the given network ID.
519      *
520      * @param networkId The network ID of the persistent p2p group to delete.
521      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
522      * @return The event posted by the callback methods of {@link ActionListener}.
523      * @throws Throwable If this failed to delete persistent group, or got invalid channel ID.
524      */
525     @Rpc(description = "Delete the persistent p2p group with the given network ID.")
wifiP2pDeletePersistentGroup(int networkId, @RpcDefault(value = "0") Integer channelId)526     public Bundle wifiP2pDeletePersistentGroup(int networkId,
527             @RpcDefault(value = "0") Integer channelId) throws Throwable {
528         WifiP2pManager.Channel channel = getChannel(channelId);
529         String callbackId = UUID.randomUUID().toString();
530         mP2pManager.deletePersistentGroup(channel, networkId, new ActionListener(callbackId));
531         return waitActionListenerResult(callbackId);
532     }
533 
534     /**
535      * Register Upnp service as a local Wi-Fi p2p service for service discovery.
536      * @param uuid The UUID to be passed to
537      *     {@link WifiP2pUpnpServiceInfo#newInstance(String, String, List)}.
538      * @param device The device to be passed to
539      *     {@link WifiP2pUpnpServiceInfo#newInstance(String, String, List)}.
540      * @param services The services to be passed to
541      *     {@link WifiP2pUpnpServiceInfo#newInstance(String, String, List)}.
542      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
543      * @throws Throwable If failed to add local service or got invalid channel ID.
544      */
545     @Rpc(description = "Register Upnp service as a local Wi-Fi p2p service for service discovery.")
wifiP2pAddUpnpLocalService( String uuid, String device, JSONArray services, @RpcDefault(value = "0" ) Integer channelId)546     public void wifiP2pAddUpnpLocalService(
547             String uuid,
548             String device,
549             JSONArray services,
550             @RpcDefault(value = "0"
551             ) Integer channelId) throws Throwable {
552         WifiP2pManager.Channel channel = getChannel(channelId);
553         List<String> serviceList = new ArrayList<String>();
554         for (int i = 0; i < services.length(); i++) {
555             serviceList.add(services.getString(i));
556             Log.d("wifiP2pAddUpnpLocalService, services: " + services.getString(i));
557         }
558         WifiP2pServiceInfo serviceInfo =
559                 WifiP2pUpnpServiceInfo.newInstance(uuid, device, serviceList);
560 
561         String callbackId = UUID.randomUUID().toString();
562         mP2pManager.addLocalService(channel, serviceInfo, new ActionListener(callbackId));
563         verifyActionListenerSucceed(callbackId);
564     }
565 
566     /**
567      * Register Bonjour service as a local Wi-Fi p2p service for service discovery.
568      *
569      * @param instanceName The instance name to be passed to
570      *     {@link WifiP2pDnsSdServiceInfo#newInstance(String, String, Map)}.
571      * @param serviceType The serviceType to be passed to
572      *     {@link WifiP2pDnsSdServiceInfo#newInstance(String, String, Map)}.
573      * @param txtMap The TXT record to be passed to
574      *     {@link WifiP2pDnsSdServiceInfo#newInstance(String, String, Map)}.
575      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
576      * @throws Throwable If failed to add local service or got invalid channel ID.
577      */
578     @Rpc(description = "Register Bonjour service as a local Wi-Fi p2p service for service"
579             + " discovery.")
wifiP2pAddBonjourLocalService(String instanceName, String serviceType, @RpcOptional JSONObject txtMap, @RpcDefault(value = "0") Integer channelId )580     public void wifiP2pAddBonjourLocalService(String instanceName,
581             String serviceType,
582             @RpcOptional JSONObject txtMap,
583             @RpcDefault(value = "0") Integer channelId
584     ) throws Throwable {
585         WifiP2pManager.Channel channel = getChannel(channelId);
586         Map<String, String> map = null;
587         if (txtMap != null) {
588             map = new HashMap<String, String>();
589             Iterator<String> keyIterator = txtMap.keys();
590             while (keyIterator.hasNext()) {
591                 String key = keyIterator.next();
592                 map.put(key, txtMap.getString(key));
593             }
594         }
595         WifiP2pDnsSdServiceInfo serviceInfo =
596                 WifiP2pDnsSdServiceInfo.newInstance(instanceName, serviceType, map);
597 
598         String callbackId = UUID.randomUUID().toString();
599         mP2pManager.addLocalService(channel, serviceInfo, new ActionListener(callbackId));
600         verifyActionListenerSucceed(callbackId);
601     }
602 
603     /**
604      * Clear all registered local services of service discovery.
605      *
606      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
607      * @throws Throwable If failed to clear local services or got invalid channel ID.
608      */
609     @Rpc(description = "Clear all registered local services of service discovery.")
wifiP2pClearLocalServices(@pcDefaultvalue = "0") Integer channelId)610     public void wifiP2pClearLocalServices(@RpcDefault(value = "0") Integer channelId)
611             throws Throwable {
612         WifiP2pManager.Channel channel = getChannel(channelId);
613         String callbackId = UUID.randomUUID().toString();
614         mP2pManager.clearLocalServices(channel, new ActionListener(callbackId));
615         waitActionListenerResult(callbackId);
616     }
617 
618     /**
619      * Add a service discovery request.
620      *
621      * @param protocolType The protocol type of the service discovery request.
622      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
623      * @return The ID of the service request, which is used when calling
624      * @throws Throwable If add service request action timed out or got invalid channel ID.
625      */
626     @Rpc(description = "Add a service discovery request.")
wifiP2pAddServiceRequest( int protocolType, @RpcDefault(value = "0") Integer channelId )627     public Integer wifiP2pAddServiceRequest(
628             int protocolType, @RpcDefault(value = "0") Integer channelId
629     ) throws Throwable {
630         WifiP2pManager.Channel channel = getChannel(channelId);
631 
632         WifiP2pServiceRequest request = WifiP2pServiceRequest.newInstance(protocolType);
633         mServiceRequestCnt += 1;
634         mServiceRequests.put(mServiceRequestCnt, request);
635 
636         String callbackId = UUID.randomUUID().toString();
637         mP2pManager.addServiceRequest(channel, request, new ActionListener(callbackId));
638         verifyActionListenerSucceed(callbackId);
639         return mServiceRequestCnt;
640     }
641 
642     /**
643      * Add a service Upnp discovery request.
644      *
645      * @param serviceType The service type to be passed to {@link WifiP2pUpnpServiceRequest}.
646      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
647      * @return The ID of the service request, which is used when calling
648      *     {@link #wifiP2pRemoveServiceRequest(int, Integer)}.
649      * @throws Throwable If add service request action timed out or got invalid channel ID.
650      */
651     @Rpc(description = "Add a service Upnp discovery request.")
wifiP2pAddUpnpServiceRequest( @pcOptional String serviceType, @RpcDefault(value = "0") Integer channelId )652     public Integer wifiP2pAddUpnpServiceRequest(
653             @RpcOptional String serviceType,
654             @RpcDefault(value = "0") Integer channelId
655     ) throws Throwable {
656         WifiP2pManager.Channel channel = getChannel(channelId);
657         WifiP2pUpnpServiceRequest request;
658         if (serviceType == null) {
659             request = WifiP2pUpnpServiceRequest.newInstance();
660         } else {
661             request = WifiP2pUpnpServiceRequest.newInstance(serviceType);
662         }
663         mServiceRequestCnt += 1;
664         mServiceRequests.put(mServiceRequestCnt, request);
665 
666         String callbackId = UUID.randomUUID().toString();
667         mP2pManager.addServiceRequest(channel, request, new ActionListener(callbackId));
668         verifyActionListenerSucceed(callbackId);
669         return mServiceRequestCnt;
670     }
671 
672     /**
673      * Add a service Bonjour discovery request.
674      *
675      * @param instanceName The instance name to be passed to
676      *     {@link WifiP2pDnsSdServiceRequest#newInstance(String, String)}.
677      * @param serviceType The service type to be passed to
678      *     {@link WifiP2pDnsSdServiceRequest#newInstance(String, String)}.
679      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
680      * @return The ID of the service request, which is used when calling
681      *     {@link #wifiP2pRemoveServiceRequest(int, Integer)}.
682      *  @throws Throwable If add service request action timed out or got invalid channel ID.
683      */
684     @Rpc(description = "Add a service Bonjour discovery request.")
wifiP2pAddBonjourServiceRequest( @pcOptional String instanceName, @RpcOptional String serviceType, @RpcDefault(value = "0") Integer channelId )685     public Integer wifiP2pAddBonjourServiceRequest(
686             @RpcOptional String instanceName,
687             @RpcOptional String serviceType,
688             @RpcDefault(value = "0") Integer channelId
689     ) throws Throwable {
690         WifiP2pManager.Channel channel = getChannel(channelId);
691         WifiP2pDnsSdServiceRequest request;
692         if (instanceName != null) {
693             request = WifiP2pDnsSdServiceRequest.newInstance(instanceName, serviceType);
694         } else if (serviceType == null) {
695             request = WifiP2pDnsSdServiceRequest.newInstance();
696         } else {
697             request = WifiP2pDnsSdServiceRequest.newInstance(serviceType);
698         }
699         mServiceRequestCnt += 1;
700         mServiceRequests.put(mServiceRequestCnt, request);
701 
702         String callbackId = UUID.randomUUID().toString();
703         mP2pManager.addServiceRequest(channel, request, new ActionListener(callbackId));
704         verifyActionListenerSucceed(callbackId);
705         return mServiceRequestCnt;
706     }
707 
708     /**
709      * Remove a service discovery request.
710      *
711      * @param index The index of the service request to remove.
712      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
713      * @throws Throwable If remove service request action timed out or got invalid channel ID.
714      */
715     @Rpc(description = "Remove a service discovery request.")
wifiP2pRemoveServiceRequest(int index, @RpcDefault(value = "0") Integer channelId)716     public void wifiP2pRemoveServiceRequest(int index, @RpcDefault(value = "0") Integer channelId)
717             throws Throwable {
718         WifiP2pManager.Channel channel = getChannel(channelId);
719         String callbackId = UUID.randomUUID().toString();
720         WifiP2pServiceRequest serviceRequest = mServiceRequests.remove(index);
721         if (serviceRequest == null) {
722             throw new WifiP2pManagerException("Service request not found. Please use the request ID"
723                     + " returned by `wifiP2pAddServiceRequest`.");
724         }
725         mP2pManager.removeServiceRequest(channel, serviceRequest,
726                 new ActionListener(callbackId));
727         verifyActionListenerSucceed(callbackId);
728     }
729 
730     /**
731      * Clear all registered service discovery requests.
732      *
733      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
734      * @throws Throwable If clear service requests action timed out or got invalid channel ID.
735      */
736     @Rpc(description = "Clear all registered service discovery requests.")
wifiP2pClearServiceRequests(@pcDefaultvalue = "0") Integer channelId)737     public void wifiP2pClearServiceRequests(@RpcDefault(value = "0") Integer channelId)
738             throws Throwable {
739         WifiP2pManager.Channel channel = getChannel(channelId);
740         String callbackId = UUID.randomUUID().toString();
741         mP2pManager.clearServiceRequests(channel, new ActionListener(callbackId));
742         waitActionListenerResult(callbackId);
743     }
744 
745     /**
746      * Set a callback to be invoked on receiving Upnp service discovery response.
747      *
748      * @param callbackId The callback ID assigned by Mobly.
749      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
750      * @throws WifiP2pManagerException If the channel is not created.
751      */
752     @AsyncRpc(description = "Set a callback to be invoked on receiving Upnp service discovery "
753             + " response.")
wifiP2pSetUpnpResponseListener(String callbackId, @RpcDefault(value = "0") Integer channelId)754     public void wifiP2pSetUpnpResponseListener(String callbackId,
755             @RpcDefault(value = "0") Integer channelId)
756             throws WifiP2pManagerException {
757         WifiP2pManager.Channel channel = getChannel(channelId);
758         mP2pManager.setUpnpServiceResponseListener(channel,
759                 new UpnpServiceResponseListener(callbackId));
760     }
761 
762     /**
763      * Unset the Upnp service response callback set by `wifiP2pSetUpnpResponseListener`.
764      *
765      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
766      * @throws WifiP2pManagerException If the channel is not created.
767      */
768     @Rpc(description = "Unset the Upnp service response callback set by "
769             + "`wifiP2pSetUpnpResponseListener`.")
wifiP2pUnsetUpnpResponseListener(@pcDefaultvalue = "0") Integer channelId)770     public void wifiP2pUnsetUpnpResponseListener(@RpcDefault(value = "0") Integer channelId)
771             throws WifiP2pManagerException {
772         WifiP2pManager.Channel channel = getChannel(channelId);
773         mP2pManager.setUpnpServiceResponseListener(channel, null);
774     }
775 
776     /**
777      * Set a callback to be invoked on receiving Bonjour service discovery response.
778      *
779      * @param callbackId The callback ID assigned by Mobly.
780      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
781      * @throws WifiP2pManagerException If the channel is not created.
782      */
783     @AsyncRpc(description = "Set a callback to be invoked on receiving Bonjour service discovery"
784             + " response.")
wifiP2pSetDnsSdResponseListeners(String callbackId, @RpcDefault(value = "0") Integer channelId)785     public void wifiP2pSetDnsSdResponseListeners(String callbackId,
786             @RpcDefault(value = "0") Integer channelId)
787             throws WifiP2pManagerException {
788         WifiP2pManager.Channel channel = getChannel(channelId);
789         mP2pManager.setDnsSdResponseListeners(channel, new DnsSdServiceResponseListener(callbackId),
790                 new DnsSdTxtRecordListener(callbackId));
791     }
792 
793     /**
794      * Unset the Bonjour service response callback set by `wifiP2pSetDnsSdResponseListeners`.
795      *
796      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
797      * @throws WifiP2pManagerException If the channel is not created.
798      */
799     @Rpc(description = "Unset the Bonjour service response callback set by "
800             + "`wifiP2pSetDnsSdResponseListeners`.")
wifiP2pUnsetDnsSdResponseListeners(@pcDefaultvalue = "0") Integer channelId)801     public void wifiP2pUnsetDnsSdResponseListeners(@RpcDefault(value = "0") Integer channelId)
802             throws WifiP2pManagerException {
803         WifiP2pManager.Channel channel = getChannel(channelId);
804         mP2pManager.setDnsSdResponseListeners(channel, null, null);
805     }
806 
807     /**
808      * Initiate service discovery.
809      *
810      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
811      * @throws Throwable If the P2P operation failed or timed out, or got invalid channel ID.
812      */
813     @Rpc(description = "Initiate service discovery.")
wifiP2pDiscoverServices( @pcDefaultvalue = "0") Integer channelId )814     public void wifiP2pDiscoverServices(
815             @RpcDefault(value = "0") Integer channelId
816     ) throws Throwable {
817         WifiP2pManager.Channel channel = getChannel(channelId);
818         String callbackId = UUID.randomUUID().toString();
819         mP2pManager.discoverServices(channel, new ActionListener(callbackId));
820         verifyActionListenerSucceed(callbackId);
821     }
822 
823     /**
824      * Close the current P2P connection and indicate to the P2P service that connections created by
825      * the app can be removed.
826      */
827     @Rpc(description = "Close all P2P connections and indicate to the P2P service that"
828             + " connections created by the app can be removed.")
p2pClose()829     public void p2pClose() {
830         for (Map.Entry<Integer, WifiP2pManager.Channel> entry : mChannels.entrySet()) {
831             Integer channelId = entry.getKey();
832             WifiP2pManager.Channel channel = entry.getValue();
833             Log.d("Cleaning p2p resources associated with channelId=" + channelId);
834             if (channel != null) {
835                 try {
836                     wifiP2pClearServiceRequests(channelId);
837                 } catch (Throwable e) {
838                     Log.e("Failed to clear service requests on channelId=" + channelId
839                             + ", error message: " + e.getMessage());
840                 }
841                 mP2pManager.setDnsSdResponseListeners(channel, null, null);
842                 mP2pManager.setUpnpServiceResponseListener(channel, null);
843                 try {
844                     wifiP2pClearLocalServices(channelId);
845                 } catch (Throwable e) {
846                     Log.e("Failed to clear local services on channelId=" + channelId
847                             + ", error message: " + e.getMessage());
848                 }
849                 channel.close();
850             }
851         }
852         mChannels.clear();
853         mChannelCnt = -1;
854         if (mStateChangedReceiver != null) {
855             mContext.unregisterReceiver(mStateChangedReceiver);
856             mStateChangedReceiver = null;
857         }
858     }
859 
860     @Override
shutdown()861     public void shutdown() {
862         p2pClose();
863     }
864 
865     private class WifiP2pStateChangedReceiver extends BroadcastReceiver {
866         private String mCallbackId;
867 
WifiP2pStateChangedReceiver(@onNull String callbackId)868         private WifiP2pStateChangedReceiver(@NonNull String callbackId) {
869             this.mCallbackId = callbackId;
870         }
871 
872         @Override
onReceive(Context mContext, Intent intent)873         public void onReceive(Context mContext, Intent intent) {
874             String action = intent.getAction();
875             SnippetEvent event = new SnippetEvent(mCallbackId, action);
876             String logPrefix = TAG + ": WifiP2pStateChangedReceiver: onReceive: Got intent: action="
877                     + action + ", ";
878             switch (action) {
879                 case WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION:
880                     int wifiP2pState = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, 0);
881                     Log.d(logPrefix + "wifiP2pState=" + wifiP2pState);
882                     event.getData().putInt(WifiP2pManager.EXTRA_WIFI_STATE, wifiP2pState);
883                     break;
884                 case WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION:
885                     WifiP2pDeviceList peerList = (WifiP2pDeviceList) intent.getParcelableExtra(
886                             WifiP2pManager.EXTRA_P2P_DEVICE_LIST);
887                     Log.d(logPrefix + "p2pPeerList=" + BundleUtils.fromWifiP2pDeviceList(peerList));
888                     event.getData().putParcelableArrayList(
889                             EVENT_KEY_PEER_LIST, BundleUtils.fromWifiP2pDeviceList(peerList));
890                     break;
891                 case WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION:
892                     NetworkInfo networkInfo = intent.getParcelableExtra(
893                             WifiP2pManager.EXTRA_NETWORK_INFO);
894                     WifiP2pInfo p2pInfo = (WifiP2pInfo) intent.getParcelableExtra(
895                             WifiP2pManager.EXTRA_WIFI_P2P_INFO);
896                     WifiP2pGroup p2pGroup = (WifiP2pGroup) intent.getParcelableExtra(
897                             WifiP2pManager.EXTRA_WIFI_P2P_GROUP);
898                     Log.d(logPrefix + "networkInfo=" + String.valueOf(networkInfo) + ", p2pInfo="
899                             + String.valueOf(p2pInfo) + ", p2pGroup=" + String.valueOf(p2pGroup));
900                     if (networkInfo != null) {
901                         event.getData().putBoolean("isConnected", networkInfo.isConnected());
902                     } else {
903                         event.getData().putBoolean("isConnected", false);
904                     }
905                     event.getData().putBundle(
906                             EVENT_KEY_P2P_INFO, BundleUtils.fromWifiP2pInfo(p2pInfo));
907                     event.getData().putBundle(
908                             EVENT_KEY_P2P_GROUP, BundleUtils.fromWifiP2pGroup(p2pGroup));
909                     break;
910             }
911             EventCache.getInstance().postEvent(event);
912         }
913     }
914 
915     private static class ActionListener implements WifiP2pManager.ActionListener {
916         public static final String CALLBACK_EVENT_NAME = "WifiP2pManagerActionListenerCallback";
917 
918         private final String mCallbackId;
919 
ActionListener(String callbackId)920         ActionListener(String callbackId) {
921             this.mCallbackId = callbackId;
922         }
923 
924         @Override
onSuccess()925         public void onSuccess() {
926             SnippetEvent event = new SnippetEvent(mCallbackId, CALLBACK_EVENT_NAME);
927             event.getData().putString(EVENT_KEY_CALLBACK_NAME, ACTION_LISTENER_ON_SUCCESS);
928             EventCache.getInstance().postEvent(event);
929         }
930 
931         @Override
onFailure(int reason)932         public void onFailure(int reason) {
933             SnippetEvent event = new SnippetEvent(mCallbackId, CALLBACK_EVENT_NAME);
934             event.getData().putString(EVENT_KEY_CALLBACK_NAME, ACTION_LISTENER_ON_FAILURE);
935             event.getData().putInt(EVENT_KEY_REASON, reason);
936             EventCache.getInstance().postEvent(event);
937         }
938     }
939 
940     private static class  WifiP2pConnectionInfoListener
941         implements WifiP2pManager.ConnectionInfoListener {
942         public static final String EVENT_NAME_ON_CONNECTION_INFO =
943             "WifiP2pOnConnectionInfoAvailable";
944         private final String mCallbackId;
945 
WifiP2pConnectionInfoListener(String callbackId)946         public WifiP2pConnectionInfoListener(String callbackId) {
947             this.mCallbackId = callbackId;
948         }
949 
950         @Override
onConnectionInfoAvailable(WifiP2pInfo info)951         public void onConnectionInfoAvailable(WifiP2pInfo info) {
952             Log.d(TAG + ": onConnectionInfoAvailable: " + info.toString());
953             SnippetEvent event = new SnippetEvent(mCallbackId, EVENT_NAME_ON_CONNECTION_INFO);
954             event.getData().putBoolean("groupFormed", info.groupFormed);
955             event.getData().putBoolean("isGroupOwner", info.isGroupOwner);
956             event.getData().putString("groupOwnerHostAddress", info.groupOwnerAddress.toString());
957             EventCache.getInstance().postEvent(event);
958         }
959     }
960 
961     private static class DeviceInfoListener implements WifiP2pManager.DeviceInfoListener {
962         public static final String EVENT_NAME_ON_DEVICE_INFO = "WifiP2pOnDeviceInfoAvailable";
963 
964         private final String mCallbackId;
965 
DeviceInfoListener(String callbackId)966         DeviceInfoListener(String callbackId) {
967             this.mCallbackId = callbackId;
968         }
969 
970         @Override
onDeviceInfoAvailable(WifiP2pDevice device)971         public void onDeviceInfoAvailable(WifiP2pDevice device) {
972             if (device == null) {
973                 return;
974             }
975             Log.d(TAG + ": onDeviceInfoAvailable: " + device.toString());
976             SnippetEvent event = new SnippetEvent(mCallbackId, EVENT_NAME_ON_DEVICE_INFO);
977             event.getData().putBundle(EVENT_KEY_P2P_DEVICE, BundleUtils.fromWifiP2pDevice(device));
978             EventCache.getInstance().postEvent(event);
979         }
980     }
981 
982     private static class PeerListListener implements WifiP2pManager.PeerListListener {
983         private final String mCallbackId;
984 
PeerListListener(String callbackId)985         PeerListListener(String callbackId) {
986             this.mCallbackId = callbackId;
987         }
988 
989         @Override
onPeersAvailable(WifiP2pDeviceList newPeers)990         public void onPeersAvailable(WifiP2pDeviceList newPeers) {
991             Log.d(TAG + ": onPeersAvailable: " + newPeers.getDeviceList());
992             ArrayList<Bundle> devices = BundleUtils.fromWifiP2pDeviceList(newPeers);
993             SnippetEvent event = new SnippetEvent(mCallbackId, "WifiP2pOnPeersAvailable");
994             event.getData().putParcelableArrayList(EVENT_KEY_PEER_LIST, devices);
995             event.getData().putLong(EVENT_KEY_TIMESTAMP_MS, System.currentTimeMillis());
996             EventCache.getInstance().postEvent(event);
997         }
998     }
999 
1000     private static class PersistentGroupInfoListener
1001             implements WifiP2pManager.PersistentGroupInfoListener {
1002         private final String mCallbackId;
1003 
PersistentGroupInfoListener(String callbackId)1004         PersistentGroupInfoListener(String callbackId) {
1005             this.mCallbackId = callbackId;
1006         }
1007 
1008         @Override
onPersistentGroupInfoAvailable(@onNull WifiP2pGroupList groups)1009         public void onPersistentGroupInfoAvailable(@NonNull WifiP2pGroupList groups) {
1010             Log.d(TAG + ": onPersistentGroupInfoAvailable: " + groups.toString());
1011             SnippetEvent event = new SnippetEvent(mCallbackId, "onPersistentGroupInfoAvailable");
1012             event.getData()
1013                     .putParcelableArrayList("groupList", BundleUtils.fromWifiP2pGroupList(groups));
1014             EventCache.getInstance().postEvent(event);
1015         }
1016     }
1017 
1018     private static class UpnpServiceResponseListener
1019             implements WifiP2pManager.UpnpServiceResponseListener {
1020         private final String mCallbackId;
1021 
UpnpServiceResponseListener(String callbackId)1022         UpnpServiceResponseListener(String callbackId) {
1023             this.mCallbackId = callbackId;
1024         }
1025 
1026         @Override
onUpnpServiceAvailable(List<String> uniqueServiceNames, WifiP2pDevice srcDevice)1027         public void onUpnpServiceAvailable(List<String> uniqueServiceNames,
1028                 WifiP2pDevice srcDevice) {
1029             Log.d(TAG + ": onUpnpServiceAvailable: service names: " + uniqueServiceNames);
1030             SnippetEvent event = new SnippetEvent(mCallbackId, "onUpnpServiceAvailable");
1031             event.getData()
1032                     .putBundle(EVENT_KEY_SOURCE_DEVICE, BundleUtils.fromWifiP2pDevice(srcDevice));
1033             event.getData()
1034                     .putStringArrayList(EVENT_KEY_SERVICE_LIST, new ArrayList(uniqueServiceNames));
1035             EventCache.getInstance().postEvent(event);
1036         }
1037     }
1038 
1039     private static class DnsSdServiceResponseListener
1040             implements WifiP2pManager.DnsSdServiceResponseListener {
1041         private final String mCallbackId;
1042 
DnsSdServiceResponseListener(String callbackId)1043         DnsSdServiceResponseListener(String callbackId) {
1044             this.mCallbackId = callbackId;
1045         }
1046 
1047         @Override
onDnsSdServiceAvailable(String instanceName, String registrationType, WifiP2pDevice srcDevice)1048         public void onDnsSdServiceAvailable(String instanceName, String registrationType,
1049                 WifiP2pDevice srcDevice) {
1050             SnippetEvent event = new SnippetEvent(mCallbackId, "onDnsSdServiceAvailable");
1051             event.getData().putString(EVENT_KEY_INSTANCE_NAME, instanceName);
1052             event.getData().putString(EVENT_KEY_REGISTRATION_TYPE, registrationType);
1053             event.getData()
1054                     .putBundle(EVENT_KEY_SOURCE_DEVICE, BundleUtils.fromWifiP2pDevice(srcDevice));
1055             EventCache.getInstance().postEvent(event);
1056         }
1057     }
1058 
1059     private static class DnsSdTxtRecordListener implements WifiP2pManager.DnsSdTxtRecordListener {
1060         private final String mCallbackId;
1061 
DnsSdTxtRecordListener(String callbackId)1062         DnsSdTxtRecordListener(String callbackId) {
1063             this.mCallbackId = callbackId;
1064         }
1065 
1066         @Override
onDnsSdTxtRecordAvailable(String fullDomainName, Map<String, String> txtRecordMap, WifiP2pDevice srcDevice)1067         public void onDnsSdTxtRecordAvailable(String fullDomainName,
1068                 Map<String, String> txtRecordMap, WifiP2pDevice srcDevice) {
1069             SnippetEvent event = new SnippetEvent(mCallbackId, "onDnsSdTxtRecordAvailable");
1070             event.getData().putString(EVENT_KEY_FULL_DOMAIN_NAME, fullDomainName);
1071             Bundle txtMap = new Bundle();
1072             for (String key : txtRecordMap.keySet()) {
1073                 txtMap.putString(key, txtRecordMap.get(key));
1074             }
1075             event.getData().putBundle(EVENT_KEY_TXT_RECORD_MAP, txtMap);
1076             event.getData()
1077                     .putBundle(EVENT_KEY_SOURCE_DEVICE, BundleUtils.fromWifiP2pDevice(srcDevice));
1078             EventCache.getInstance().postEvent(event);
1079         }
1080     }
1081 
1082     /**
1083      * Get the channel by channel ID.
1084      *
1085      * @param channelId The ID of the channel for Wi-Fi P2P to operate on.
1086      * @return The channel.
1087      * @throws WifiP2pManagerException If the channel is not created.
1088      */
getChannel(int channelId)1089     private WifiP2pManager.Channel getChannel(int channelId)
1090             throws WifiP2pManagerException {
1091         WifiP2pManager.Channel channel = mChannels.get(channelId);
1092         if (channel == null) {
1093             Log.e(TAG + ": getChannel : channel keys" + mChannels.keySet());
1094             throw new WifiP2pManagerException(
1095                     "The channelId " + channelId + " is wrong. Please use the channelId returned "
1096                             + "by calling `wifiP2pInitialize` or `wifiP2pInitExtraChannel`.");
1097         }
1098         return channel;
1099     }
1100 
1101     /**
1102      * Check if the device supports Wi-Fi Direct.
1103      *
1104      * @throws WifiP2pManagerException If the device does not support Wi-Fi Direct.
1105      */
checkP2pManager()1106     private void checkP2pManager() throws WifiP2pManagerException {
1107         if (mP2pManager == null) {
1108             throw new WifiP2pManagerException("Device does not support Wi-Fi Direct.");
1109         }
1110     }
1111 
1112     /**
1113      * Check permissions for the given permissions.
1114      *
1115      * @param context The context to check permissions.
1116      * @param permissions The permissions to check.
1117      */
checkPermissions(Context context, String... permissions)1118     private static void checkPermissions(Context context, String... permissions) {
1119         for (String permission : permissions) {
1120             if (context.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
1121                 throw new SecurityException(
1122                         "Permission denied (missing " + permission + " permission)");
1123             }
1124         }
1125     }
1126 
1127     /**
1128      * Wait until any callback of {@link ActionListener} is triggered.
1129      *
1130      * @param callbackId The callback ID associated with the action listener.
1131      * @return The event posted by the callback methods of {@link ActionListener}.
1132      * @throws Throwable If the action timed out.
1133      */
waitActionListenerResult(String callbackId)1134     private Bundle waitActionListenerResult(String callbackId) throws Throwable {
1135         SnippetEvent event = waitForSnippetEvent(callbackId, ActionListener.CALLBACK_EVENT_NAME,
1136                 TIMEOUT_SHORT_MS);
1137         Log.d("Got action listener result event: " + event.getData().toString());
1138         return event.getData();
1139     }
1140 
1141     /**
1142      * Wait until any callback of {@link ActionListener} is triggered and verify it succeeded.
1143      *
1144      * @param callbackId The callback ID associated with the action listener.
1145      * @throws Throwable If the action timed out or failed.
1146      */
verifyActionListenerSucceed(String callbackId)1147     private void verifyActionListenerSucceed(String callbackId) throws Throwable {
1148         Bundle eventData = waitActionListenerResult(callbackId);
1149         String result = eventData.getString(EVENT_KEY_CALLBACK_NAME);
1150         if (Objects.equals(ACTION_LISTENER_ON_SUCCESS, result)) {
1151             return;
1152         }
1153         if (Objects.equals(ACTION_LISTENER_ON_FAILURE, result)) {
1154             // Please keep reason code in error message for client side to check the reason.
1155             throw new WifiP2pManagerException(
1156                     "Action failed with reason_code=" + eventData.getInt(EVENT_KEY_REASON)
1157             );
1158         }
1159         throw new WifiP2pManagerException("Action got unknown event: " + eventData.toString());
1160     }
1161 
1162     /**
1163      * Wait for a SnippetEvent with the given callbackId and eventName.
1164      *
1165      * @param callbackId The callback ID associated with the action listener.
1166      * @param eventName The event name to wait for.
1167      * @param timeout The timeout in milliseconds.
1168      * @return The SnippetEvent.
1169      * @throws Throwable If the action timed out.
1170      */
waitForSnippetEvent(String callbackId, String eventName, Integer timeout)1171     private static SnippetEvent waitForSnippetEvent(String callbackId, String eventName,
1172             Integer timeout) throws Throwable {
1173         String qId = EventCache.getQueueId(callbackId, eventName);
1174         LinkedBlockingDeque<SnippetEvent> q = EventCache.getInstance().getEventDeque(qId);
1175         SnippetEvent result;
1176         try {
1177             result = q.pollFirst(timeout, TimeUnit.MILLISECONDS);
1178         } catch (InterruptedException e) {
1179             throw e.getCause();
1180         }
1181 
1182         if (result == null) {
1183             throw new TimeoutException(
1184                     "Timed out waiting(" + timeout + " millis) for SnippetEvent: " + callbackId);
1185         }
1186         return result;
1187     }
1188 }
1189