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.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.compat.annotation.UnsupportedAppUsage;
23 import android.net.MacAddress;
24 import android.net.wifi.WpsInfo;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 import android.text.TextUtils;
28 
29 import java.lang.annotation.Retention;
30 import java.lang.annotation.RetentionPolicy;
31 import java.nio.charset.StandardCharsets;
32 import java.util.regex.PatternSyntaxException;
33 
34 /**
35  * A class representing a Wi-Fi P2p configuration for setting up a connection
36  *
37  * {@see WifiP2pManager}
38  */
39 public class WifiP2pConfig implements Parcelable {
40 
41     /**
42      * The device MAC address uniquely identifies a Wi-Fi p2p device
43      */
44     public String deviceAddress = "";
45 
46     /**
47      * Wi-Fi Protected Setup information
48      */
49     public WpsInfo wps;
50 
51     /**
52      * The network name of a group, should be configured by helper method
53      */
54     /** @hide */
55     public String networkName = "";
56 
57     /**
58      * The passphrase of a group, should be configured by helper method
59      */
60     /** @hide */
61     public String passphrase = "";
62 
63     /**
64      * The required band for Group Owner
65      */
66     /** @hide */
67     public int groupOwnerBand = GROUP_OWNER_BAND_AUTO;
68 
69     /** @hide */
70     public static final int MAX_GROUP_OWNER_INTENT   =   15;
71     /** @hide */
72     @UnsupportedAppUsage
73     public static final int MIN_GROUP_OWNER_INTENT   =   0;
74 
75     /** @hide */
76     @IntDef(flag = false, prefix = { "GROUP_OWNER_BAND_" }, value = {
77         GROUP_OWNER_BAND_AUTO,
78         GROUP_OWNER_BAND_2GHZ,
79         GROUP_OWNER_BAND_5GHZ
80     })
81     @Retention(RetentionPolicy.SOURCE)
82     public @interface GroupOperatingBandType {}
83 
84     /**
85      * Allow the system to pick the operating frequency from all supported bands.
86      */
87     public static final int GROUP_OWNER_BAND_AUTO = 0;
88     /**
89      * Allow the system to pick the operating frequency from the 2.4 GHz band.
90      */
91     public static final int GROUP_OWNER_BAND_2GHZ = 1;
92     /**
93      * Allow the system to pick the operating frequency from the 5 GHz band.
94      */
95     public static final int GROUP_OWNER_BAND_5GHZ = 2;
96 
97     /**
98      * This is an integer value between 0 and 15 where 0 indicates the least
99      * inclination to be a group owner and 15 indicates the highest inclination
100      * to be a group owner.
101      *
102      * A value of -1 indicates the system can choose an appropriate value.
103      */
104     public int groupOwnerIntent = -1;
105 
106     /** @hide */
107     @UnsupportedAppUsage
108     public int netId = WifiP2pGroup.PERSISTENT_NET_ID;
109 
WifiP2pConfig()110     public WifiP2pConfig() {
111         //set defaults
112         wps = new WpsInfo();
113         wps.setup = WpsInfo.PBC;
114     }
115 
116     /** @hide */
invalidate()117     public void invalidate() {
118         deviceAddress = "";
119     }
120 
121     /** P2P-GO-NEG-REQUEST 42:fc:89:a8:96:09 dev_passwd_id=4 {@hide}*/
122     @UnsupportedAppUsage
WifiP2pConfig(String supplicantEvent)123     public WifiP2pConfig(String supplicantEvent) throws IllegalArgumentException {
124         String[] tokens = supplicantEvent.split(" ");
125 
126         if (tokens.length < 2 || !tokens[0].equals("P2P-GO-NEG-REQUEST")) {
127             throw new IllegalArgumentException("Malformed supplicant event");
128         }
129 
130         deviceAddress = tokens[1];
131         wps = new WpsInfo();
132 
133         if (tokens.length > 2) {
134             String[] nameVal = tokens[2].split("=");
135             int devPasswdId;
136             try {
137                 devPasswdId = Integer.parseInt(nameVal[1]);
138             } catch (NumberFormatException e) {
139                 devPasswdId = 0;
140             }
141             //Based on definitions in wps/wps_defs.h
142             switch (devPasswdId) {
143                 //DEV_PW_USER_SPECIFIED = 0x0001,
144                 case 0x01:
145                     wps.setup = WpsInfo.DISPLAY;
146                     break;
147                 //DEV_PW_PUSHBUTTON = 0x0004,
148                 case 0x04:
149                     wps.setup = WpsInfo.PBC;
150                     break;
151                 //DEV_PW_REGISTRAR_SPECIFIED = 0x0005
152                 case 0x05:
153                     wps.setup = WpsInfo.KEYPAD;
154                     break;
155                 default:
156                     wps.setup = WpsInfo.PBC;
157                     break;
158             }
159         }
160     }
161 
toString()162     public String toString() {
163         StringBuffer sbuf = new StringBuffer();
164         sbuf.append("\n address: ").append(deviceAddress);
165         sbuf.append("\n wps: ").append(wps);
166         sbuf.append("\n groupOwnerIntent: ").append(groupOwnerIntent);
167         sbuf.append("\n persist: ").append(netId);
168         sbuf.append("\n networkName: ").append(networkName);
169         sbuf.append("\n passphrase: ").append(
170                 TextUtils.isEmpty(passphrase) ? "<empty>" : "<non-empty>");
171         sbuf.append("\n groupOwnerBand: ").append(groupOwnerBand);
172         return sbuf.toString();
173     }
174 
175     /** Implement the Parcelable interface */
describeContents()176     public int describeContents() {
177         return 0;
178     }
179 
180     /** copy constructor */
WifiP2pConfig(WifiP2pConfig source)181     public WifiP2pConfig(WifiP2pConfig source) {
182         if (source != null) {
183             deviceAddress = source.deviceAddress;
184             wps = new WpsInfo(source.wps);
185             groupOwnerIntent = source.groupOwnerIntent;
186             netId = source.netId;
187             networkName = source.networkName;
188             passphrase = source.passphrase;
189             groupOwnerBand = source.groupOwnerBand;
190         }
191     }
192 
193     /** Implement the Parcelable interface */
writeToParcel(Parcel dest, int flags)194     public void writeToParcel(Parcel dest, int flags) {
195         dest.writeString(deviceAddress);
196         dest.writeParcelable(wps, flags);
197         dest.writeInt(groupOwnerIntent);
198         dest.writeInt(netId);
199         dest.writeString(networkName);
200         dest.writeString(passphrase);
201         dest.writeInt(groupOwnerBand);
202     }
203 
204     /** Implement the Parcelable interface */
205     public static final @android.annotation.NonNull Creator<WifiP2pConfig> CREATOR =
206         new Creator<WifiP2pConfig>() {
207             public WifiP2pConfig createFromParcel(Parcel in) {
208                 WifiP2pConfig config = new WifiP2pConfig();
209                 config.deviceAddress = in.readString();
210                 config.wps = (WpsInfo) in.readParcelable(null);
211                 config.groupOwnerIntent = in.readInt();
212                 config.netId = in.readInt();
213                 config.networkName = in.readString();
214                 config.passphrase = in.readString();
215                 config.groupOwnerBand = in.readInt();
216                 return config;
217             }
218 
219             public WifiP2pConfig[] newArray(int size) {
220                 return new WifiP2pConfig[size];
221             }
222         };
223 
224     /**
225      * Builder used to build {@link WifiP2pConfig} objects for
226      * creating or joining a group.
227      */
228     public static final class Builder {
229 
230         private static final MacAddress MAC_ANY_ADDRESS =
231                 MacAddress.fromString("02:00:00:00:00:00");
232         /**
233          * Maximum number of bytes allowed for a SSID.
234          */
235         private static final int MAX_SSID_BYTES = 32;
236 
237         private MacAddress mDeviceAddress = MAC_ANY_ADDRESS;
238         private String mNetworkName = "";
239         private String mPassphrase = "";
240         private int mGroupOperatingBand = GROUP_OWNER_BAND_AUTO;
241         private int mGroupOperatingFrequency = GROUP_OWNER_BAND_AUTO;
242         private int mNetId = WifiP2pGroup.TEMPORARY_NET_ID;
243 
244         /**
245          * Specify the peer's MAC address. If not set, the device will
246          * try to find a peer whose SSID matches the network name as
247          * specified by {@link #setNetworkName(String)}. Specifying null will
248          * reset the peer's MAC address to "02:00:00:00:00:00".
249          * <p>
250          *     Optional. "02:00:00:00:00:00" by default.
251          *
252          * @param deviceAddress the peer's MAC address.
253          * @return The builder to facilitate chaining
254          *         {@code builder.setXXX(..).setXXX(..)}.
255          */
setDeviceAddress(@ullable MacAddress deviceAddress)256         public @NonNull Builder setDeviceAddress(@Nullable MacAddress deviceAddress) {
257             if (deviceAddress == null) {
258                 mDeviceAddress = MAC_ANY_ADDRESS;
259             } else {
260                 mDeviceAddress = deviceAddress;
261             }
262             return this;
263         }
264 
265         /**
266          * Specify the network name, a.k.a. group name,
267          * for creating or joining a group.
268          * <p>
269          * A network name shall begin with "DIRECT-xy". x and y are selected
270          * from the following character set: upper case letters, lower case
271          * letters and numbers. Any byte values allowed for an SSID according to
272          * IEEE802.11-2012 [1] may be included after the string "DIRECT-xy"
273          * (including none).
274          * <p>
275          *     Must be called - an empty network name or an network name
276          *     not conforming to the P2P Group ID naming rule is not valid.
277          *
278          * @param networkName network name of a group.
279          * @return The builder to facilitate chaining
280          *         {@code builder.setXXX(..).setXXX(..)}.
281          */
setNetworkName(@onNull String networkName)282         public @NonNull Builder setNetworkName(@NonNull String networkName) {
283             if (TextUtils.isEmpty(networkName)) {
284                 throw new IllegalArgumentException(
285                         "network name must be non-empty.");
286             }
287             if (networkName.getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) {
288                 throw new IllegalArgumentException(
289                         "network name exceeds " + MAX_SSID_BYTES + " bytes.");
290             }
291             try {
292                 if (!networkName.matches("^DIRECT-[a-zA-Z0-9]{2}.*")) {
293                     throw new IllegalArgumentException(
294                             "network name must starts with the prefix DIRECT-xy.");
295                 }
296             } catch (PatternSyntaxException e) {
297                 // can never happen (fixed pattern)
298             }
299             mNetworkName = networkName;
300             return this;
301         }
302 
303         /**
304          * Specify the passphrase for creating or joining a group.
305          * <p>
306          * The passphrase must be an ASCII string whose length is between 8
307          * and 63.
308          * <p>
309          *     Must be called - an empty passphrase is not valid.
310          *
311          * @param passphrase the passphrase of a group.
312          * @return The builder to facilitate chaining
313          *         {@code builder.setXXX(..).setXXX(..)}.
314          */
setPassphrase(@onNull String passphrase)315         public @NonNull Builder setPassphrase(@NonNull String passphrase) {
316             if (TextUtils.isEmpty(passphrase)) {
317                 throw new IllegalArgumentException(
318                         "passphrase must be non-empty.");
319             }
320             if (passphrase.length() < 8 || passphrase.length() > 63) {
321                 throw new IllegalArgumentException(
322                         "The length of a passphrase must be between 8 and 63.");
323             }
324             mPassphrase = passphrase;
325             return this;
326         }
327 
328         /**
329          * Specify the band to use for creating the group or joining the group. The band should
330          * be {@link #GROUP_OWNER_BAND_2GHZ}, {@link #GROUP_OWNER_BAND_5GHZ} or
331          * {@link #GROUP_OWNER_BAND_AUTO}.
332          * <p>
333          * When creating a group as Group Owner using {@link
334          * WifiP2pManager#createGroup(WifiP2pManager.Channel,
335          * WifiP2pConfig, WifiP2pManager.ActionListener)},
336          * specifying {@link #GROUP_OWNER_BAND_AUTO} allows the system to pick the operating
337          * frequency from all supported bands.
338          * Specifying {@link #GROUP_OWNER_BAND_2GHZ} or {@link #GROUP_OWNER_BAND_5GHZ}
339          * only allows the system to pick the operating frequency in the specified band.
340          * If the Group Owner cannot create a group in the specified band, the operation will fail.
341          * <p>
342          * When joining a group as Group Client using {@link
343          * WifiP2pManager#connect(WifiP2pManager.Channel, WifiP2pConfig,
344          * WifiP2pManager.ActionListener)},
345          * specifying {@link #GROUP_OWNER_BAND_AUTO} allows the system to scan all supported
346          * frequencies to find the desired group. Specifying {@link #GROUP_OWNER_BAND_2GHZ} or
347          * {@link #GROUP_OWNER_BAND_5GHZ} only allows the system to scan the specified band.
348          * <p>
349          *     {@link #setGroupOperatingBand(int)} and {@link #setGroupOperatingFrequency(int)} are
350          *     mutually exclusive. Setting operating band and frequency both is invalid.
351          * <p>
352          *     Optional. {@link #GROUP_OWNER_BAND_AUTO} by default.
353          *
354          * @param band the operating band of the group.
355          *             This should be one of {@link #GROUP_OWNER_BAND_AUTO},
356          *             {@link #GROUP_OWNER_BAND_2GHZ}, {@link #GROUP_OWNER_BAND_5GHZ}.
357          * @return The builder to facilitate chaining
358          *         {@code builder.setXXX(..).setXXX(..)}.
359          */
setGroupOperatingBand(@roupOperatingBandType int band)360         public @NonNull Builder setGroupOperatingBand(@GroupOperatingBandType int band) {
361             switch (band) {
362                 case GROUP_OWNER_BAND_AUTO:
363                 case GROUP_OWNER_BAND_2GHZ:
364                 case GROUP_OWNER_BAND_5GHZ:
365                     mGroupOperatingBand = band;
366                     break;
367                 default:
368                     throw new IllegalArgumentException(
369                         "Invalid constant for the group operating band!");
370             }
371             return this;
372         }
373 
374         /**
375          * Specify the frequency, in MHz, to use for creating the group or joining the group.
376          * <p>
377          * When creating a group as Group Owner using {@link WifiP2pManager#createGroup(
378          * WifiP2pManager.Channel, WifiP2pConfig, WifiP2pManager.ActionListener)},
379          * specifying a frequency only allows the system to pick the specified frequency.
380          * If the Group Owner cannot create a group at the specified frequency,
381          * the operation will fail.
382          * When not specifying a frequency, it allows the system to pick operating frequency
383          * from all supported bands.
384          * <p>
385          * When joining a group as Group Client using {@link WifiP2pManager#connect(
386          * WifiP2pManager.Channel, WifiP2pConfig, WifiP2pManager.ActionListener)},
387          * specifying a frequency only allows the system to scan the specified frequency.
388          * If the frequency is not supported or invalid, the operation will fail.
389          * When not specifying a frequency, it allows the system to scan all supported
390          * frequencies to find the desired group.
391          * <p>
392          *     {@link #setGroupOperatingBand(int)} and {@link #setGroupOperatingFrequency(int)} are
393          *     mutually exclusive. Setting operating band and frequency both is invalid.
394          * <p>
395          *     Optional. 0 by default.
396          *
397          * @param frequency the operating frequency of the group.
398          * @return The builder to facilitate chaining
399          *         {@code builder.setXXX(..).setXXX(..)}.
400          */
setGroupOperatingFrequency(int frequency)401         public @NonNull Builder setGroupOperatingFrequency(int frequency) {
402             if (frequency < 0) {
403                 throw new IllegalArgumentException(
404                     "Invalid group operating frequency!");
405             }
406             mGroupOperatingFrequency = frequency;
407             return this;
408         }
409 
410         /**
411          * Specify that the group configuration be persisted (i.e. saved).
412          * By default the group configuration will not be saved.
413          * <p>
414          *     Optional. false by default.
415          *
416          * @param persistent is this group persistent group.
417          * @return The builder to facilitate chaining
418          *         {@code builder.setXXX(..).setXXX(..)}.
419          */
enablePersistentMode(boolean persistent)420         public @NonNull Builder enablePersistentMode(boolean persistent) {
421             if (persistent) {
422                 mNetId = WifiP2pGroup.PERSISTENT_NET_ID;
423             } else {
424                 mNetId = WifiP2pGroup.TEMPORARY_NET_ID;
425             }
426             return this;
427         }
428 
429         /**
430          * Build {@link WifiP2pConfig} given the current requests made on the builder.
431          * @return {@link WifiP2pConfig} constructed based on builder method calls.
432          */
build()433         public @NonNull WifiP2pConfig build() {
434             if (TextUtils.isEmpty(mNetworkName)) {
435                 throw new IllegalStateException(
436                         "network name must be non-empty.");
437             }
438             if (TextUtils.isEmpty(mPassphrase)) {
439                 throw new IllegalStateException(
440                         "passphrase must be non-empty.");
441             }
442 
443             if (mGroupOperatingFrequency > 0 && mGroupOperatingBand > 0) {
444                 throw new IllegalStateException(
445                         "Preferred frequency and band are mutually exclusive.");
446             }
447 
448             WifiP2pConfig config = new WifiP2pConfig();
449             config.deviceAddress = mDeviceAddress.toString();
450             config.networkName = mNetworkName;
451             config.passphrase = mPassphrase;
452             config.groupOwnerBand = GROUP_OWNER_BAND_AUTO;
453             if (mGroupOperatingFrequency > 0) {
454                 config.groupOwnerBand = mGroupOperatingFrequency;
455             } else if (mGroupOperatingBand > 0) {
456                 config.groupOwnerBand = mGroupOperatingBand;
457             }
458             config.netId = mNetId;
459             return config;
460         }
461     }
462 }
463