/* * Copyright (C) 2007-2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package android.view.inputmethod; import android.os.Parcel; import android.util.Slog; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.List; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; /** * An array-like container that stores multiple instances of {@link InputMethodSubtype}. * *

This container is designed to reduce the risk of {@link TransactionTooLargeException} * when one or more instancess of {@link InputMethodInfo} are transferred through IPC. * Basically this class does following three tasks.

* * * @hide */ public class InputMethodSubtypeArray { private final static String TAG = "InputMethodSubtypeArray"; /** * Create a new instance of {@link InputMethodSubtypeArray} from an existing list of * {@link InputMethodSubtype}. * * @param subtypes A list of {@link InputMethodSubtype} from which * {@link InputMethodSubtypeArray} will be created. */ public InputMethodSubtypeArray(final List subtypes) { if (subtypes == null) { mCount = 0; return; } mCount = subtypes.size(); mInstance = subtypes.toArray(new InputMethodSubtype[mCount]); } /** * Unmarshall an instance of {@link InputMethodSubtypeArray} from a given {@link Parcel} * object. * * @param source A {@link Parcel} object from which {@link InputMethodSubtypeArray} will be * unmarshalled. */ public InputMethodSubtypeArray(final Parcel source) { mCount = source.readInt(); if (mCount > 0) { mDecompressedSize = source.readInt(); mCompressedData = source.createByteArray(); } } /** * Marshall the instance into a given {@link Parcel} object. * *

This methods may take a bit additional time to compress data lazily when called * first time.

* * @param source A {@link Parcel} object to which {@link InputMethodSubtypeArray} will be * marshalled. */ public void writeToParcel(final Parcel dest) { if (mCount == 0) { dest.writeInt(mCount); return; } byte[] compressedData = mCompressedData; int decompressedSize = mDecompressedSize; if (compressedData == null && decompressedSize == 0) { synchronized (mLockObject) { compressedData = mCompressedData; decompressedSize = mDecompressedSize; if (compressedData == null && decompressedSize == 0) { final byte[] decompressedData = marshall(mInstance); compressedData = compress(decompressedData); if (compressedData == null) { decompressedSize = -1; Slog.i(TAG, "Failed to compress data."); } else { decompressedSize = decompressedData.length; } mDecompressedSize = decompressedSize; mCompressedData = compressedData; } } } if (compressedData != null && decompressedSize > 0) { dest.writeInt(mCount); dest.writeInt(decompressedSize); dest.writeByteArray(compressedData); } else { Slog.i(TAG, "Unexpected state. Behaving as an empty array."); dest.writeInt(0); } } /** * Return {@link InputMethodSubtype} specified with the given index. * *

This methods may take a bit additional time to decompress data lazily when called * first time.

* * @param index The index of {@link InputMethodSubtype}. */ public InputMethodSubtype get(final int index) { if (index < 0 || mCount <= index) { throw new ArrayIndexOutOfBoundsException(); } InputMethodSubtype[] instance = mInstance; if (instance == null) { synchronized (mLockObject) { instance = mInstance; if (instance == null) { final byte[] decompressedData = decompress(mCompressedData, mDecompressedSize); // Clear the compressed data until {@link #getMarshalled()} is called. mCompressedData = null; mDecompressedSize = 0; if (decompressedData != null) { instance = unmarshall(decompressedData); } else { Slog.e(TAG, "Failed to decompress data. Returns null as fallback."); instance = new InputMethodSubtype[mCount]; } mInstance = instance; } } } return instance[index]; } /** * Return the number of {@link InputMethodSubtype} objects. */ public int getCount() { return mCount; } private final Object mLockObject = new Object(); private final int mCount; private volatile InputMethodSubtype[] mInstance; private volatile byte[] mCompressedData; private volatile int mDecompressedSize; private static byte[] marshall(final InputMethodSubtype[] array) { Parcel parcel = null; try { parcel = Parcel.obtain(); parcel.writeTypedArray(array, 0); return parcel.marshall(); } finally { if (parcel != null) { parcel.recycle(); parcel = null; } } } private static InputMethodSubtype[] unmarshall(final byte[] data) { Parcel parcel = null; try { parcel = Parcel.obtain(); parcel.unmarshall(data, 0, data.length); parcel.setDataPosition(0); return parcel.createTypedArray(InputMethodSubtype.CREATOR); } finally { if (parcel != null) { parcel.recycle(); parcel = null; } } } private static byte[] compress(final byte[] data) { try (final ByteArrayOutputStream resultStream = new ByteArrayOutputStream(); final GZIPOutputStream zipper = new GZIPOutputStream(resultStream)) { zipper.write(data); zipper.finish(); return resultStream.toByteArray(); } catch(Exception e) { Slog.e(TAG, "Failed to compress the data.", e); return null; } } private static byte[] decompress(final byte[] data, final int expectedSize) { try (final ByteArrayInputStream inputStream = new ByteArrayInputStream(data); final GZIPInputStream unzipper = new GZIPInputStream(inputStream)) { final byte [] result = new byte[expectedSize]; int totalReadBytes = 0; while (totalReadBytes < result.length) { final int restBytes = result.length - totalReadBytes; final int readBytes = unzipper.read(result, totalReadBytes, restBytes); if (readBytes < 0) { break; } totalReadBytes += readBytes; } if (expectedSize != totalReadBytes) { return null; } return result; } catch(Exception e) { Slog.e(TAG, "Failed to decompress the data.", e); return null; } } }