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