/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.net.wifi; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.net.wifi.util.HexEncoding; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CoderResult; import java.nio.charset.CodingErrorAction; import java.nio.charset.StandardCharsets; import java.util.Arrays; /** * Representation of a Wi-Fi Service Set Identifier (SSID). */ public final class WifiSsid implements Parcelable { private final byte[] mBytes; /** * Creates a WifiSsid from the raw bytes. If the byte array is null, creates an empty WifiSsid * object which will return an empty byte array and empty text. * @param bytes the SSID */ private WifiSsid(@Nullable byte[] bytes) { if (bytes == null) { bytes = new byte[0]; } mBytes = bytes; // Duplicate the bytes to #octets for legacy apps. octets.write(bytes, 0, bytes.length); } /** * Create a WifiSsid from the raw bytes. If the byte array is null, return an empty WifiSsid * object which will return an empty byte array and empty text. */ @NonNull public static WifiSsid fromBytes(@Nullable byte[] bytes) { return new WifiSsid(bytes); } /** * Returns the raw byte array representing this SSID. * @return the SSID */ @NonNull public byte[] getBytes() { return mBytes.clone(); } /** * Create a UTF-8 WifiSsid from unquoted plaintext. If the text is null, return an * empty WifiSsid object which will return an empty byte array and empty text. * @hide */ @NonNull public static WifiSsid fromUtf8Text(@Nullable CharSequence utf8Text) { if (utf8Text == null) { return new WifiSsid(null); } return new WifiSsid(utf8Text.toString().getBytes(StandardCharsets.UTF_8)); } /** * If the SSID is encoded with UTF-8, this method returns the decoded SSID as plaintext. * Otherwise, it returns {@code null}. * @return the SSID * @hide */ @Nullable public CharSequence getUtf8Text() { return decodeSsid(mBytes, StandardCharsets.UTF_8); } /** * Create a WifiSsid from a string matching the format of {@link WifiSsid#toString()}. * If the string is null, return an empty WifiSsid object which will return an empty byte array * and empty text. * @throws IllegalArgumentException if the string is unquoted but not hexadecimal, * or if the hexadecimal string is odd-length. * @hide */ @NonNull public static WifiSsid fromString(@Nullable String string) { if (string == null) { return new WifiSsid(null); } final int length = string.length(); if ((length > 1) && (string.charAt(0) == '"') && (string.charAt(length - 1) == '"')) { return new WifiSsid(string.substring(1, length - 1).getBytes(StandardCharsets.UTF_8)); } return new WifiSsid(HexEncoding.decode(string)); } /** * Returns the string representation of the WifiSsid. If the SSID can be decoded as UTF-8, it * will be returned in plain text surrounded by double quotation marks. Otherwise, it is * returned as an unquoted string of hex digits. This format is consistent with * {@link WifiInfo#getSSID()} and {@link WifiConfiguration#SSID}. * * @return SSID as double-quoted plain text from UTF-8 or unquoted hex digits */ @Override @NonNull public String toString() { String utf8String = decodeSsid(mBytes, StandardCharsets.UTF_8); if (TextUtils.isEmpty(utf8String)) { return HexEncoding.encodeToString(mBytes, false /* upperCase */); } return "\"" + utf8String + "\""; } /** * Returns the given SSID bytes as a String decoded using the given Charset. If the bytes cannot * be decoded, then this returns {@code null}. * @param ssidBytes SSID as bytes * @param charset Charset to decode with * @return SSID as string, or {@code null}. */ @Nullable private static String decodeSsid(@NonNull byte[] ssidBytes, @NonNull Charset charset) { CharsetDecoder decoder = charset.newDecoder() .onMalformedInput(CodingErrorAction.REPORT) .onUnmappableCharacter(CodingErrorAction.REPORT); CharBuffer out = CharBuffer.allocate(32); CoderResult result = decoder.decode(ByteBuffer.wrap(ssidBytes), out, true); out.flip(); if (result.isError()) { return null; } return out.toString(); } @Override public boolean equals(Object thatObject) { if (this == thatObject) { return true; } if (!(thatObject instanceof WifiSsid)) { return false; } WifiSsid that = (WifiSsid) thatObject; return Arrays.equals(mBytes, that.mBytes); } @Override public int hashCode() { return Arrays.hashCode(mBytes); } /** Implement the Parcelable interface */ @Override public int describeContents() { return 0; } /** Implement the Parcelable interface */ @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeByteArray(mBytes); } /** Implement the Parcelable interface */ public static final @NonNull Creator CREATOR = new Creator() { @Override public WifiSsid createFromParcel(Parcel in) { return new WifiSsid(in.createByteArray()); } @Override public WifiSsid[] newArray(int size) { return new WifiSsid[size]; } }; /** * Use {@link #getBytes()} instead. * @hide */ // TODO(b/231433398): add maxTargetSdk = Build.VERSION_CODES.S @UnsupportedAppUsage(publicAlternatives = "{@link #getBytes()}") public final ByteArrayOutputStream octets = new ByteArrayOutputStream(32); /** * Use {@link android.net.wifi.WifiManager#UNKNOWN_SSID} instead. * @hide */ // TODO(b/231433398): add maxTargetSdk = Build.VERSION_CODES.S @UnsupportedAppUsage(publicAlternatives = "{@link android.net.wifi.WifiManager#UNKNOWN_SSID}") public static final String NONE = WifiManager.UNKNOWN_SSID; /** * Use {@link #fromBytes(byte[])} instead. * @hide */ // TODO(b/231433398): add maxTargetSdk = Build.VERSION_CODES.S @UnsupportedAppUsage(publicAlternatives = "{@link #fromBytes(byte[])}") public static WifiSsid createFromAsciiEncoded(String asciiEncoded) { return fromUtf8Text(asciiEncoded); } /** * Use {@link #getBytes()} instead. * @hide */ // TODO(b/231433398): add maxTargetSdk = Build.VERSION_CODES.S @UnsupportedAppUsage(publicAlternatives = "{@link #getBytes()}") public byte[] getOctets() { return getBytes(); } }