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 String TAG = "ParceledListSlice"; 46 private static boolean DEBUG = false; 47 48 /* 49 * TODO get this number from somewhere else. For now set it to a quarter of 50 * the 1MB limit. 51 */ 52 private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes(); 53 54 private List<T> mList; 55 56 private int mInlineCountLimit = Integer.MAX_VALUE; 57 58 private boolean mHasBeenParceled = false; 59 BaseParceledListSlice(List<T> list)60 public BaseParceledListSlice(List<T> list) { 61 mList = list; 62 } 63 64 @SuppressWarnings("unchecked") BaseParceledListSlice(Parcel p, ClassLoader loader)65 BaseParceledListSlice(Parcel p, ClassLoader loader) { 66 final int N = p.readInt(); 67 mList = new ArrayList<T>(N); 68 if (DEBUG) Log.d(TAG, "Retrieving " + N + " items"); 69 if (N <= 0) { 70 return; 71 } 72 73 Parcelable.Creator<?> creator = readParcelableCreator(p, loader); 74 Class<?> listElementClass = null; 75 76 int i = 0; 77 while (i < N) { 78 if (p.readInt() == 0) { 79 break; 80 } 81 listElementClass = readVerifyAndAddElement(creator, p, loader, listElementClass); 82 if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1)); 83 i++; 84 } 85 if (i >= N) { 86 return; 87 } 88 final IBinder retriever = p.readStrongBinder(); 89 while (i < N) { 90 if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever); 91 Parcel data = Parcel.obtain(); 92 Parcel reply = Parcel.obtain(); 93 data.writeInt(i); 94 try { 95 retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0); 96 reply.readException(); 97 while (i < N && reply.readInt() != 0) { 98 listElementClass = readVerifyAndAddElement(creator, reply, loader, 99 listElementClass); 100 if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1)); 101 i++; 102 } 103 } catch (RemoteException e) { 104 throw new BadParcelableException( 105 "Failure retrieving array; only received " + i + " of " + N, e); 106 } finally { 107 reply.recycle(); 108 data.recycle(); 109 } 110 } 111 } 112 readVerifyAndAddElement(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader, Class<?> listElementClass)113 private Class<?> readVerifyAndAddElement(Parcelable.Creator<?> creator, Parcel p, 114 ClassLoader loader, Class<?> listElementClass) { 115 final T parcelable = readCreator(creator, p, loader); 116 if (listElementClass == null) { 117 listElementClass = parcelable.getClass(); 118 } else { 119 verifySameType(listElementClass, parcelable.getClass()); 120 } 121 mList.add(parcelable); 122 return listElementClass; 123 } 124 readCreator(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader)125 private T readCreator(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader) { 126 if (creator instanceof Parcelable.ClassLoaderCreator<?>) { 127 Parcelable.ClassLoaderCreator<?> classLoaderCreator = 128 (Parcelable.ClassLoaderCreator<?>) creator; 129 return (T) classLoaderCreator.createFromParcel(p, loader); 130 } 131 return (T) creator.createFromParcel(p); 132 } 133 verifySameType(final Class<?> expected, final Class<?> actual)134 private static void verifySameType(final Class<?> expected, final Class<?> actual) { 135 if (!actual.equals(expected)) { 136 throw new IllegalArgumentException("Can't unparcel type " 137 + (actual == null ? null : actual.getName()) + " in list of type " 138 + (expected == null ? null : expected.getName())); 139 } 140 } 141 142 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getList()143 public List<T> getList() { 144 return mList; 145 } 146 147 /** 148 * Set a limit on the maximum number of entries in the array that will be included 149 * inline in the initial parcelling of this object. 150 */ setInlineCountLimit(int maxCount)151 public void setInlineCountLimit(int maxCount) { 152 mInlineCountLimit = maxCount; 153 } 154 155 /** 156 * Write this to another Parcel. Note that this discards the internal Parcel 157 * and should not be used anymore. This is so we can pass this to a Binder 158 * where we won't have a chance to call recycle on this. 159 * 160 * This method can only be called once per BaseParceledListSlice to ensure that 161 * the referenced list can be cleaned up before the recipient cleans up the 162 * Binder reference. 163 */ 164 @Override writeToParcel(Parcel dest, int flags)165 public void writeToParcel(Parcel dest, int flags) { 166 if (mHasBeenParceled) { 167 throw new IllegalStateException("Can't Parcel a ParceledListSlice more than once"); 168 } 169 mHasBeenParceled = true; 170 final int N = mList.size(); 171 final int callFlags = flags; 172 dest.writeInt(N); 173 if (DEBUG) Log.d(TAG, "Writing " + N + " items"); 174 if (N > 0) { 175 final Class<?> listElementClass = mList.get(0).getClass(); 176 writeParcelableCreator(mList.get(0), dest); 177 int i = 0; 178 while (i < N && i < mInlineCountLimit && dest.dataSize() < MAX_IPC_SIZE) { 179 dest.writeInt(1); 180 181 final T parcelable = mList.get(i); 182 verifySameType(listElementClass, parcelable.getClass()); 183 writeElement(parcelable, dest, callFlags); 184 185 if (DEBUG) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i)); 186 i++; 187 } 188 if (i < N) { 189 dest.writeInt(0); 190 Binder retriever = new Binder() { 191 @Override 192 protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) 193 throws RemoteException { 194 if (code != FIRST_CALL_TRANSACTION) { 195 return super.onTransact(code, data, reply, flags); 196 } else if (mList == null) { 197 throw new IllegalArgumentException("Attempt to transfer null list, " 198 + "did transfer finish?"); 199 } 200 int i = data.readInt(); 201 202 if (DEBUG) { 203 Log.d(TAG, "Writing more @" + i + " of " + N + " to " 204 + Binder.getCallingPid() + ", sender=" + this); 205 } 206 207 try { 208 reply.writeNoException(); 209 while (i < N && reply.dataSize() < MAX_IPC_SIZE) { 210 reply.writeInt(1); 211 212 final T parcelable = mList.get(i); 213 verifySameType(listElementClass, parcelable.getClass()); 214 writeElement(parcelable, reply, callFlags); 215 216 if (DEBUG) Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i)); 217 i++; 218 } 219 if (i < N) { 220 if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N); 221 reply.writeInt(0); 222 } else { 223 if (DEBUG) Log.d(TAG, "Transfer done, clearing mList reference"); 224 mList = null; 225 } 226 } catch (RuntimeException e) { 227 if (DEBUG) Log.d(TAG, "Transfer failed, clearing mList reference"); 228 mList = null; 229 throw e; 230 } 231 return true; 232 } 233 }; 234 if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever); 235 dest.writeStrongBinder(retriever); 236 } 237 } 238 } 239 writeElement(T parcelable, Parcel reply, int callFlags)240 protected abstract void writeElement(T parcelable, Parcel reply, int callFlags); 241 242 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) writeParcelableCreator(T parcelable, Parcel dest)243 protected abstract void writeParcelableCreator(T parcelable, Parcel dest); 244 readParcelableCreator(Parcel from, ClassLoader loader)245 protected abstract Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader); 246 } 247