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