1 /* 2 * Copyright (C) 2021 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 package com.android.bedstead.dpmwrapper; 17 18 import static com.android.bedstead.dpmwrapper.Utils.EXTRA_ARG_PREFIX; 19 import static com.android.bedstead.dpmwrapper.Utils.VERBOSE; 20 21 import android.annotation.Nullable; 22 import android.content.Intent; 23 import android.os.Bundle; 24 import android.os.CpuUsageInfo; 25 import android.os.Parcelable; 26 import android.util.ArraySet; 27 import android.util.Log; 28 29 import java.io.Serializable; 30 import java.security.PrivateKey; 31 import java.security.cert.Certificate; 32 import java.util.ArrayList; 33 import java.util.List; 34 import java.util.Set; 35 36 final class DataFormatter { 37 38 private static final String TAG = DataFormatter.class.getSimpleName(); 39 40 // NOTE: Bundle has a putObject() method that would make it much easier to marshal the args, 41 // but unfortunately there is no Intent.putObjectExtra() method (and intent.getBundle() returns 42 // a copy, so we need to explicitly marshal any supported type). 43 private static final String TYPE_BOOLEAN = "boolean"; 44 private static final String TYPE_INT = "int"; 45 private static final String TYPE_LONG = "long"; 46 private static final String TYPE_BYTE_ARRAY = "byte_array"; 47 private static final String TYPE_FLOAT_ARRAY = "float_array"; 48 private static final String TYPE_STRING_OR_CHAR_SEQUENCE = "string"; 49 private static final String TYPE_PARCELABLE = "parcelable"; 50 private static final String TYPE_SERIALIZABLE = "serializable"; 51 private static final String TYPE_ARRAY_LIST_STRING = "array_list_string"; 52 private static final String TYPE_ARRAY_LIST_PARCELABLE = "array_list_parcelable"; 53 // NOTE: the value of a TYPE_ARRAY_LIST_BYTE_ARRAY is its length - the individual elements 54 // are contained on separate extras, one per index, whose name is defined by 55 // getExtraNameForArrayListElement() 56 private static final String TYPE_ARRAY_LIST_BYTE_ARRAY = "array_list_byte_array"; 57 private static final String TYPE_SET_STRING = "set_string"; 58 // Must handle each array of parcelable subclass , as they need to be explicitly converted 59 private static final String TYPE_CPU_USAFE_INFO_ARRAY = "cpu_usage_info_array"; 60 private static final String TYPE_CERTIFICATE = "certificate"; 61 private static final String TYPE_PRIVATE_KEY = "private_key"; 62 63 // Used when a method is called passing a null argument - the proper method will have to be 64 // infered using findMethod() 65 private static final String TYPE_NULL = "null"; 66 addArg(Intent intent, Object[] args, int index)67 static void addArg(Intent intent, Object[] args, int index) { 68 Object value = args[index]; 69 String extraTypeName = getArgExtraTypeName(index); 70 String extraValueName = getArgExtraValueName(index); 71 if (VERBOSE) { 72 Log.v(TAG, "addArg(" + index + "): typeName= " + extraTypeName 73 + ", valueName= " + extraValueName); 74 } 75 if (value == null) { 76 logMarshalling("Adding Null", index, extraTypeName, TYPE_NULL, extraValueName, value); 77 intent.putExtra(extraTypeName, TYPE_NULL); 78 return; 79 80 } 81 if ((value instanceof Boolean)) { 82 logMarshalling("Adding Boolean", index, extraTypeName, TYPE_BOOLEAN, extraValueName, 83 value); 84 intent.putExtra(extraTypeName, TYPE_BOOLEAN); 85 intent.putExtra(extraValueName, ((Boolean) value).booleanValue()); 86 return; 87 } 88 if ((value instanceof Integer)) { 89 logMarshalling("Adding Integer", index, extraTypeName, TYPE_INT, extraValueName, value); 90 intent.putExtra(extraTypeName, TYPE_INT); 91 intent.putExtra(extraValueName, ((Integer) value).intValue()); 92 return; 93 } 94 if ((value instanceof Long)) { 95 logMarshalling("Adding Long", index, extraTypeName, TYPE_LONG, extraValueName, value); 96 intent.putExtra(extraTypeName, TYPE_LONG); 97 intent.putExtra(extraValueName, ((Long) value).longValue()); 98 return; 99 } 100 if ((value instanceof byte[])) { 101 logMarshalling("Adding byte[]", index, extraTypeName, TYPE_BYTE_ARRAY, extraValueName, 102 value); 103 intent.putExtra(extraTypeName, TYPE_BYTE_ARRAY); 104 intent.putExtra(extraValueName, (byte[]) value); 105 return; 106 } 107 if ((value instanceof float[])) { 108 logMarshalling("Adding float[]", index, extraTypeName, TYPE_FLOAT_ARRAY, extraValueName, 109 value); 110 intent.putExtra(extraTypeName, TYPE_FLOAT_ARRAY); 111 intent.putExtra(extraValueName, (float[]) value); 112 return; 113 } 114 if ((value instanceof CpuUsageInfo[])) { 115 logMarshalling("Adding CpuUsageInfo[]", index, extraTypeName, 116 TYPE_CPU_USAFE_INFO_ARRAY, extraValueName, value); 117 intent.putExtra(extraTypeName, TYPE_CPU_USAFE_INFO_ARRAY); 118 intent.putExtra(extraValueName, (CpuUsageInfo[]) value); 119 return; 120 } 121 if ((value instanceof CharSequence)) { 122 logMarshalling("Adding CharSequence", index, extraTypeName, 123 TYPE_STRING_OR_CHAR_SEQUENCE, extraValueName, value); 124 intent.putExtra(extraTypeName, TYPE_STRING_OR_CHAR_SEQUENCE); 125 intent.putExtra(extraValueName, (CharSequence) value); 126 return; 127 } 128 if (value instanceof PrivateKey) { 129 if (!(value instanceof Parcelable)) { 130 throw new IllegalArgumentException("PrivateKey is not Parcelable: " + value); 131 } 132 logMarshalling("Adding PrivateKey", index, extraTypeName, TYPE_PRIVATE_KEY, 133 extraValueName, value); 134 intent.putExtra(extraTypeName, TYPE_PRIVATE_KEY); 135 intent.putExtra(extraValueName, (Parcelable) value); 136 return; 137 } 138 if ((value instanceof Parcelable)) { 139 logMarshalling("Adding Parcelable", index, extraTypeName, TYPE_PARCELABLE, 140 extraValueName, value); 141 intent.putExtra(extraTypeName, TYPE_PARCELABLE); 142 intent.putExtra(extraValueName, (Parcelable) value); 143 return; 144 } 145 146 if (value instanceof Certificate) { 147 logMarshalling("Adding Certificate", index, extraTypeName, TYPE_CERTIFICATE, 148 extraValueName, value); 149 intent.putExtra(extraTypeName, TYPE_CERTIFICATE); 150 intent.putExtra(extraValueName, (Serializable) value); 151 return; 152 } 153 154 if ((value instanceof List<?>)) { 155 List<?> list = (List<?>) value; 156 157 String type = null; 158 if (list.isEmpty()) { 159 Log.w(TAG, "Empty list at index " + index + "; assuming it's List<String>"); 160 type = TYPE_ARRAY_LIST_STRING; 161 } else { 162 Object firstItem = list.get(0); 163 if (firstItem instanceof String) { 164 type = TYPE_ARRAY_LIST_STRING; 165 } else if (firstItem instanceof Parcelable) { 166 type = TYPE_ARRAY_LIST_PARCELABLE; 167 } else if (firstItem instanceof byte[]) { 168 type = TYPE_ARRAY_LIST_BYTE_ARRAY; 169 } else { 170 throw new IllegalArgumentException("Unsupported List type at index " + index 171 + ": " + firstItem); 172 } 173 } 174 175 logMarshalling("Adding " + type, index, extraTypeName, type, extraValueName, value); 176 intent.putExtra(extraTypeName, type); 177 switch (type) { 178 case TYPE_ARRAY_LIST_STRING: 179 @SuppressWarnings("unchecked") 180 ArrayList<String> arrayListString = (value instanceof ArrayList) 181 ? (ArrayList<String>) list 182 : new ArrayList<>((List<String>) list); 183 intent.putStringArrayListExtra(extraValueName, arrayListString); 184 break; 185 case TYPE_ARRAY_LIST_PARCELABLE: 186 @SuppressWarnings("unchecked") 187 ArrayList<Parcelable> arrayListParcelable = (value instanceof ArrayList) 188 ? (ArrayList<Parcelable>) list 189 : new ArrayList<>((List<Parcelable>) list); 190 intent.putParcelableArrayListExtra(extraValueName, arrayListParcelable); 191 break; 192 case TYPE_ARRAY_LIST_BYTE_ARRAY: 193 @SuppressWarnings("unchecked") 194 ArrayList<byte[]> arrayListByteArray = (value instanceof ArrayList) 195 ? (ArrayList<byte[]>) list 196 : new ArrayList<>((List<byte[]>) list); 197 int listSize = arrayListByteArray.size(); 198 intent.putExtra(extraValueName, listSize); 199 for (int i = 0; i < listSize; i++) { 200 intent.putExtra(getExtraNameForArrayListElement(extraValueName, i), 201 arrayListByteArray.get(i)); 202 } 203 break; 204 default: 205 // should never happen because type is checked above 206 throw new AssertionError("invalid type conversion: " + type); 207 } 208 return; 209 } 210 211 // TODO(b/176993670): ArraySet<> is encapsulate as ArrayList<>, so most of the code below 212 // could be reused (right now it was copy-and-paste from ArrayList<>, minus the Parcelable 213 // part. 214 if ((value instanceof Set<?>)) { 215 Set<?> set = (Set<?>) value; 216 217 String type = null; 218 if (set.isEmpty()) { 219 Log.w(TAG, "Empty set at index " + index + "; assuming it's Set<String>"); 220 type = TYPE_SET_STRING; 221 } else { 222 Object firstItem = set.iterator().next(); 223 if (firstItem instanceof String) { 224 type = TYPE_SET_STRING; 225 } else { 226 throw new IllegalArgumentException("Unsupported Set type at index " 227 + index + ": " + firstItem); 228 } 229 } 230 231 logMarshalling("Adding " + type, index, extraTypeName, type, extraValueName, value); 232 intent.putExtra(extraTypeName, type); 233 switch (type) { 234 case TYPE_SET_STRING: 235 @SuppressWarnings("unchecked") 236 Set<String> stringSet = (Set<String>) value; 237 intent.putStringArrayListExtra(extraValueName, new ArrayList<>(stringSet)); 238 break; 239 default: 240 // should never happen because type is checked above 241 throw new AssertionError("invalid type conversion: " + type); 242 } 243 return; 244 } 245 246 if ((value instanceof Serializable)) { 247 logMarshalling("Adding Serializable", index, extraTypeName, TYPE_SERIALIZABLE, 248 extraValueName, value); 249 intent.putExtra(extraTypeName, TYPE_SERIALIZABLE); 250 intent.putExtra(extraValueName, (Serializable) value); 251 return; 252 } 253 254 throw new IllegalArgumentException("Unsupported value type at index " + index + ": " 255 + (value == null ? "null" : value.getClass())); 256 } 257 getExtraNameForArrayListElement(String baseExtraName, int index)258 private static String getExtraNameForArrayListElement(String baseExtraName, int index) { 259 return baseExtraName + "_" + index; 260 } 261 getArg(Bundle extras, Object[] args, @Nullable Class<?>[] parameterTypes, int index)262 static void getArg(Bundle extras, Object[] args, @Nullable Class<?>[] parameterTypes, 263 int index) { 264 String extraTypeName = getArgExtraTypeName(index); 265 String extraValueName = getArgExtraValueName(index); 266 String type = extras.getString(extraTypeName); 267 if (VERBOSE) { 268 Log.v(TAG, "getArg(" + index + "): typeName= " + extraTypeName + ", type=" + type 269 + ", valueName= " + extraValueName); 270 } 271 Object value = null; 272 switch (type) { 273 case TYPE_NULL: 274 logMarshalling("Got null", index, extraTypeName, type, extraValueName, value); 275 break; 276 case TYPE_SET_STRING: 277 @SuppressWarnings("unchecked") 278 ArrayList<String> list = (ArrayList<String>) extras.get(extraValueName); 279 value = new ArraySet<String>(list); 280 logMarshalling("Got ArraySet<String>", index, extraTypeName, type, extraValueName, 281 value); 282 break; 283 case TYPE_CPU_USAFE_INFO_ARRAY: 284 Parcelable[] raw = (Parcelable[]) extras.get(extraValueName); 285 CpuUsageInfo[] cast = new CpuUsageInfo[raw.length]; 286 for (int i = 0; i < raw.length; i++) { 287 cast[i] = (CpuUsageInfo) raw[i]; 288 } 289 value = cast; 290 logMarshalling("Got CpuUsageInfo[]", index, extraTypeName, type, extraValueName, 291 value); 292 break; 293 case TYPE_ARRAY_LIST_BYTE_ARRAY: 294 int size = extras.getInt(extraValueName); 295 ArrayList<byte[]> array = new ArrayList<>(size); 296 for (int i = 0; i < size; i++) { 297 String extraName = getExtraNameForArrayListElement(extraValueName, i); 298 array.add(extras.getByteArray(extraName)); 299 } 300 value = array; 301 break; 302 case TYPE_ARRAY_LIST_STRING: 303 case TYPE_ARRAY_LIST_PARCELABLE: 304 case TYPE_BYTE_ARRAY: 305 case TYPE_FLOAT_ARRAY: 306 case TYPE_BOOLEAN: 307 case TYPE_INT: 308 case TYPE_LONG: 309 case TYPE_STRING_OR_CHAR_SEQUENCE: 310 case TYPE_PARCELABLE: 311 case TYPE_SERIALIZABLE: 312 case TYPE_CERTIFICATE: 313 case TYPE_PRIVATE_KEY: 314 value = extras.get(extraValueName); 315 logMarshalling("Got generic", index, extraTypeName, type, extraValueName, value); 316 break; 317 default: 318 throw new IllegalArgumentException("Unsupported value type at index " + index + ": " 319 + extraTypeName); 320 } 321 if (parameterTypes != null) { 322 Class<?> parameterType = null; 323 // Must convert special types (like primitive to Object, generic list to list, etc...), 324 // but not those that can be inferred from getClass() (like String or array) 325 switch (type) { 326 case TYPE_NULL: 327 break; 328 case TYPE_BOOLEAN: 329 parameterType = boolean.class; 330 break; 331 case TYPE_INT: 332 parameterType = int.class; 333 break; 334 case TYPE_LONG: 335 parameterType = long.class; 336 break; 337 case TYPE_STRING_OR_CHAR_SEQUENCE: 338 // A String is a CharSequence, but most methods take String, so we're assuming 339 // a string and handle the exceptional cases on findMethod() 340 parameterType = String.class; 341 break; 342 case TYPE_ARRAY_LIST_STRING: 343 parameterType = List.class; 344 break; 345 case TYPE_SET_STRING: 346 parameterType = Set.class; 347 break; 348 case TYPE_PRIVATE_KEY: 349 parameterType = PrivateKey.class; 350 break; 351 case TYPE_CERTIFICATE: 352 parameterType = Certificate.class; 353 break; 354 default: 355 parameterType = value.getClass(); 356 } 357 parameterTypes[index] = parameterType; 358 } 359 args[index] = value; 360 } 361 getArgExtraTypeName(int index)362 static String getArgExtraTypeName(int index) { 363 return EXTRA_ARG_PREFIX + index + "_type"; 364 } 365 getArgExtraValueName(int index)366 static String getArgExtraValueName(int index) { 367 return EXTRA_ARG_PREFIX + index + "_value"; 368 } 369 logMarshalling(String operation, int index, String typeName, String type, String valueName, Object value)370 private static void logMarshalling(String operation, int index, String typeName, 371 String type, String valueName, Object value) { 372 if (VERBOSE) { 373 Log.v(TAG, operation + " on " + index + ": typeName=" + typeName + ", type=" + type 374 + ", valueName=" + valueName + ", value=" + value); 375 } 376 } 377 DataFormatter()378 private DataFormatter() { 379 throw new UnsupportedOperationException("contains only static methods"); 380 } 381 } 382