Home | History | Annotate | Download | only in inputmethod
      1 /*
      2  * Copyright (C) 2007-2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * 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, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 
     17 package android.view.inputmethod;
     18 
     19 import android.os.Parcel;
     20 import android.util.Slog;
     21 
     22 import java.io.ByteArrayInputStream;
     23 import java.io.ByteArrayOutputStream;
     24 import java.util.List;
     25 import java.util.zip.GZIPInputStream;
     26 import java.util.zip.GZIPOutputStream;
     27 
     28 /**
     29  * An array-like container that stores multiple instances of {@link InputMethodSubtype}.
     30  *
     31  * <p>This container is designed to reduce the risk of {@link TransactionTooLargeException}
     32  * when one or more instancess of {@link InputMethodInfo} are transferred through IPC.
     33  * Basically this class does following three tasks.</p>
     34  * <ul>
     35  * <li>Applying compression for the marshalled data</li>
     36  * <li>Lazily unmarshalling objects</li>
     37  * <li>Caching the marshalled data when appropriate</li>
     38  * </ul>
     39  *
     40  * @hide
     41  */
     42 public class InputMethodSubtypeArray {
     43     private final static String TAG = "InputMethodSubtypeArray";
     44 
     45     /**
     46      * Create a new instance of {@link InputMethodSubtypeArray} from an existing list of
     47      * {@link InputMethodSubtype}.
     48      *
     49      * @param subtypes A list of {@link InputMethodSubtype} from which
     50      * {@link InputMethodSubtypeArray} will be created.
     51      */
     52     public InputMethodSubtypeArray(final List<InputMethodSubtype> subtypes) {
     53         if (subtypes == null) {
     54             mCount = 0;
     55             return;
     56         }
     57         mCount = subtypes.size();
     58         mInstance = subtypes.toArray(new InputMethodSubtype[mCount]);
     59     }
     60 
     61     /**
     62      * Unmarshall an instance of {@link InputMethodSubtypeArray} from a given {@link Parcel}
     63      * object.
     64      *
     65      * @param source A {@link Parcel} object from which {@link InputMethodSubtypeArray} will be
     66      * unmarshalled.
     67      */
     68     public InputMethodSubtypeArray(final Parcel source) {
     69         mCount = source.readInt();
     70         if (mCount > 0) {
     71             mDecompressedSize = source.readInt();
     72             mCompressedData = source.createByteArray();
     73         }
     74     }
     75 
     76     /**
     77      * Marshall the instance into a given {@link Parcel} object.
     78      *
     79      * <p>This methods may take a bit additional time to compress data lazily when called
     80      * first time.</p>
     81      *
     82      * @param source A {@link Parcel} object to which {@link InputMethodSubtypeArray} will be
     83      * marshalled.
     84      */
     85     public void writeToParcel(final Parcel dest) {
     86         if (mCount == 0) {
     87             dest.writeInt(mCount);
     88             return;
     89         }
     90 
     91         byte[] compressedData = mCompressedData;
     92         int decompressedSize = mDecompressedSize;
     93         if (compressedData == null && decompressedSize == 0) {
     94             synchronized (mLockObject) {
     95                 compressedData = mCompressedData;
     96                 decompressedSize = mDecompressedSize;
     97                 if (compressedData == null && decompressedSize == 0) {
     98                     final byte[] decompressedData = marshall(mInstance);
     99                     compressedData = compress(decompressedData);
    100                     if (compressedData == null) {
    101                         decompressedSize = -1;
    102                         Slog.i(TAG, "Failed to compress data.");
    103                     } else {
    104                         decompressedSize = decompressedData.length;
    105                     }
    106                     mDecompressedSize = decompressedSize;
    107                     mCompressedData = compressedData;
    108                 }
    109             }
    110         }
    111 
    112         if (compressedData != null && decompressedSize > 0) {
    113             dest.writeInt(mCount);
    114             dest.writeInt(decompressedSize);
    115             dest.writeByteArray(compressedData);
    116         } else {
    117             Slog.i(TAG, "Unexpected state. Behaving as an empty array.");
    118             dest.writeInt(0);
    119         }
    120     }
    121 
    122     /**
    123      * Return {@link InputMethodSubtype} specified with the given index.
    124      *
    125      * <p>This methods may take a bit additional time to decompress data lazily when called
    126      * first time.</p>
    127      *
    128      * @param index The index of {@link InputMethodSubtype}.
    129      */
    130     public InputMethodSubtype get(final int index) {
    131         if (index < 0 || mCount <= index) {
    132             throw new ArrayIndexOutOfBoundsException();
    133         }
    134         InputMethodSubtype[] instance = mInstance;
    135         if (instance == null) {
    136             synchronized (mLockObject) {
    137                 instance = mInstance;
    138                 if (instance == null) {
    139                     final byte[] decompressedData =
    140                           decompress(mCompressedData, mDecompressedSize);
    141                     // Clear the compressed data until {@link #getMarshalled()} is called.
    142                     mCompressedData = null;
    143                     mDecompressedSize = 0;
    144                     if (decompressedData != null) {
    145                         instance = unmarshall(decompressedData);
    146                     } else {
    147                         Slog.e(TAG, "Failed to decompress data. Returns null as fallback.");
    148                         instance = new InputMethodSubtype[mCount];
    149                     }
    150                     mInstance = instance;
    151                 }
    152             }
    153         }
    154         return instance[index];
    155     }
    156 
    157     /**
    158      * Return the number of {@link InputMethodSubtype} objects.
    159      */
    160     public int getCount() {
    161         return mCount;
    162     }
    163 
    164     private final Object mLockObject = new Object();
    165     private final int mCount;
    166 
    167     private volatile InputMethodSubtype[] mInstance;
    168     private volatile byte[] mCompressedData;
    169     private volatile int mDecompressedSize;
    170 
    171     private static byte[] marshall(final InputMethodSubtype[] array) {
    172         Parcel parcel = null;
    173         try {
    174             parcel = Parcel.obtain();
    175             parcel.writeTypedArray(array, 0);
    176             return parcel.marshall();
    177         } finally {
    178             if (parcel != null) {
    179                 parcel.recycle();
    180                 parcel = null;
    181             }
    182         }
    183     }
    184 
    185     private static InputMethodSubtype[] unmarshall(final byte[] data) {
    186         Parcel parcel = null;
    187         try {
    188             parcel = Parcel.obtain();
    189             parcel.unmarshall(data, 0, data.length);
    190             parcel.setDataPosition(0);
    191             return parcel.createTypedArray(InputMethodSubtype.CREATOR);
    192         } finally {
    193             if (parcel != null) {
    194                 parcel.recycle();
    195                 parcel = null;
    196             }
    197         }
    198     }
    199 
    200     private static byte[] compress(final byte[] data) {
    201         try (final ByteArrayOutputStream resultStream = new ByteArrayOutputStream();
    202                 final GZIPOutputStream zipper = new GZIPOutputStream(resultStream)) {
    203             zipper.write(data);
    204             zipper.finish();
    205             return resultStream.toByteArray();
    206         } catch(Exception e) {
    207             Slog.e(TAG, "Failed to compress the data.", e);
    208             return null;
    209         }
    210     }
    211 
    212     private static byte[] decompress(final byte[] data, final int expectedSize) {
    213         try (final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
    214                 final GZIPInputStream unzipper = new GZIPInputStream(inputStream)) {
    215             final byte [] result = new byte[expectedSize];
    216             int totalReadBytes = 0;
    217             while (totalReadBytes < result.length) {
    218                 final int restBytes = result.length - totalReadBytes;
    219                 final int readBytes = unzipper.read(result, totalReadBytes, restBytes);
    220                 if (readBytes < 0) {
    221                     break;
    222                 }
    223                 totalReadBytes += readBytes;
    224             }
    225             if (expectedSize != totalReadBytes) {
    226                 return null;
    227             }
    228             return result;
    229         } catch(Exception e) {
    230             Slog.e(TAG, "Failed to decompress the data.", e);
    231             return null;
    232         }
    233     }
    234 }
    235