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.Binder; 21 import android.os.Build; 22 import android.os.IBinder; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 import android.os.RemoteException; 26 import android.util.Log; 27 28 import java.util.ArrayList; 29 import java.util.List; 30 31 /** 32 * Transfer a large list of Parcelable objects across an IPC. Splits into 33 * multiple transactions if needed. 34 * 35 * Caveat: for efficiency and security, all elements must be the same concrete type. 36 * In order to avoid writing the class name of each object, we must ensure that 37 * each object is the same type, or else unparceling then reparceling the data may yield 38 * a different result if the class name encoded in the Parcelable is a Base type. 39 * See b/17671747. 40 * 41 * @hide 42 */ 43 abstract class BaseParceledListSlice<T> implements Parcelable { 44 private static String TAG = "ParceledListSlice"; 45 private static boolean DEBUG = false; 46 47 /* 48 * TODO get this number from somewhere else. For now set it to a quarter of 49 * the 1MB limit. 50 */ 51 private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes(); 52 53 private final List<T> mList; 54 55 private int mInlineCountLimit = Integer.MAX_VALUE; 56 BaseParceledListSlice(List<T> list)57 public BaseParceledListSlice(List<T> list) { 58 mList = list; 59 } 60 61 @SuppressWarnings("unchecked") BaseParceledListSlice(Parcel p, ClassLoader loader)62 BaseParceledListSlice(Parcel p, ClassLoader loader) { 63 final int N = p.readInt(); 64 mList = new ArrayList<T>(N); 65 if (DEBUG) Log.d(TAG, "Retrieving " + N + " items"); 66 if (N <= 0) { 67 return; 68 } 69 70 Parcelable.Creator<?> creator = readParcelableCreator(p, loader); 71 Class<?> listElementClass = null; 72 73 int i = 0; 74 while (i < N) { 75 if (p.readInt() == 0) { 76 break; 77 } 78 listElementClass = readVerifyAndAddElement(creator, p, loader, listElementClass); 79 if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1)); 80 i++; 81 } 82 if (i >= N) { 83 return; 84 } 85 final IBinder retriever = p.readStrongBinder(); 86 while (i < N) { 87 if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever); 88 Parcel data = Parcel.obtain(); 89 Parcel reply = Parcel.obtain(); 90 data.writeInt(i); 91 try { 92 retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0); 93 } catch (RemoteException e) { 94 Log.w(TAG, "Failure retrieving array; only received " + i + " of " + N, e); 95 return; 96 } 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 reply.recycle(); 104 data.recycle(); 105 } 106 } 107 readVerifyAndAddElement(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader, Class<?> listElementClass)108 private Class<?> readVerifyAndAddElement(Parcelable.Creator<?> creator, Parcel p, 109 ClassLoader loader, Class<?> listElementClass) { 110 final T parcelable = readCreator(creator, p, loader); 111 if (listElementClass == null) { 112 listElementClass = parcelable.getClass(); 113 } else { 114 verifySameType(listElementClass, parcelable.getClass()); 115 } 116 mList.add(parcelable); 117 return listElementClass; 118 } 119 readCreator(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader)120 private T readCreator(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader) { 121 if (creator instanceof Parcelable.ClassLoaderCreator<?>) { 122 Parcelable.ClassLoaderCreator<?> classLoaderCreator = 123 (Parcelable.ClassLoaderCreator<?>) creator; 124 return (T) classLoaderCreator.createFromParcel(p, loader); 125 } 126 return (T) creator.createFromParcel(p); 127 } 128 verifySameType(final Class<?> expected, final Class<?> actual)129 private static void verifySameType(final Class<?> expected, final Class<?> actual) { 130 if (!actual.equals(expected)) { 131 throw new IllegalArgumentException("Can't unparcel type " 132 + (actual == null ? null : actual.getName()) + " in list of type " 133 + (expected == null ? null : expected.getName())); 134 } 135 } 136 137 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getList()138 public List<T> getList() { 139 return mList; 140 } 141 142 /** 143 * Set a limit on the maximum number of entries in the array that will be included 144 * inline in the initial parcelling of this object. 145 */ setInlineCountLimit(int maxCount)146 public void setInlineCountLimit(int maxCount) { 147 mInlineCountLimit = maxCount; 148 } 149 150 /** 151 * Write this to another Parcel. Note that this discards the internal Parcel 152 * and should not be used anymore. This is so we can pass this to a Binder 153 * where we won't have a chance to call recycle on this. 154 */ 155 @Override writeToParcel(Parcel dest, int flags)156 public void writeToParcel(Parcel dest, int flags) { 157 final int N = mList.size(); 158 final int callFlags = flags; 159 dest.writeInt(N); 160 if (DEBUG) Log.d(TAG, "Writing " + N + " items"); 161 if (N > 0) { 162 final Class<?> listElementClass = mList.get(0).getClass(); 163 writeParcelableCreator(mList.get(0), dest); 164 int i = 0; 165 while (i < N && i < mInlineCountLimit && dest.dataSize() < MAX_IPC_SIZE) { 166 dest.writeInt(1); 167 168 final T parcelable = mList.get(i); 169 verifySameType(listElementClass, parcelable.getClass()); 170 writeElement(parcelable, dest, callFlags); 171 172 if (DEBUG) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i)); 173 i++; 174 } 175 if (i < N) { 176 dest.writeInt(0); 177 Binder retriever = new Binder() { 178 @Override 179 protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) 180 throws RemoteException { 181 if (code != FIRST_CALL_TRANSACTION) { 182 return super.onTransact(code, data, reply, flags); 183 } 184 int i = data.readInt(); 185 if (DEBUG) Log.d(TAG, "Writing more @" + i + " of " + N); 186 while (i < N && reply.dataSize() < MAX_IPC_SIZE) { 187 reply.writeInt(1); 188 189 final T parcelable = mList.get(i); 190 verifySameType(listElementClass, parcelable.getClass()); 191 writeElement(parcelable, reply, callFlags); 192 193 if (DEBUG) Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i)); 194 i++; 195 } 196 if (i < N) { 197 if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N); 198 reply.writeInt(0); 199 } 200 return true; 201 } 202 }; 203 if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever); 204 dest.writeStrongBinder(retriever); 205 } 206 } 207 } 208 writeElement(T parcelable, Parcel reply, int callFlags)209 protected abstract void writeElement(T parcelable, Parcel reply, int callFlags); 210 211 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) writeParcelableCreator(T parcelable, Parcel dest)212 protected abstract void writeParcelableCreator(T parcelable, Parcel dest); 213 readParcelableCreator(Parcel from, ClassLoader loader)214 protected abstract Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader); 215 } 216