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 public class ParceledListSlice<T extends Parcelable> 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 = 256 * 1024;
     50     private static final int MAX_FIRST_IPC_SIZE = MAX_IPC_SIZE / 2;
     51 
     52     private final List<T> mList;
     53 
     54     public ParceledListSlice(List<T> list) {
     55         mList = list;
     56     }
     57 
     58     private ParceledListSlice(Parcel p, ClassLoader loader) {
     59         final int N = p.readInt();
     60         mList = new ArrayList<T>(N);
     61         if (DEBUG) Log.d(TAG, "Retrieving " + N + " items");
     62         if (N <= 0) {
     63             return;
     64         }
     65 
     66         Parcelable.Creator<T> creator = p.readParcelableCreator(loader);
     67         Class<?> listElementClass = null;
     68 
     69         int i = 0;
     70         while (i < N) {
     71             if (p.readInt() == 0) {
     72                 break;
     73             }
     74 
     75             final T parcelable = p.readCreator(creator, loader);
     76             if (listElementClass == null) {
     77                 listElementClass = parcelable.getClass();
     78             } else {
     79                 verifySameType(listElementClass, parcelable.getClass());
     80             }
     81 
     82             mList.add(parcelable);
     83 
     84             if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1));
     85             i++;
     86         }
     87         if (i >= N) {
     88             return;
     89         }
     90         final IBinder retriever = p.readStrongBinder();
     91         while (i < N) {
     92             if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever);
     93             Parcel data = Parcel.obtain();
     94             Parcel reply = Parcel.obtain();
     95             data.writeInt(i);
     96             try {
     97                 retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
     98             } catch (RemoteException e) {
     99                 Log.w(TAG, "Failure retrieving array; only received " + i + " of " + N, e);
    100                 return;
    101             }
    102             while (i < N && reply.readInt() != 0) {
    103                 final T parcelable = reply.readCreator(creator, loader);
    104                 verifySameType(listElementClass, parcelable.getClass());
    105 
    106                 mList.add(parcelable);
    107 
    108                 if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1));
    109                 i++;
    110             }
    111             reply.recycle();
    112             data.recycle();
    113         }
    114     }
    115 
    116     private static void verifySameType(final Class<?> expected, final Class<?> actual) {
    117         if (!actual.equals(expected)) {
    118             throw new IllegalArgumentException("Can't unparcel type "
    119                     + actual.getName() + " in list of type "
    120                     + expected.getName());
    121         }
    122     }
    123 
    124     public List<T> getList() {
    125         return mList;
    126     }
    127 
    128     @Override
    129     public int describeContents() {
    130         int contents = 0;
    131         for (int i=0; i<mList.size(); i++) {
    132             contents |= mList.get(i).describeContents();
    133         }
    134         return contents;
    135     }
    136 
    137     /**
    138      * Write this to another Parcel. Note that this discards the internal Parcel
    139      * and should not be used anymore. This is so we can pass this to a Binder
    140      * where we won't have a chance to call recycle on this.
    141      */
    142     @Override
    143     public void writeToParcel(Parcel dest, int flags) {
    144         final int N = mList.size();
    145         final int callFlags = flags;
    146         dest.writeInt(N);
    147         if (DEBUG) Log.d(TAG, "Writing " + N + " items");
    148         if (N > 0) {
    149             final Class<?> listElementClass = mList.get(0).getClass();
    150             dest.writeParcelableCreator(mList.get(0));
    151             int i = 0;
    152             while (i < N && dest.dataSize() < MAX_FIRST_IPC_SIZE) {
    153                 dest.writeInt(1);
    154 
    155                 final T parcelable = mList.get(i);
    156                 verifySameType(listElementClass, parcelable.getClass());
    157                 parcelable.writeToParcel(dest, callFlags);
    158 
    159                 if (DEBUG) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i));
    160                 i++;
    161             }
    162             if (i < N) {
    163                 dest.writeInt(0);
    164                 Binder retriever = new Binder() {
    165                     @Override
    166                     protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
    167                             throws RemoteException {
    168                         if (code != FIRST_CALL_TRANSACTION) {
    169                             return super.onTransact(code, data, reply, flags);
    170                         }
    171                         int i = data.readInt();
    172                         if (DEBUG) Log.d(TAG, "Writing more @" + i + " of " + N);
    173                         while (i < N && reply.dataSize() < MAX_IPC_SIZE) {
    174                             reply.writeInt(1);
    175 
    176                             final T parcelable = mList.get(i);
    177                             verifySameType(listElementClass, parcelable.getClass());
    178                             parcelable.writeToParcel(reply, callFlags);
    179 
    180                             if (DEBUG) Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i));
    181                             i++;
    182                         }
    183                         if (i < N) {
    184                             if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N);
    185                             reply.writeInt(0);
    186                         }
    187                         return true;
    188                     }
    189                 };
    190                 if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever);
    191                 dest.writeStrongBinder(retriever);
    192             }
    193         }
    194     }
    195 
    196     @SuppressWarnings("unchecked")
    197     public static final Parcelable.ClassLoaderCreator<ParceledListSlice> CREATOR =
    198             new Parcelable.ClassLoaderCreator<ParceledListSlice>() {
    199         public ParceledListSlice createFromParcel(Parcel in) {
    200             return new ParceledListSlice(in, null);
    201         }
    202 
    203         @Override
    204         public ParceledListSlice createFromParcel(Parcel in, ClassLoader loader) {
    205             return new ParceledListSlice(in, loader);
    206         }
    207 
    208         public ParceledListSlice[] newArray(int size) {
    209             return new ParceledListSlice[size];
    210         }
    211     };
    212 }
    213