1 /* 2 * Copyright (C) 2012 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; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.compat.annotation.UnsupportedAppUsage; 22 import android.net.wifi.util.HexEncoding; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 import android.text.TextUtils; 26 27 import java.io.ByteArrayOutputStream; 28 import java.nio.ByteBuffer; 29 import java.nio.CharBuffer; 30 import java.nio.charset.Charset; 31 import java.nio.charset.CharsetDecoder; 32 import java.nio.charset.CoderResult; 33 import java.nio.charset.CodingErrorAction; 34 import java.nio.charset.StandardCharsets; 35 import java.util.Arrays; 36 37 /** 38 * Representation of a Wi-Fi Service Set Identifier (SSID). 39 */ 40 public final class WifiSsid implements Parcelable { 41 private final byte[] mBytes; 42 43 /** 44 * Creates a WifiSsid from the raw bytes. If the byte array is null, creates an empty WifiSsid 45 * object which will return an empty byte array and empty text. 46 * @param bytes the SSID 47 */ WifiSsid(@ullable byte[] bytes)48 private WifiSsid(@Nullable byte[] bytes) { 49 if (bytes == null) { 50 bytes = new byte[0]; 51 } 52 mBytes = bytes; 53 // Duplicate the bytes to #octets for legacy apps. 54 octets.write(bytes, 0, bytes.length); 55 } 56 57 /** 58 * Create a WifiSsid from the raw bytes. If the byte array is null, return an empty WifiSsid 59 * object which will return an empty byte array and empty text. 60 */ 61 @NonNull fromBytes(@ullable byte[] bytes)62 public static WifiSsid fromBytes(@Nullable byte[] bytes) { 63 return new WifiSsid(bytes); 64 } 65 66 /** 67 * Returns the raw byte array representing this SSID. 68 * @return the SSID 69 */ 70 @NonNull getBytes()71 public byte[] getBytes() { 72 return mBytes.clone(); 73 } 74 75 /** 76 * Create a UTF-8 WifiSsid from unquoted plaintext. If the text is null, return an 77 * empty WifiSsid object which will return an empty byte array and empty text. 78 * @hide 79 */ 80 @NonNull fromUtf8Text(@ullable CharSequence utf8Text)81 public static WifiSsid fromUtf8Text(@Nullable CharSequence utf8Text) { 82 if (utf8Text == null) { 83 return new WifiSsid(null); 84 } 85 return new WifiSsid(utf8Text.toString().getBytes(StandardCharsets.UTF_8)); 86 } 87 88 /** 89 * If the SSID is encoded with UTF-8, this method returns the decoded SSID as plaintext. 90 * Otherwise, it returns {@code null}. 91 * @return the SSID 92 * @hide 93 */ 94 @Nullable getUtf8Text()95 public CharSequence getUtf8Text() { 96 return decodeSsid(mBytes, StandardCharsets.UTF_8); 97 } 98 99 /** 100 * Create a WifiSsid from a string matching the format of {@link WifiSsid#toString()}. 101 * If the string is null, return an empty WifiSsid object which will return an empty byte array 102 * and empty text. 103 * @throws IllegalArgumentException if the string is unquoted but not hexadecimal, 104 * or if the hexadecimal string is odd-length. 105 * @hide 106 */ 107 @NonNull fromString(@ullable String string)108 public static WifiSsid fromString(@Nullable String string) { 109 if (string == null) { 110 return new WifiSsid(null); 111 } 112 final int length = string.length(); 113 if ((length > 1) && (string.charAt(0) == '"') && (string.charAt(length - 1) == '"')) { 114 return new WifiSsid(string.substring(1, length - 1).getBytes(StandardCharsets.UTF_8)); 115 } 116 return new WifiSsid(HexEncoding.decode(string)); 117 } 118 119 /** 120 * Returns the string representation of the WifiSsid. If the SSID can be decoded as UTF-8, it 121 * will be returned in plain text surrounded by double quotation marks. Otherwise, it is 122 * returned as an unquoted string of hex digits. This format is consistent with 123 * {@link WifiInfo#getSSID()} and {@link WifiConfiguration#SSID}. 124 * 125 * @return SSID as double-quoted plain text from UTF-8 or unquoted hex digits 126 */ 127 @Override 128 @NonNull toString()129 public String toString() { 130 String utf8String = decodeSsid(mBytes, StandardCharsets.UTF_8); 131 if (TextUtils.isEmpty(utf8String)) { 132 return HexEncoding.encodeToString(mBytes, false /* upperCase */); 133 } 134 return "\"" + utf8String + "\""; 135 } 136 137 /** 138 * Returns the given SSID bytes as a String decoded using the given Charset. If the bytes cannot 139 * be decoded, then this returns {@code null}. 140 * @param ssidBytes SSID as bytes 141 * @param charset Charset to decode with 142 * @return SSID as string, or {@code null}. 143 */ 144 @Nullable decodeSsid(@onNull byte[] ssidBytes, @NonNull Charset charset)145 private static String decodeSsid(@NonNull byte[] ssidBytes, @NonNull Charset charset) { 146 CharsetDecoder decoder = charset.newDecoder() 147 .onMalformedInput(CodingErrorAction.REPORT) 148 .onUnmappableCharacter(CodingErrorAction.REPORT); 149 CharBuffer out = CharBuffer.allocate(32); 150 CoderResult result = decoder.decode(ByteBuffer.wrap(ssidBytes), out, true); 151 out.flip(); 152 if (result.isError()) { 153 return null; 154 } 155 return out.toString(); 156 } 157 158 @Override equals(Object thatObject)159 public boolean equals(Object thatObject) { 160 if (this == thatObject) { 161 return true; 162 } 163 if (!(thatObject instanceof WifiSsid)) { 164 return false; 165 } 166 WifiSsid that = (WifiSsid) thatObject; 167 return Arrays.equals(mBytes, that.mBytes); 168 } 169 170 @Override hashCode()171 public int hashCode() { 172 return Arrays.hashCode(mBytes); 173 } 174 175 /** Implement the Parcelable interface */ 176 @Override describeContents()177 public int describeContents() { 178 return 0; 179 } 180 181 /** Implement the Parcelable interface */ 182 @Override writeToParcel(@onNull Parcel dest, int flags)183 public void writeToParcel(@NonNull Parcel dest, int flags) { 184 dest.writeByteArray(mBytes); 185 } 186 187 /** Implement the Parcelable interface */ 188 public static final @NonNull Creator<WifiSsid> CREATOR = 189 new Creator<WifiSsid>() { 190 @Override 191 public WifiSsid createFromParcel(Parcel in) { 192 return new WifiSsid(in.createByteArray()); 193 } 194 195 @Override 196 public WifiSsid[] newArray(int size) { 197 return new WifiSsid[size]; 198 } 199 }; 200 201 /** 202 * Use {@link #getBytes()} instead. 203 * @hide 204 */ 205 // TODO(b/231433398): add maxTargetSdk = Build.VERSION_CODES.S 206 @UnsupportedAppUsage(publicAlternatives = "{@link #getBytes()}") 207 public final ByteArrayOutputStream octets = new ByteArrayOutputStream(32); 208 209 /** 210 * Use {@link android.net.wifi.WifiManager#UNKNOWN_SSID} instead. 211 * @hide 212 */ 213 // TODO(b/231433398): add maxTargetSdk = Build.VERSION_CODES.S 214 @UnsupportedAppUsage(publicAlternatives = "{@link android.net.wifi.WifiManager#UNKNOWN_SSID}") 215 public static final String NONE = WifiManager.UNKNOWN_SSID; 216 217 /** 218 * Use {@link #fromBytes(byte[])} instead. 219 * @hide 220 */ 221 // TODO(b/231433398): add maxTargetSdk = Build.VERSION_CODES.S 222 @UnsupportedAppUsage(publicAlternatives = "{@link #fromBytes(byte[])}") createFromAsciiEncoded(String asciiEncoded)223 public static WifiSsid createFromAsciiEncoded(String asciiEncoded) { 224 return fromUtf8Text(asciiEncoded); 225 } 226 227 /** 228 * Use {@link #getBytes()} instead. 229 * @hide 230 */ 231 // TODO(b/231433398): add maxTargetSdk = Build.VERSION_CODES.S 232 @UnsupportedAppUsage(publicAlternatives = "{@link #getBytes()}") getOctets()233 public byte[] getOctets() { 234 return getBytes(); 235 } 236 } 237