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