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