/*
 * 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 InputMethodSubtype.

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

  • Applying compression for the marshalled data
  • Lazily unmarshalling objects
  • Caching the marshalled data when appropriate
@hide
/** * An array-like container that stores multiple instances of {@link InputMethodSubtype}. * * <p>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.</p> * <ul> * <li>Applying compression for the marshalled data</li> * <li>Lazily unmarshalling objects</li> * <li>Caching the marshalled data when appropriate</li> * </ul> * * @hide */
public class InputMethodSubtypeArray { private final static String TAG = "InputMethodSubtypeArray";
Create a new instance of InputMethodSubtypeArray from an existing list of InputMethodSubtype.
Params:
/** * 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<InputMethodSubtype> subtypes) { if (subtypes == null) { mCount = 0; return; } mCount = subtypes.size(); mInstance = subtypes.toArray(new InputMethodSubtype[mCount]); }
Unmarshall an instance of InputMethodSubtypeArray from a given Parcel object.
Params:
/** * 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 Parcel object.

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

Params:
/** * Marshall the instance into a given {@link Parcel} object. * * <p>This methods may take a bit additional time to compress data lazily when called * first time.</p> * * @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 InputMethodSubtype specified with the given index.

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

Params:
/** * Return {@link InputMethodSubtype} specified with the given index. * * <p>This methods may take a bit additional time to decompress data lazily when called * first time.</p> * * @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 InputMethodSubtype objects.
/** * 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; } } }