1 /* 2 * Copyright (C) 2011 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 android.content.pm; 18 19 import android.compat.annotation.UnsupportedAppUsage; 20 import android.os.BadParcelableException; 21 import android.os.Binder; 22 import android.os.Build; 23 import android.os.IBinder; 24 import android.os.Parcel; 25 import android.os.Parcelable; 26 import android.os.RemoteException; 27 import android.util.Log; 28 29 import java.util.ArrayList; 30 import java.util.List; 31 32 /** 33 * Transfer a large list of Parcelable objects across an IPC. Splits into 34 * multiple transactions if needed. 35 * 36 * Caveat: for efficiency and security, all elements must be the same concrete type. 37 * In order to avoid writing the class name of each object, we must ensure that 38 * each object is the same type, or else unparceling then reparceling the data may yield 39 * a different result if the class name encoded in the Parcelable is a Base type. 40 * See b/17671747. 41 * 42 * @hide 43 */ 44 abstract class BaseParceledListSlice<T> implements Parcelable { 45 private static final String TAG = "ParceledListSlice"; 46 private static final boolean DEBUG = false; 47 48 private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes(); 49 50 /** 51 * As of 2024 and for some time, max size has been 64KB. If a single 52 * element is too large, this class will write too big of Parcels, 53 * so log. 64KB/4 is 16KB is still pretty big for a single element 54 * (which could result in a ~64KB + 16KB = 80KB transaction). We may 55 * want to reduce the warning size just in case. Though, 64KB is 56 * already quite large for binder transactions, another strategy may 57 * be needed. 58 */ 59 private static final int WARN_ELM_SIZE = MAX_IPC_SIZE / 4; 60 61 private List<T> mList; 62 63 private int mInlineCountLimit = Integer.MAX_VALUE; 64 65 private boolean mHasBeenParceled = false; 66 BaseParceledListSlice(List<T> list)67 public BaseParceledListSlice(List<T> list) { 68 mList = list; 69 } 70 71 @SuppressWarnings("unchecked") BaseParceledListSlice(Parcel p, ClassLoader loader)72 BaseParceledListSlice(Parcel p, ClassLoader loader) { 73 final int N = p.readInt(); 74 mList = new ArrayList<T>(N); 75 if (DEBUG) Log.d(TAG, "Retrieving " + N + " items"); 76 if (N <= 0) { 77 return; 78 } 79 80 Parcelable.Creator<?> creator = readParcelableCreator(p, loader); 81 Class<?> listElementClass = null; 82 83 int i = 0; 84 while (i < N) { 85 if (p.readInt() == 0) { 86 break; 87 } 88 listElementClass = readVerifyAndAddElement(creator, p, loader, listElementClass); 89 if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1)); 90 i++; 91 } 92 if (i >= N) { 93 return; 94 } 95 final IBinder retriever = p.readStrongBinder(); 96 while (i < N) { 97 if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever); 98 Parcel data = Parcel.obtain(); 99 Parcel reply = Parcel.obtain(); 100 data.writeInt(i); 101 try { 102 retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0); 103 reply.readException(); 104 while (i < N && reply.readInt() != 0) { 105 listElementClass = readVerifyAndAddElement(creator, reply, loader, 106 listElementClass); 107 if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1)); 108 i++; 109 } 110 } catch (RemoteException e) { 111 throw new BadParcelableException( 112 "Failure retrieving array; only received " + i + " of " + N, e); 113 } finally { 114 reply.recycle(); 115 data.recycle(); 116 } 117 } 118 } 119 readVerifyAndAddElement(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader, Class<?> listElementClass)120 private Class<?> readVerifyAndAddElement(Parcelable.Creator<?> creator, Parcel p, 121 ClassLoader loader, Class<?> listElementClass) { 122 final T parcelable = readCreator(creator, p, loader); 123 if (listElementClass == null) { 124 listElementClass = parcelable.getClass(); 125 } else { 126 verifySameType(listElementClass, parcelable.getClass()); 127 } 128 mList.add(parcelable); 129 return listElementClass; 130 } 131 readCreator(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader)132 private T readCreator(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader) { 133 if (creator instanceof Parcelable.ClassLoaderCreator<?>) { 134 Parcelable.ClassLoaderCreator<?> classLoaderCreator = 135 (Parcelable.ClassLoaderCreator<?>) creator; 136 return (T) classLoaderCreator.createFromParcel(p, loader); 137 } 138 return (T) creator.createFromParcel(p); 139 } 140 verifySameType(final Class<?> expected, final Class<?> actual)141 private static void verifySameType(final Class<?> expected, final Class<?> actual) { 142 if (!actual.equals(expected)) { 143 throw new IllegalArgumentException("Can't unparcel type " 144 + (actual == null ? null : actual.getName()) + " in list of type " 145 + (expected == null ? null : expected.getName())); 146 } 147 } 148 149 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getList()150 public List<T> getList() { 151 return mList; 152 } 153 154 /** 155 * Set a limit on the maximum number of entries in the array that will be included 156 * inline in the initial parcelling of this object. 157 */ setInlineCountLimit(int maxCount)158 public void setInlineCountLimit(int maxCount) { 159 mInlineCountLimit = maxCount; 160 } 161 162 /** 163 * Write this to another Parcel. Note that this discards the internal Parcel 164 * and should not be used anymore. This is so we can pass this to a Binder 165 * where we won't have a chance to call recycle on this. 166 * 167 * This method can only be called once per BaseParceledListSlice to ensure that 168 * the referenced list can be cleaned up before the recipient cleans up the 169 * Binder reference. 170 */ 171 @Override writeToParcel(Parcel dest, int flags)172 public void writeToParcel(Parcel dest, int flags) { 173 if (mHasBeenParceled) { 174 throw new IllegalStateException("Can't Parcel a ParceledListSlice more than once"); 175 } 176 mHasBeenParceled = true; 177 final int N = mList.size(); 178 final int callFlags = flags; 179 dest.writeInt(N); 180 if (DEBUG) Log.d(TAG, "Writing " + N + " items"); 181 if (N > 0) { 182 final Class<?> listElementClass = mList.get(0).getClass(); 183 writeParcelableCreator(mList.get(0), dest); 184 int i = 0; 185 while (i < N && i < mInlineCountLimit && dest.dataSize() < MAX_IPC_SIZE) { 186 dest.writeInt(1); 187 188 final T parcelable = mList.get(i); 189 verifySameType(listElementClass, parcelable.getClass()); 190 writeElement(parcelable, dest, callFlags); 191 192 if (DEBUG) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i)); 193 i++; 194 } 195 if (i < N) { 196 dest.writeInt(0); 197 Binder retriever = new Binder() { 198 @Override 199 protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) 200 throws RemoteException { 201 if (code != FIRST_CALL_TRANSACTION) { 202 return super.onTransact(code, data, reply, flags); 203 } else if (mList == null) { 204 throw new IllegalArgumentException("Attempt to transfer null list, " 205 + "did transfer finish?"); 206 } 207 int i = data.readInt(); 208 209 if (DEBUG) { 210 Log.d(TAG, "Writing more @" + i + " of " + N + " to " 211 + Binder.getCallingPid() + ", sender=" + this); 212 } 213 214 try { 215 reply.writeNoException(); 216 217 // note: this logic ensures if there are enough elements in the list, 218 // we will always write over the max IPC size. This is dangerous 219 // when there are large elements. 220 while (i < N && reply.dataSize() < MAX_IPC_SIZE) { 221 reply.writeInt(1); 222 223 int preWriteSize = reply.dataSize(); 224 225 final T parcelable = mList.get(i); 226 verifySameType(listElementClass, parcelable.getClass()); 227 writeElement(parcelable, reply, callFlags); 228 229 int elmSize = reply.dataSize() - preWriteSize; 230 if (elmSize >= WARN_ELM_SIZE) { 231 Log.w(TAG, "Element #" + i + " is " + elmSize + " bytes."); 232 } 233 234 if (DEBUG) Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i)); 235 i++; 236 } 237 if (i < N) { 238 if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N); 239 reply.writeInt(0); 240 } else { 241 if (DEBUG) Log.d(TAG, "Transfer done, clearing mList reference"); 242 mList = null; 243 } 244 if (reply.dataSize() >= MAX_IPC_SIZE + WARN_ELM_SIZE) { 245 Log.w(TAG, "Overly large reply size: " + reply.dataSize()); 246 } 247 } catch (RuntimeException e) { 248 if (DEBUG) Log.d(TAG, "Transfer failed, clearing mList reference"); 249 mList = null; 250 throw e; 251 } 252 return true; 253 } 254 }; 255 if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever); 256 dest.writeStrongBinder(retriever); 257 } 258 } 259 } 260 writeElement(T parcelable, Parcel reply, int callFlags)261 protected abstract void writeElement(T parcelable, Parcel reply, int callFlags); 262 263 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) writeParcelableCreator(T parcelable, Parcel dest)264 protected abstract void writeParcelableCreator(T parcelable, Parcel dest); 265 readParcelableCreator(Parcel from, ClassLoader loader)266 protected abstract Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader); 267 } 268