1 /* 2 * Copyright (C) 2017 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.google.android.mobly.snippet.bundled.utils; 18 19 import static java.nio.charset.StandardCharsets.UTF_8; 20 21 import android.annotation.TargetApi; 22 import android.bluetooth.BluetoothDevice; 23 import android.bluetooth.BluetoothGatt; 24 import android.bluetooth.BluetoothGattCharacteristic; 25 import android.bluetooth.BluetoothGattService; 26 import android.bluetooth.le.AdvertiseSettings; 27 import android.bluetooth.le.ScanRecord; 28 import android.net.DhcpInfo; 29 import android.net.wifi.SupplicantState; 30 import android.net.wifi.WifiConfiguration; 31 import android.net.wifi.WifiInfo; 32 import android.os.Build; 33 import android.os.Bundle; 34 import android.os.ParcelUuid; 35 import android.util.Base64; 36 import android.util.SparseArray; 37 import com.google.gson.Gson; 38 import com.google.gson.GsonBuilder; 39 import java.lang.reflect.Modifier; 40 import java.net.InetAddress; 41 import java.net.UnknownHostException; 42 import java.util.ArrayList; 43 import java.util.Collection; 44 import org.json.JSONException; 45 import org.json.JSONObject; 46 47 /** 48 * A collection of methods used to serialize data types defined in Android API into JSON strings. 49 */ 50 public class JsonSerializer { 51 private static final Gson gson = 52 new GsonBuilder() 53 .serializeNulls() 54 .excludeFieldsWithModifiers(Modifier.STATIC) 55 .enableComplexMapKeySerialization() 56 .disableInnerClassSerialization() 57 .create(); 58 59 /** 60 * Remove the extra quotation marks from the beginning and the end of a string. 61 * 62 * <p>This is useful for strings like the SSID field of Android's Wi-Fi configuration. 63 * 64 * @param originalString 65 */ trimQuotationMarks(String originalString)66 public static String trimQuotationMarks(String originalString) { 67 String result = originalString; 68 if (originalString.length() > 2 69 && originalString.charAt(0) == '"' 70 && originalString.charAt(originalString.length() - 1) == '"') { 71 result = originalString.substring(1, originalString.length() - 1); 72 } 73 return result; 74 } 75 toJson(Object object)76 public JSONObject toJson(Object object) throws JSONException { 77 if (object instanceof DhcpInfo) { 78 return serializeDhcpInfo((DhcpInfo) object); 79 } else if (object instanceof WifiConfiguration) { 80 return serializeWifiConfiguration((WifiConfiguration) object); 81 } else if (object instanceof WifiInfo) { 82 return serializeWifiInfo((WifiInfo) object); 83 } 84 return defaultSerialization(object); 85 } 86 87 /** 88 * By default, we rely on Gson to do the right job. 89 * 90 * @param data An object to serialize 91 * @return A JSONObject that has the info of the serialized data object. 92 * @throws JSONException 93 */ defaultSerialization(Object data)94 private JSONObject defaultSerialization(Object data) throws JSONException { 95 return new JSONObject(gson.toJson(data)); 96 } 97 serializeDhcpInfo(DhcpInfo data)98 private JSONObject serializeDhcpInfo(DhcpInfo data) throws JSONException { 99 JSONObject result = new JSONObject(gson.toJson(data)); 100 int ipAddress = data.ipAddress; 101 byte[] addressBytes = { 102 (byte) (0xff & ipAddress), 103 (byte) (0xff & (ipAddress >> 8)), 104 (byte) (0xff & (ipAddress >> 16)), 105 (byte) (0xff & (ipAddress >> 24)) 106 }; 107 try { 108 String addressString = InetAddress.getByAddress(addressBytes).toString(); 109 result.put("IpAddress", addressString); 110 } catch (UnknownHostException e) { 111 result.put("IpAddress", ipAddress); 112 } 113 return result; 114 } 115 serializeWifiConfiguration(WifiConfiguration data)116 private JSONObject serializeWifiConfiguration(WifiConfiguration data) throws JSONException { 117 JSONObject result = new JSONObject(gson.toJson(data)); 118 result.put("Status", WifiConfiguration.Status.strings[data.status]); 119 result.put("SSID", trimQuotationMarks(data.SSID)); 120 return result; 121 } 122 serializeWifiInfo(WifiInfo data)123 private JSONObject serializeWifiInfo(WifiInfo data) throws JSONException { 124 JSONObject result = new JSONObject(gson.toJson(data)); 125 result.put("SSID", trimQuotationMarks(data.getSSID())); 126 for (SupplicantState state : SupplicantState.values()) { 127 if (data.getSupplicantState().equals(state)) { 128 result.put("SupplicantState", state.name()); 129 } 130 } 131 return result; 132 } 133 serializeBluetoothDevice(BluetoothDevice data)134 public static Bundle serializeBluetoothDevice(BluetoothDevice data) { 135 Bundle result = new Bundle(); 136 result.putString("Address", data.getAddress()); 137 final String bondState = 138 MbsEnums.BLUETOOTH_DEVICE_BOND_STATE.getString(data.getBondState()); 139 result.putString("BondState", bondState); 140 result.putString("Name", data.getName()); 141 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 142 String deviceType = MbsEnums.BLUETOOTH_DEVICE_TYPE.getString(data.getType()); 143 result.putString("DeviceType", deviceType); 144 ParcelUuid[] parcelUuids = data.getUuids(); 145 if (parcelUuids != null) { 146 ArrayList<String> uuidStrings = new ArrayList<>(parcelUuids.length); 147 for (ParcelUuid parcelUuid : parcelUuids) { 148 uuidStrings.add(parcelUuid.getUuid().toString()); 149 } 150 result.putStringArrayList("UUIDs", uuidStrings); 151 } 152 } 153 return result; 154 } 155 serializeBluetoothDeviceList( Collection<BluetoothDevice> bluetoothDevices)156 public ArrayList<Bundle> serializeBluetoothDeviceList( 157 Collection<BluetoothDevice> bluetoothDevices) { 158 ArrayList<Bundle> results = new ArrayList<>(); 159 for (BluetoothDevice device : bluetoothDevices) { 160 results.add(serializeBluetoothDevice(device)); 161 } 162 return results; 163 } 164 165 @TargetApi(Build.VERSION_CODES.LOLLIPOP) serializeBleScanResult(android.bluetooth.le.ScanResult scanResult)166 public Bundle serializeBleScanResult(android.bluetooth.le.ScanResult scanResult) { 167 Bundle result = new Bundle(); 168 result.putBundle("Device", serializeBluetoothDevice(scanResult.getDevice())); 169 result.putInt("Rssi", scanResult.getRssi()); 170 result.putBundle("ScanRecord", serializeBleScanRecord(scanResult.getScanRecord())); 171 result.putLong("TimestampNanos", scanResult.getTimestampNanos()); 172 return result; 173 } 174 175 /** 176 * Serialize ScanRecord for Bluetooth LE. 177 * 178 * <p>Not all fields are serialized here. Will add more as we need. 179 * 180 * <pre>The returned {@link Bundle} has the following info: 181 * "DeviceName", String 182 * "TxPowerLevel", String 183 * </pre> 184 * 185 * @param record A {@link ScanRecord} object. 186 * @return A {@link Bundle} object. 187 */ 188 @TargetApi(Build.VERSION_CODES.LOLLIPOP) serializeBleScanRecord(ScanRecord record)189 private Bundle serializeBleScanRecord(ScanRecord record) { 190 Bundle result = new Bundle(); 191 result.putString("DeviceName", record.getDeviceName()); 192 result.putInt("TxPowerLevel", record.getTxPowerLevel()); 193 result.putParcelableArrayList("Services", serializeBleScanServices(record)); 194 result.putBundle( 195 "manufacturerSpecificData", serializeBleScanManufacturerSpecificData(record)); 196 return result; 197 } 198 199 /** Serialize manufacturer specific data from ScanRecord for Bluetooth LE. */ 200 @TargetApi(Build.VERSION_CODES.LOLLIPOP) serializeBleScanServices(ScanRecord record)201 private ArrayList<Bundle> serializeBleScanServices(ScanRecord record) { 202 ArrayList<Bundle> result = new ArrayList<>(); 203 if (record.getServiceUuids() != null) { 204 for (ParcelUuid uuid : record.getServiceUuids()) { 205 Bundle service = new Bundle(); 206 service.putString("UUID", uuid.getUuid().toString()); 207 if (record.getServiceData(uuid) != null) { 208 service.putString( 209 "Data", 210 new String(Base64.encode(record.getServiceData(uuid), Base64.NO_WRAP), 211 UTF_8)); 212 } else { 213 service.putString("Data", ""); 214 } 215 result.add(service); 216 } 217 } 218 return result; 219 } 220 221 /** Serialize manufacturer specific data from ScanRecord for Bluetooth LE. */ 222 @TargetApi(Build.VERSION_CODES.LOLLIPOP) serializeBleScanManufacturerSpecificData(ScanRecord record)223 private Bundle serializeBleScanManufacturerSpecificData(ScanRecord record) { 224 Bundle result = new Bundle(); 225 SparseArray<byte[]> sparseArray = record.getManufacturerSpecificData(); 226 for (int i = 0; i < sparseArray.size(); i++) { 227 int key = sparseArray.keyAt(i); 228 result.putByteArray(String.valueOf(key), sparseArray.get(key)); 229 } 230 return result; 231 } 232 233 @TargetApi(Build.VERSION_CODES.LOLLIPOP) serializeBleAdvertisingSettings(AdvertiseSettings advertiseSettings)234 public static Bundle serializeBleAdvertisingSettings(AdvertiseSettings advertiseSettings) { 235 Bundle result = new Bundle(); 236 result.putString( 237 "TxPowerLevel", 238 MbsEnums.BLE_ADVERTISE_TX_POWER.getString(advertiseSettings.getTxPowerLevel())); 239 result.putString( 240 "Mode", MbsEnums.BLE_ADVERTISE_MODE.getString(advertiseSettings.getMode())); 241 result.putInt("Timeout", advertiseSettings.getTimeout()); 242 result.putBoolean("IsConnectable", advertiseSettings.isConnectable()); 243 return result; 244 } 245 246 @TargetApi(Build.VERSION_CODES.LOLLIPOP) serializeBluetoothGatt(BluetoothGatt gatt)247 public static Bundle serializeBluetoothGatt(BluetoothGatt gatt) { 248 Bundle result = new Bundle(); 249 ArrayList<Bundle> services = new ArrayList<>(); 250 for (BluetoothGattService service : gatt.getServices()) { 251 services.add(JsonSerializer.serializeBluetoothGattService(service)); 252 } 253 result.putParcelableArrayList("Services", services); 254 result.putBundle("Device", JsonSerializer.serializeBluetoothDevice(gatt.getDevice())); 255 return result; 256 } 257 258 @TargetApi(Build.VERSION_CODES.LOLLIPOP) serializeBluetoothGattService(BluetoothGattService service)259 public static Bundle serializeBluetoothGattService(BluetoothGattService service) { 260 Bundle result = new Bundle(); 261 result.putString("UUID", service.getUuid().toString()); 262 result.putString("Type", MbsEnums.BLE_SERVICE_TYPE.getString(service.getType())); 263 ArrayList<Bundle> characteristics = new ArrayList<>(); 264 for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) { 265 characteristics.add(serializeBluetoothGattCharacteristic(characteristic)); 266 } 267 result.putParcelableArrayList("Characteristics", characteristics); 268 return result; 269 } 270 271 @TargetApi(Build.VERSION_CODES.LOLLIPOP) serializeBluetoothGattCharacteristic( BluetoothGattCharacteristic characteristic)272 public static Bundle serializeBluetoothGattCharacteristic( 273 BluetoothGattCharacteristic characteristic) { 274 Bundle result = new Bundle(); 275 result.putString("UUID", characteristic.getUuid().toString()); 276 result.putString( 277 "Property", MbsEnums.BLE_PROPERTY_TYPE.getString(characteristic.getProperties())); 278 result.putString( 279 "Permission", 280 MbsEnums.BLE_PERMISSION_TYPE.getString(characteristic.getPermissions())); 281 return result; 282 } 283 } 284