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 17 package com.android.car.internal; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.car.builtin.os.SharedMemoryHelper; 22 import android.os.Parcel; 23 import android.os.ParcelFileDescriptor; 24 import android.os.Parcelable; 25 import android.os.SharedMemory; 26 import android.util.Slog; 27 28 import java.io.IOException; 29 import java.lang.ref.WeakReference; 30 import java.lang.reflect.Field; 31 import java.lang.reflect.Method; 32 import java.util.concurrent.Callable; 33 34 /** 35 * Utility to pass any {@code Parcelable} through binder with automatic conversion into shared 36 * memory when data size is too big. 37 * 38 * <p> This class can work by itself but child class can be useful to use a custom class to 39 * interface with C++ world. For such usage, child class will only add its own {@code CREATOR} impl 40 * and a constructor taking {@code Parcel in}. 41 * <p>For stable AIDL, this class also provides two methods for serialization {@link 42 * #toLargeParcelable(Parcelable)} and deserialization 43 * {@link #reconstructStableAIDLParcelable(Parcelable, boolean)}. Plz check included test for the 44 * usage. 45 * 46 * @hide 47 */ 48 public class LargeParcelable extends LargeParcelableBase { 49 /** 50 * Stable AIDL Parcelable should have this member with {@code ParcelFileDescriptor} to support 51 * bigger payload passing over shared memory. 52 */ 53 public static final String STABLE_AIDL_SHARED_MEMORY_MEMBER = "sharedMemoryFd"; 54 /** 55 * Stable AIDL Parcelable has {@code readFromParcel(Parcel)} public method. 56 */ 57 public static final String STABLE_AIDL_PARCELABLE_READ_FROM_PARCEL = "readFromParcel"; 58 59 private static final String TAG = LargeParcelable.class.getSimpleName(); 60 private static final boolean DBG_PAYLOAD = false; 61 private static final boolean DBG_STABLE_AIDL_CLASS = false; 62 63 // cannot set this final even if it is set only once in constructor as set is done in 64 // deserialize call. 65 private @Nullable Parcelable mParcelable; 66 67 // This is shared across thread. As this is per class, use volatile to avoid adding 68 // separate lock. If additional static volatile is added, a lock should be added. 69 private static volatile WeakReference<ClassLoader> sClassLoader = null; 70 71 /** 72 * Sets {@code ClassLoader} for loading the {@Code Parcelable}. This should be done before 73 * getting binder call. Default classloader may not recognize the Parcelable passed and relevant 74 * classloader like package classloader should be set before getting any binder data. 75 */ setClassLoader(ClassLoader loader)76 public static void setClassLoader(ClassLoader loader) { 77 sClassLoader = new WeakReference<>(loader); 78 } 79 LargeParcelable(Parcel in)80 public LargeParcelable(Parcel in) { 81 super(in); 82 } 83 LargeParcelable(Parcelable parcelable)84 public LargeParcelable(Parcelable parcelable) { 85 mParcelable = parcelable; 86 } 87 88 /** 89 * Returns {@code Parcelable} carried by this instance. 90 */ getParcelable()91 public Parcelable getParcelable() { 92 return mParcelable; 93 } 94 95 @Override serialize(@onNull Parcel dest, int flags)96 protected void serialize(@NonNull Parcel dest, int flags) { 97 int startPosition; 98 if (DBG_PAYLOAD) { 99 startPosition = dest.dataPosition(); 100 } 101 dest.writeParcelable(mParcelable, flags); 102 if (DBG_PAYLOAD) { 103 Slog.d(TAG, "serialize-payload, start:" + startPosition 104 + " size:" + (dest.dataPosition() - startPosition)); 105 } 106 } 107 108 @Override serializeNullPayload(@onNull Parcel dest)109 protected void serializeNullPayload(@NonNull Parcel dest) { 110 int startPosition; 111 if (DBG_PAYLOAD) { 112 startPosition = dest.dataPosition(); 113 } 114 dest.writeParcelable(null, 0); 115 if (DBG_PAYLOAD) { 116 Slog.d(TAG, "serializeNullPayload-payload, start:" + startPosition 117 + " size:" + (dest.dataPosition() - startPosition)); 118 } 119 } 120 121 @Override deserialize(@onNull Parcel src)122 protected void deserialize(@NonNull Parcel src) { 123 // default class loader does not work as it may not be in boot class path. 124 ClassLoader loader = (sClassLoader == null) ? null : sClassLoader.get(); 125 int startPosition; 126 if (DBG_PAYLOAD) { 127 startPosition = src.dataPosition(); 128 } 129 mParcelable = src.readParcelable(loader); 130 if (DBG_PAYLOAD) { 131 Slog.d(TAG, "deserialize-payload, start:" + startPosition 132 + " size:" + (src.dataPosition() - startPosition) 133 + " mParcelable:" + mParcelable); 134 } 135 } 136 137 public static final @NonNull Parcelable.Creator<LargeParcelable> CREATOR = 138 new Parcelable.Creator<LargeParcelable>() { 139 @Override 140 public LargeParcelable[] newArray(int size) { 141 return new LargeParcelable[size]; 142 } 143 144 @Override 145 public LargeParcelable createFromParcel(@NonNull Parcel in) { 146 return new LargeParcelable(in); 147 } 148 }; 149 150 /** 151 * @see #toLargeParcelable(Parcelable, Callable<Parcelable>) 152 */ 153 @Nullable toLargeParcelable(@ullable Parcelable p)154 public static Parcelable toLargeParcelable(@Nullable Parcelable p) { 155 return toLargeParcelable(p, null); 156 } 157 158 /** 159 * Prepare a {@code Parcelable} defined from Stable AIDL to be able to sent through binder. 160 * 161 * <p>The {@code Parcelable} should have a public member having name of 162 * {@link #STABLE_AIDL_SHARED_MEMORY_MEMBER} with {@code ParcelFileDescriptor} type. 163 * 164 * <p>If payload size is big, the input would be serialized to a shared memory file and a 165 * an empty {@code Parcelable} with only the file descriptor set would be returned. If the 166 * payload size is small enough to be sent across binder or the input already contains a shared 167 * memory file, the original input would be returned. 168 * 169 * @param p {@code Parcelable} the input to convert that might contain large data. 170 * @param constructEmptyParcelable a callable to create an empty Parcelable with the same type 171 * as input. If this is null, the default initializer for the input type would be used. 172 * @return a {@code Parcelable} that could be sent through binder despite memory limitation. 173 */ 174 @Nullable toLargeParcelable( @ullable Parcelable p, @Nullable Callable<Parcelable> constructEmptyParcelable)175 public static Parcelable toLargeParcelable( 176 @Nullable Parcelable p, @Nullable Callable<Parcelable> constructEmptyParcelable) { 177 if (p == null) { 178 return null; 179 } 180 Class parcelableClass = p.getClass(); 181 if (DBG_STABLE_AIDL_CLASS) { 182 Slog.d(TAG, "toLargeParcelable stable AIDL Parcelable:" 183 + parcelableClass.getSimpleName()); 184 } 185 Field field; 186 ParcelFileDescriptor sharedMemoryFd; 187 try { 188 field = parcelableClass.getField(STABLE_AIDL_SHARED_MEMORY_MEMBER); 189 sharedMemoryFd = (ParcelFileDescriptor) field.get(p); 190 } catch (Exception e) { 191 throw new IllegalArgumentException("Cannot access " + STABLE_AIDL_SHARED_MEMORY_MEMBER, 192 e); 193 } 194 if (sharedMemoryFd != null) { 195 return p; 196 } 197 Parcel dataParcel = Parcel.obtain(); 198 p.writeToParcel(dataParcel, 0); 199 int payloadSize = dataParcel.dataSize(); 200 if (payloadSize <= LargeParcelableBase.MAX_DIRECT_PAYLOAD_SIZE) { 201 // direct path, no re-write to shared memory. 202 if (DBG_PAYLOAD) { 203 Slog.d(TAG, "toLargeParcelable send directly, payload size:" + payloadSize); 204 } 205 return p; 206 } 207 SharedMemory memory = LargeParcelableBase.serializeParcelToSharedMemory(dataParcel); 208 dataParcel.recycle(); 209 210 try { 211 sharedMemoryFd = SharedMemoryHelper.createParcelFileDescriptor(memory); 212 } catch (IOException e) { 213 throw new IllegalArgumentException("unable to duplicate shared memory fd", e); 214 } 215 216 Parcelable emptyPayload; 217 if (constructEmptyParcelable != null) { 218 try { 219 emptyPayload = constructEmptyParcelable.call(); 220 } catch (Exception e) { 221 throw new IllegalArgumentException("Cannot use Parcelable constructor", e); 222 } 223 } else { 224 try { 225 emptyPayload = (Parcelable) parcelableClass.newInstance(); 226 } catch (Exception e) { 227 throw new IllegalArgumentException("Cannot access Parcelable constructor", e); 228 } 229 } 230 231 try { 232 field.set(emptyPayload, sharedMemoryFd); 233 } catch (Exception e) { 234 throw new IllegalArgumentException("Cannot access Parcelable member for FD", e); 235 } 236 237 return emptyPayload; 238 } 239 240 /** 241 * Reconstructs {@code Parcelable} defined from Stable AIDL. It should have a {@link 242 * ParcelFileDescriptor} member named {@link #STABLE_AIDL_SHARED_MEMORY_MEMBER} and will create 243 * a new {@code Parcelable} if the shared memory portion is not null. If there is no shared 244 * memory, it will return the original {@code Parcelable p} as it is. 245 * 246 * @param p Original {@code Parcelable} containing the payload. 247 * @param keepSharedMemory Whether to keep created shared memory in the returned {@code 248 * Parcelable}. Set to {@code true} if this {@code Parcelable} is sent 249 * across binder repeatedly. 250 * @return a new {@code Parcelable} if payload read from shared memory or old one if payload 251 * is small enough. 252 */ reconstructStableAIDLParcelable(@ullable Parcelable p, boolean keepSharedMemory)253 public static @Nullable Parcelable reconstructStableAIDLParcelable(@Nullable Parcelable p, 254 boolean keepSharedMemory) { 255 if (p == null) { 256 return null; 257 } 258 Class parcelableClass = p.getClass(); 259 if (DBG_STABLE_AIDL_CLASS) { 260 Slog.d(TAG, "reconstructStableAIDLParcelable stable AIDL Parcelable:" 261 + parcelableClass.getSimpleName()); 262 } 263 ParcelFileDescriptor sharedMemoryFd = null; 264 Field fieldSharedMemory; 265 try { 266 fieldSharedMemory = parcelableClass.getField(STABLE_AIDL_SHARED_MEMORY_MEMBER); 267 sharedMemoryFd = (ParcelFileDescriptor) fieldSharedMemory.get(p); 268 } catch (Exception e) { 269 throw new IllegalArgumentException("Cannot access " + STABLE_AIDL_SHARED_MEMORY_MEMBER, 270 e); 271 } 272 if (sharedMemoryFd == null) { 273 if (DBG_PAYLOAD) { 274 Slog.d(TAG, "reconstructStableAIDLParcelable null shared memory"); 275 } 276 return p; 277 } 278 Parcel in = null; 279 Parcelable retParcelable; 280 try { 281 // SharedMemory.fromFileDescriptor take ownership, so we need to dupe to keep 282 // sharedMemoryFd the Parcelable valid. 283 SharedMemory memory = SharedMemory.fromFileDescriptor(sharedMemoryFd.dup()); 284 in = LargeParcelableBase.copyFromSharedMemory(memory); 285 retParcelable = (Parcelable) parcelableClass.newInstance(); 286 // runs retParcelable.readFromParcel(in) 287 Method readMethod = parcelableClass.getMethod(STABLE_AIDL_PARCELABLE_READ_FROM_PARCEL, 288 new Class[]{Parcel.class}); 289 readMethod.invoke(retParcelable, in); 290 if (keepSharedMemory) { 291 fieldSharedMemory.set(retParcelable, sharedMemoryFd); 292 } 293 if (DBG_PAYLOAD) { 294 Slog.d(TAG, "reconstructStableAIDLParcelable read shared memory, data size:" 295 + in.dataPosition()); 296 } 297 } catch (Exception e) { 298 throw new IllegalArgumentException("Cannot access Parcelable constructor/method", e); 299 } finally { 300 if (in != null) { 301 in.recycle(); 302 } 303 } 304 return retParcelable; 305 } 306 } 307