diff options
author | Yohei Yukawa <yukawa@google.com> | 2014-03-07 08:27:23 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2014-03-07 08:27:23 +0000 |
commit | ae90d762c3ae5cd9c8c918849dc91dfea825387b (patch) | |
tree | 90fdccbbf159dfb01cc47c5087b9f36c00a890ce /core/java | |
parent | e3987dff9cf6b3b956a4a90dbff697ac45d00c65 (diff) | |
parent | f06569561fe1c6e898debf8bb9f37331a9f87323 (diff) | |
download | frameworks_base-ae90d762c3ae5cd9c8c918849dc91dfea825387b.zip frameworks_base-ae90d762c3ae5cd9c8c918849dc91dfea825387b.tar.gz frameworks_base-ae90d762c3ae5cd9c8c918849dc91dfea825387b.tar.bz2 |
Merge "Introduce InputMethodSubtypeArray for memory efficient IPCs"
Diffstat (limited to 'core/java')
3 files changed, 294 insertions, 13 deletions
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java index 9f2bf33..f8160c8 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -37,6 +37,7 @@ import android.util.Printer; import android.util.Slog; import android.util.Xml; import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; +import android.view.inputmethod.InputMethodSubtypeArray; import java.io.IOException; import java.util.ArrayList; @@ -86,9 +87,9 @@ public final class InputMethodInfo implements Parcelable { final int mIsDefaultResId; /** - * The array of the subtypes. + * An array-like container of the subtypes. */ - private final ArrayList<InputMethodSubtype> mSubtypes = new ArrayList<InputMethodSubtype>(); + private final InputMethodSubtypeArray mSubtypes; private final boolean mIsAuxIme; @@ -138,6 +139,7 @@ public final class InputMethodInfo implements Parcelable { int isDefaultResId = 0; XmlResourceParser parser = null; + final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); try { parser = si.loadXmlMetaData(pm, InputMethod.SERVICE_META_DATA); if (parser == null) { @@ -206,7 +208,7 @@ public final class InputMethodInfo implements Parcelable { if (!subtype.isAuxiliary()) { isAuxIme = false; } - mSubtypes.add(subtype); + subtypes.add(subtype); } } } catch (NameNotFoundException e) { @@ -216,7 +218,7 @@ public final class InputMethodInfo implements Parcelable { if (parser != null) parser.close(); } - if (mSubtypes.size() == 0) { + if (subtypes.size() == 0) { isAuxIme = false; } @@ -225,14 +227,15 @@ public final class InputMethodInfo implements Parcelable { final int N = additionalSubtypes.size(); for (int i = 0; i < N; ++i) { final InputMethodSubtype subtype = additionalSubtypes.get(i); - if (!mSubtypes.contains(subtype)) { - mSubtypes.add(subtype); + if (!subtypes.contains(subtype)) { + subtypes.add(subtype); } else { Slog.w(TAG, "Duplicated subtype definition found: " + subtype.getLocale() + ", " + subtype.getMode()); } } } + mSubtypes = new InputMethodSubtypeArray(subtypes); mSettingsActivityName = settingsActivityComponent; mIsDefaultResId = isDefaultResId; mIsAuxIme = isAuxIme; @@ -246,7 +249,7 @@ public final class InputMethodInfo implements Parcelable { mIsAuxIme = source.readInt() == 1; mSupportsSwitchingToNextInputMethod = source.readInt() == 1; mService = ResolveInfo.CREATOR.createFromParcel(source); - source.readTypedList(mSubtypes, InputMethodSubtype.CREATOR); + mSubtypes = new InputMethodSubtypeArray(source); mForceDefault = false; } @@ -272,9 +275,7 @@ public final class InputMethodInfo implements Parcelable { mSettingsActivityName = settingsActivity; mIsDefaultResId = isDefaultResId; mIsAuxIme = isAuxIme; - if (subtypes != null) { - mSubtypes.addAll(subtypes); - } + mSubtypes = new InputMethodSubtypeArray(subtypes); mForceDefault = forceDefault; mSupportsSwitchingToNextInputMethod = true; } @@ -364,7 +365,7 @@ public final class InputMethodInfo implements Parcelable { * composed of {@link #getPackageName} and the class name returned here. * * <p>A null will be returned if there is no settings activity associated - * with the input method. + * with the input method.</p> */ public String getSettingsActivity() { return mSettingsActivityName; @@ -374,7 +375,7 @@ public final class InputMethodInfo implements Parcelable { * Return the count of the subtypes of Input Method. */ public int getSubtypeCount() { - return mSubtypes.size(); + return mSubtypes.getCount(); } /** @@ -479,7 +480,7 @@ public final class InputMethodInfo implements Parcelable { dest.writeInt(mIsAuxIme ? 1 : 0); dest.writeInt(mSupportsSwitchingToNextInputMethod ? 1 : 0); mService.writeToParcel(dest, flags); - dest.writeTypedList(mSubtypes); + mSubtypes.writeToParcel(dest); } /** diff --git a/core/java/android/view/inputmethod/InputMethodSubtypeArray.java b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java new file mode 100644 index 0000000..5bef71f --- /dev/null +++ b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java @@ -0,0 +1,278 @@ +/* + * 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.os.Parcelable; +import android.util.AndroidRuntimeException; +import android.util.Slog; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +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}. + * + * <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 {@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 {@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. + * + * <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 {@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 {@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) { + ByteArrayOutputStream resultStream = null; + GZIPOutputStream zipper = null; + try { + resultStream = new ByteArrayOutputStream(); + zipper = new GZIPOutputStream(resultStream); + zipper.write(data); + } catch(IOException e) { + return null; + } finally { + try { + if (zipper != null) { + zipper.close(); + } + } catch (IOException e) { + zipper = null; + Slog.e(TAG, "Failed to close the stream.", e); + // swallowed, not propagated back to the caller + } + try { + if (resultStream != null) { + resultStream.close(); + } + } catch (IOException e) { + resultStream = null; + Slog.e(TAG, "Failed to close the stream.", e); + // swallowed, not propagated back to the caller + } + } + return resultStream != null ? resultStream.toByteArray() : null; + } + + private static byte[] decompress(final byte[] data, final int expectedSize) { + ByteArrayInputStream inputStream = null; + GZIPInputStream unzipper = null; + try { + inputStream = new ByteArrayInputStream(data); + 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(IOException e) { + return null; + } finally { + try { + if (unzipper != null) { + unzipper.close(); + } + } catch (IOException e) { + Slog.e(TAG, "Failed to close the stream.", e); + // swallowed, not propagated back to the caller + } + try { + if (inputStream != null) { + inputStream.close(); + } + } catch (IOException e) { + Slog.e(TAG, "Failed to close the stream.", e); + // swallowed, not propagated back to the caller + } + } + } +} diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 325a27d..e51345c 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -32,7 +32,9 @@ import com.android.internal.view.IInputMethodClient; * this file. */ interface IInputMethodManager { + // TODO: Use ParceledListSlice instead List<InputMethodInfo> getInputMethodList(); + // TODO: Use ParceledListSlice instead List<InputMethodInfo> getEnabledInputMethodList(); List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in String imiId, boolean allowsImplicitlySelectedSubtypes); |