1 /*
2  * Copyright (C) 2011 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 android.net.wifi.p2p;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 import android.os.Parcel;
21 import android.os.Parcelable;
22 import android.util.Log;
23 
24 import java.util.Objects;
25 import java.util.regex.Matcher;
26 import java.util.regex.Pattern;
27 
28 /**
29  * A class representing a Wi-Fi p2p device
30  *
31  * Note that the operations are not thread safe
32  * {@see WifiP2pManager}
33  */
34 public class WifiP2pDevice implements Parcelable {
35 
36     private static final String TAG = "WifiP2pDevice";
37 
38     /**
39      * The device name is a user friendly string to identify a Wi-Fi p2p device
40      */
41     public String deviceName = "";
42 
43     /**
44      * The device MAC address uniquely identifies a Wi-Fi p2p device
45      */
46     public String deviceAddress = "";
47 
48     /**
49      * Primary device type identifies the type of device. For example, an application
50      * could filter the devices discovered to only display printers if the purpose is to
51      * enable a printing action from the user. See the Wi-Fi Direct technical specification
52      * for the full list of standard device types supported.
53      */
54     public String primaryDeviceType;
55 
56     /**
57      * Secondary device type is an optional attribute that can be provided by a device in
58      * addition to the primary device type.
59      */
60     public String secondaryDeviceType;
61 
62 
63     // These definitions match the ones in wpa_supplicant
64     /* WPS config methods supported */
65     private static final int WPS_CONFIG_DISPLAY         = 0x0008;
66     private static final int WPS_CONFIG_PUSHBUTTON      = 0x0080;
67     private static final int WPS_CONFIG_KEYPAD          = 0x0100;
68 
69     /* Device Capability bitmap */
70     private static final int DEVICE_CAPAB_SERVICE_DISCOVERY         = 1;
71     @SuppressWarnings("unused")
72     private static final int DEVICE_CAPAB_CLIENT_DISCOVERABILITY    = 1<<1;
73     @SuppressWarnings("unused")
74     private static final int DEVICE_CAPAB_CONCURRENT_OPER           = 1<<2;
75     @SuppressWarnings("unused")
76     private static final int DEVICE_CAPAB_INFRA_MANAGED             = 1<<3;
77     @SuppressWarnings("unused")
78     private static final int DEVICE_CAPAB_DEVICE_LIMIT              = 1<<4;
79     private static final int DEVICE_CAPAB_INVITATION_PROCEDURE      = 1<<5;
80 
81     /* Group Capability bitmap */
82     private static final int GROUP_CAPAB_GROUP_OWNER                = 1;
83     @SuppressWarnings("unused")
84     private static final int GROUP_CAPAB_PERSISTENT_GROUP           = 1<<1;
85     private static final int GROUP_CAPAB_GROUP_LIMIT                = 1<<2;
86     @SuppressWarnings("unused")
87     private static final int GROUP_CAPAB_INTRA_BSS_DIST             = 1<<3;
88     @SuppressWarnings("unused")
89     private static final int GROUP_CAPAB_CROSS_CONN                 = 1<<4;
90     @SuppressWarnings("unused")
91     private static final int GROUP_CAPAB_PERSISTENT_RECONN          = 1<<5;
92     @SuppressWarnings("unused")
93     private static final int GROUP_CAPAB_GROUP_FORMATION            = 1<<6;
94 
95     /**
96      * WPS config methods supported
97      * @hide
98      */
99     @UnsupportedAppUsage
100     public int wpsConfigMethodsSupported;
101 
102     /**
103      * Device capability
104      * @hide
105      */
106     @UnsupportedAppUsage
107     public int deviceCapability;
108 
109     /**
110      * Group capability
111      * @hide
112      */
113     @UnsupportedAppUsage
114     public int groupCapability;
115 
116     public static final int CONNECTED   = 0;
117     public static final int INVITED     = 1;
118     public static final int FAILED      = 2;
119     public static final int AVAILABLE   = 3;
120     public static final int UNAVAILABLE = 4;
121 
122     /** Device connection status */
123     public int status = UNAVAILABLE;
124 
125     /** @hide */
126     @UnsupportedAppUsage
127     public WifiP2pWfdInfo wfdInfo;
128 
129     /** Detailed device string pattern with WFD info
130      * Example:
131      *  P2P-DEVICE-FOUND 00:18:6b:de:a3:6e p2p_dev_addr=00:18:6b:de:a3:6e
132      *  pri_dev_type=1-0050F204-1 name='DWD-300-DEA36E' config_methods=0x188
133      *  dev_capab=0x21 group_capab=0x9
134      */
135     private static final Pattern detailedDevicePattern = Pattern.compile(
136         "((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) " +
137         "(\\d+ )?" +
138         "p2p_dev_addr=((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) " +
139         "pri_dev_type=(\\d+-[0-9a-fA-F]+-\\d+) " +
140         "name='(.*)' " +
141         "config_methods=(0x[0-9a-fA-F]+) " +
142         "dev_capab=(0x[0-9a-fA-F]+) " +
143         "group_capab=(0x[0-9a-fA-F]+)" +
144         "( wfd_dev_info=0x([0-9a-fA-F]{12}))?"
145     );
146 
147     /** 2 token device address pattern
148      * Example:
149      *  P2P-DEVICE-LOST p2p_dev_addr=fa:7b:7a:42:02:13
150      *  AP-STA-DISCONNECTED 42:fc:89:a8:96:09
151      */
152     private static final Pattern twoTokenPattern = Pattern.compile(
153         "(p2p_dev_addr=)?((?:[0-9a-f]{2}:){5}[0-9a-f]{2})"
154     );
155 
156     /** 3 token device address pattern
157      * Example:
158      *  AP-STA-CONNECTED 42:fc:89:a8:96:09 p2p_dev_addr=fa:7b:7a:42:02:13
159      *  AP-STA-DISCONNECTED 42:fc:89:a8:96:09 p2p_dev_addr=fa:7b:7a:42:02:13
160      */
161     private static final Pattern threeTokenPattern = Pattern.compile(
162         "(?:[0-9a-f]{2}:){5}[0-9a-f]{2} p2p_dev_addr=((?:[0-9a-f]{2}:){5}[0-9a-f]{2})"
163     );
164 
165 
WifiP2pDevice()166     public WifiP2pDevice() {
167     }
168 
169     /**
170      * @param string formats supported include
171      *  P2P-DEVICE-FOUND fa:7b:7a:42:02:13 p2p_dev_addr=fa:7b:7a:42:02:13
172      *  pri_dev_type=1-0050F204-1 name='p2p-TEST1' config_methods=0x188 dev_capab=0x27
173      *  group_capab=0x0 wfd_dev_info=000006015d022a0032
174      *
175      *  P2P-DEVICE-LOST p2p_dev_addr=fa:7b:7a:42:02:13
176      *
177      *  AP-STA-CONNECTED 42:fc:89:a8:96:09 [p2p_dev_addr=02:90:4c:a0:92:54]
178      *
179      *  AP-STA-DISCONNECTED 42:fc:89:a8:96:09 [p2p_dev_addr=02:90:4c:a0:92:54]
180      *
181      *  fa:7b:7a:42:02:13
182      *
183      *  Note: The events formats can be looked up in the wpa_supplicant code
184      * @hide
185      */
186     @UnsupportedAppUsage
WifiP2pDevice(String string)187     public WifiP2pDevice(String string) throws IllegalArgumentException {
188         String[] tokens = string.split("[ \n]");
189         Matcher match;
190 
191         if (tokens.length < 1) {
192             throw new IllegalArgumentException("Malformed supplicant event");
193         }
194 
195         switch (tokens.length) {
196             case 1:
197                 /* Just a device address */
198                 deviceAddress = string;
199                 return;
200             case 2:
201                 match = twoTokenPattern.matcher(string);
202                 if (!match.find()) {
203                     throw new IllegalArgumentException("Malformed supplicant event");
204                 }
205                 deviceAddress = match.group(2);
206                 return;
207             case 3:
208                 match = threeTokenPattern.matcher(string);
209                 if (!match.find()) {
210                     throw new IllegalArgumentException("Malformed supplicant event");
211                 }
212                 deviceAddress = match.group(1);
213                 return;
214             default:
215                 match = detailedDevicePattern.matcher(string);
216                 if (!match.find()) {
217                     throw new IllegalArgumentException("Malformed supplicant event");
218                 }
219 
220                 deviceAddress = match.group(3);
221                 primaryDeviceType = match.group(4);
222                 deviceName = match.group(5);
223                 wpsConfigMethodsSupported = parseHex(match.group(6));
224                 deviceCapability = parseHex(match.group(7));
225                 groupCapability = parseHex(match.group(8));
226                 if (match.group(9) != null) {
227                     String str = match.group(10);
228                     wfdInfo = new WifiP2pWfdInfo(parseHex(str.substring(0,4)),
229                             parseHex(str.substring(4,8)),
230                             parseHex(str.substring(8,12)));
231                 }
232                 break;
233         }
234 
235         if (tokens[0].startsWith("P2P-DEVICE-FOUND")) {
236             status = AVAILABLE;
237         }
238     }
239 
240     /** Returns true if WPS push button configuration is supported */
wpsPbcSupported()241     public boolean wpsPbcSupported() {
242         return (wpsConfigMethodsSupported & WPS_CONFIG_PUSHBUTTON) != 0;
243     }
244 
245     /** Returns true if WPS keypad configuration is supported */
wpsKeypadSupported()246     public boolean wpsKeypadSupported() {
247         return (wpsConfigMethodsSupported & WPS_CONFIG_KEYPAD) != 0;
248     }
249 
250     /** Returns true if WPS display configuration is supported */
wpsDisplaySupported()251     public boolean wpsDisplaySupported() {
252         return (wpsConfigMethodsSupported & WPS_CONFIG_DISPLAY) != 0;
253     }
254 
255     /** Returns true if the device is capable of service discovery */
isServiceDiscoveryCapable()256     public boolean isServiceDiscoveryCapable() {
257         return (deviceCapability & DEVICE_CAPAB_SERVICE_DISCOVERY) != 0;
258     }
259 
260     /** Returns true if the device is capable of invitation {@hide}*/
isInvitationCapable()261     public boolean isInvitationCapable() {
262         return (deviceCapability & DEVICE_CAPAB_INVITATION_PROCEDURE) != 0;
263     }
264 
265     /** Returns true if the device reaches the limit. {@hide}*/
isDeviceLimit()266     public boolean isDeviceLimit() {
267         return (deviceCapability & DEVICE_CAPAB_DEVICE_LIMIT) != 0;
268     }
269 
270     /** Returns true if the device is a group owner */
isGroupOwner()271     public boolean isGroupOwner() {
272         return (groupCapability & GROUP_CAPAB_GROUP_OWNER) != 0;
273     }
274 
275     /** Returns true if the group reaches the limit. {@hide}*/
isGroupLimit()276     public boolean isGroupLimit() {
277         return (groupCapability & GROUP_CAPAB_GROUP_LIMIT) != 0;
278     }
279 
280     /**
281      * Update device details. This will be throw an exception if the device address
282      * does not match.
283      * @param device to be updated
284      * @throws IllegalArgumentException if the device is null or device address does not match
285      * @hide
286      */
287     @UnsupportedAppUsage
update(WifiP2pDevice device)288     public void update(WifiP2pDevice device) {
289         updateSupplicantDetails(device);
290         status = device.status;
291     }
292 
293     /** Updates details obtained from supplicant @hide */
updateSupplicantDetails(WifiP2pDevice device)294     public void updateSupplicantDetails(WifiP2pDevice device) {
295         if (device == null) {
296             throw new IllegalArgumentException("device is null");
297         }
298         if (device.deviceAddress == null) {
299             throw new IllegalArgumentException("deviceAddress is null");
300         }
301         if (!deviceAddress.equals(device.deviceAddress)) {
302             throw new IllegalArgumentException("deviceAddress does not match");
303         }
304         deviceName = device.deviceName;
305         primaryDeviceType = device.primaryDeviceType;
306         secondaryDeviceType = device.secondaryDeviceType;
307         wpsConfigMethodsSupported = device.wpsConfigMethodsSupported;
308         deviceCapability = device.deviceCapability;
309         groupCapability = device.groupCapability;
310         wfdInfo = device.wfdInfo;
311     }
312 
313     @Override
equals(Object obj)314     public boolean equals(Object obj) {
315         if (this == obj) return true;
316         if (!(obj instanceof WifiP2pDevice)) return false;
317 
318         WifiP2pDevice other = (WifiP2pDevice) obj;
319         if (other == null || other.deviceAddress == null) {
320             return (deviceAddress == null);
321         }
322         return other.deviceAddress.equals(deviceAddress);
323     }
324 
325     @Override
hashCode()326     public int hashCode() {
327         return Objects.hashCode(deviceAddress);
328     }
329 
330     @Override
toString()331     public String toString() {
332         StringBuffer sbuf = new StringBuffer();
333         sbuf.append("Device: ").append(deviceName);
334         sbuf.append("\n deviceAddress: ").append(deviceAddress);
335         sbuf.append("\n primary type: ").append(primaryDeviceType);
336         sbuf.append("\n secondary type: ").append(secondaryDeviceType);
337         sbuf.append("\n wps: ").append(wpsConfigMethodsSupported);
338         sbuf.append("\n grpcapab: ").append(groupCapability);
339         sbuf.append("\n devcapab: ").append(deviceCapability);
340         sbuf.append("\n status: ").append(status);
341         sbuf.append("\n wfdInfo: ").append(wfdInfo);
342         return sbuf.toString();
343     }
344 
345     /** Implement the Parcelable interface */
346     @Override
describeContents()347     public int describeContents() {
348         return 0;
349     }
350 
351     /** copy constructor */
WifiP2pDevice(WifiP2pDevice source)352     public WifiP2pDevice(WifiP2pDevice source) {
353         if (source != null) {
354             deviceName = source.deviceName;
355             deviceAddress = source.deviceAddress;
356             primaryDeviceType = source.primaryDeviceType;
357             secondaryDeviceType = source.secondaryDeviceType;
358             wpsConfigMethodsSupported = source.wpsConfigMethodsSupported;
359             deviceCapability = source.deviceCapability;
360             groupCapability = source.groupCapability;
361             status = source.status;
362             if (source.wfdInfo != null) {
363                 wfdInfo = new WifiP2pWfdInfo(source.wfdInfo);
364             }
365         }
366     }
367 
368     /** Implement the Parcelable interface */
369     @Override
writeToParcel(Parcel dest, int flags)370     public void writeToParcel(Parcel dest, int flags) {
371         dest.writeString(deviceName);
372         dest.writeString(deviceAddress);
373         dest.writeString(primaryDeviceType);
374         dest.writeString(secondaryDeviceType);
375         dest.writeInt(wpsConfigMethodsSupported);
376         dest.writeInt(deviceCapability);
377         dest.writeInt(groupCapability);
378         dest.writeInt(status);
379         if (wfdInfo != null) {
380             dest.writeInt(1);
381             wfdInfo.writeToParcel(dest, flags);
382         } else {
383             dest.writeInt(0);
384         }
385     }
386 
387     /** Implement the Parcelable interface */
388     public static final @android.annotation.NonNull Creator<WifiP2pDevice> CREATOR =
389         new Creator<WifiP2pDevice>() {
390             @Override
391             public WifiP2pDevice createFromParcel(Parcel in) {
392                 WifiP2pDevice device = new WifiP2pDevice();
393                 device.deviceName = in.readString();
394                 device.deviceAddress = in.readString();
395                 device.primaryDeviceType = in.readString();
396                 device.secondaryDeviceType = in.readString();
397                 device.wpsConfigMethodsSupported = in.readInt();
398                 device.deviceCapability = in.readInt();
399                 device.groupCapability = in.readInt();
400                 device.status = in.readInt();
401                 if (in.readInt() == 1) {
402                     device.wfdInfo = WifiP2pWfdInfo.CREATOR.createFromParcel(in);
403                 }
404                 return device;
405             }
406 
407             @Override
408             public WifiP2pDevice[] newArray(int size) {
409                 return new WifiP2pDevice[size];
410             }
411         };
412 
413     //supported formats: 0x1abc, 0X1abc, 1abc
parseHex(String hexString)414     private int parseHex(String hexString) {
415         int num = 0;
416         if (hexString.startsWith("0x") || hexString.startsWith("0X")) {
417             hexString = hexString.substring(2);
418         }
419 
420         try {
421             num = Integer.parseInt(hexString, 16);
422         } catch(NumberFormatException e) {
423             Log.e(TAG, "Failed to parse hex string " + hexString);
424         }
425         return num;
426     }
427 }
428