diff options
Diffstat (limited to 'core/java/android')
3 files changed, 236 insertions, 3 deletions
diff --git a/core/java/android/view/textclassifier/ActionsModelParamsSupplier.java b/core/java/android/view/textclassifier/ActionsModelParamsSupplier.java new file mode 100644 index 000000000000..6b90588f8d25 --- /dev/null +++ b/core/java/android/view/textclassifier/ActionsModelParamsSupplier.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2019 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.textclassifier; + +import android.annotation.Nullable; +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Base64; +import android.util.KeyValueListParser; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; + +import java.lang.ref.WeakReference; +import java.util.Objects; +import java.util.function.Supplier; + +/** + * Parses the {@link Settings.Global#TEXT_CLASSIFIER_ACTION_MODEL_PARAMS} flag. + * + * @hide + */ +public final class ActionsModelParamsSupplier implements + Supplier<ActionsModelParamsSupplier.ActionsModelParams> { + private static final String TAG = TextClassifier.DEFAULT_LOG_TAG; + + @VisibleForTesting + static final String KEY_REQUIRED_MODEL_VERSION = "required_model_version"; + @VisibleForTesting + static final String KEY_REQUIRED_LOCALES = "required_locales"; + @VisibleForTesting + static final String KEY_SERIALIZED_PRECONDITIONS = "serialized_preconditions"; + + private final Context mAppContext; + private final SettingsObserver mSettingsObserver; + + private final Object mLock = new Object(); + private final Runnable mOnChangedListener; + @Nullable + @GuardedBy("mLock") + private ActionsModelParams mActionsModelParams; + @GuardedBy("mLock") + private boolean mParsed = true; + + public ActionsModelParamsSupplier(Context context, @Nullable Runnable onChangedListener) { + mAppContext = Preconditions.checkNotNull(context).getApplicationContext(); + mOnChangedListener = onChangedListener == null ? () -> {} : onChangedListener; + mSettingsObserver = new SettingsObserver(mAppContext, () -> { + synchronized (mLock) { + Log.v(TAG, "Settings.Global.TEXT_CLASSIFIER_ACTION_MODEL_PARAMS is updated"); + mParsed = true; + mOnChangedListener.run(); + } + }); + } + + /** + * Returns the parsed actions params or {@link ActionsModelParams#INVALID} if the value is + * invalid. + */ + @Override + public ActionsModelParams get() { + synchronized (mLock) { + if (mParsed) { + mActionsModelParams = parse(mAppContext.getContentResolver()); + mParsed = false; + } + } + return mActionsModelParams; + } + + private ActionsModelParams parse(ContentResolver contentResolver) { + String settingStr = Settings.Global.getString(contentResolver, + Settings.Global.TEXT_CLASSIFIER_ACTION_MODEL_PARAMS); + if (TextUtils.isEmpty(settingStr)) { + return ActionsModelParams.INVALID; + } + try { + KeyValueListParser keyValueListParser = new KeyValueListParser(','); + keyValueListParser.setString(settingStr); + int version = keyValueListParser.getInt(KEY_REQUIRED_MODEL_VERSION, -1); + if (version == -1) { + Log.w(TAG, "ActionsModelParams.Parse, invalid model version"); + return ActionsModelParams.INVALID; + } + String locales = keyValueListParser.getString(KEY_REQUIRED_LOCALES, null); + if (locales == null) { + Log.w(TAG, "ActionsModelParams.Parse, invalid locales"); + return ActionsModelParams.INVALID; + } + String serializedPreconditionsStr = + keyValueListParser.getString(KEY_SERIALIZED_PRECONDITIONS, null); + if (serializedPreconditionsStr == null) { + Log.w(TAG, "ActionsModelParams.Parse, invalid preconditions"); + return ActionsModelParams.INVALID; + } + byte[] serializedPreconditions = + Base64.decode(serializedPreconditionsStr, Base64.NO_WRAP); + return new ActionsModelParams(version, locales, serializedPreconditions); + } catch (Throwable t) { + Log.e(TAG, "Invalid TEXT_CLASSIFIER_ACTION_MODEL_PARAMS, ignore", t); + } + return ActionsModelParams.INVALID; + } + + @Override + protected void finalize() throws Throwable { + try { + mAppContext.getContentResolver().unregisterContentObserver(mSettingsObserver); + } finally { + super.finalize(); + } + } + + /** + * Represents the parsed result. + */ + public static final class ActionsModelParams { + + public static final ActionsModelParams INVALID = + new ActionsModelParams(-1, "", new byte[0]); + + /** + * The required model version to apply {@code mSerializedPreconditions}. + */ + private final int mRequiredModelVersion; + + /** + * The required model locales to apply {@code mSerializedPreconditions}. + */ + private final String mRequiredModelLocales; + + /** + * The serialized params that will be applied to the model file, if all requirements are + * met. Do not modify. + */ + private final byte[] mSerializedPreconditions; + + public ActionsModelParams(int requiredModelVersion, String requiredModelLocales, + byte[] serializedPreconditions) { + mRequiredModelVersion = requiredModelVersion; + mRequiredModelLocales = Preconditions.checkNotNull(requiredModelLocales); + mSerializedPreconditions = Preconditions.checkNotNull(serializedPreconditions); + } + + /** + * Returns the serialized preconditions. Returns {@code null} if the the model in use does + * not meet all the requirements listed in the {@code ActionsModelParams} or the params + * are invalid. + */ + @Nullable + public byte[] getSerializedPreconditions(ModelFileManager.ModelFile modelInUse) { + if (this == INVALID) { + return null; + } + if (modelInUse.getVersion() != mRequiredModelVersion) { + Log.w(TAG, String.format( + "Not applying mSerializedPreconditions, required version=%d, actual=%d", + mRequiredModelVersion, modelInUse.getVersion())); + return null; + } + if (!Objects.equals(modelInUse.getSupportedLocalesStr(), mRequiredModelLocales)) { + Log.w(TAG, String.format( + "Not applying mSerializedPreconditions, required locales=%s, actual=%s", + mRequiredModelLocales, modelInUse.getSupportedLocalesStr())); + return null; + } + return mSerializedPreconditions; + } + } + + private static final class SettingsObserver extends ContentObserver { + + private final WeakReference<Runnable> mOnChangedListener; + + SettingsObserver(Context appContext, Runnable listener) { + super(null); + mOnChangedListener = new WeakReference<>(listener); + appContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.TEXT_CLASSIFIER_ACTION_MODEL_PARAMS), + false /* notifyForDescendants */, + this); + } + + public void onChange(boolean selfChange) { + if (mOnChangedListener.get() != null) { + mOnChangedListener.get().run(); + } + } + } +} diff --git a/core/java/android/view/textclassifier/ModelFileManager.java b/core/java/android/view/textclassifier/ModelFileManager.java index 8558a462fa40..e04285db75be 100644 --- a/core/java/android/view/textclassifier/ModelFileManager.java +++ b/core/java/android/view/textclassifier/ModelFileManager.java @@ -167,6 +167,7 @@ public final class ModelFileManager { file, version, supportedLocales, + supportedLocalesStr, ModelFile.LANGUAGE_INDEPENDENT.equals(supportedLocalesStr)); } catch (FileNotFoundException e) { Log.e(DEFAULT_LOG_TAG, "Failed to find " + file.getAbsolutePath(), e); @@ -201,13 +202,16 @@ public final class ModelFileManager { private final File mFile; private final int mVersion; private final List<Locale> mSupportedLocales; + private final String mSupportedLocalesStr; private final boolean mLanguageIndependent; public ModelFile(File file, int version, List<Locale> supportedLocales, + String supportedLocalesStr, boolean languageIndependent) { mFile = Preconditions.checkNotNull(file); mVersion = version; mSupportedLocales = Preconditions.checkNotNull(supportedLocales); + mSupportedLocalesStr = Preconditions.checkNotNull(supportedLocalesStr); mLanguageIndependent = languageIndependent; } @@ -237,6 +241,11 @@ public final class ModelFileManager { return Collections.unmodifiableList(mSupportedLocales); } + /** Returns the original supported locals string read from the model file. */ + public String getSupportedLocalesStr() { + return mSupportedLocalesStr; + } + /** * Returns if this model file is preferred to the given one. */ diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index 8f40a3ed956b..323bf597ab55 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -29,6 +29,7 @@ import android.os.ParcelFileDescriptor; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Pair; +import android.view.textclassifier.ActionsModelParamsSupplier.ActionsModelParams; import android.view.textclassifier.intent.ClassificationIntentFactory; import android.view.textclassifier.intent.LabeledIntent; import android.view.textclassifier.intent.LegacyClassificationIntentFactory; @@ -57,6 +58,7 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.function.Supplier; /** * Default implementation of the {@link TextClassifier} interface. @@ -124,6 +126,7 @@ public final class TextClassifierImpl implements TextClassifier { private final ClassificationIntentFactory mClassificationIntentFactory; private final TemplateIntentFactory mTemplateIntentFactory; + private final Supplier<ActionsModelParams> mActionsModelParamsSupplier; public TextClassifierImpl( Context context, TextClassificationConstants settings, TextClassifier fallback) { @@ -158,6 +161,15 @@ public final class TextClassifierImpl implements TextClassifier { ? new TemplateClassificationIntentFactory( mTemplateIntentFactory, new LegacyClassificationIntentFactory()) : new LegacyClassificationIntentFactory(); + mActionsModelParamsSupplier = new ActionsModelParamsSupplier(mContext, + () -> { + synchronized (mLock) { + // Clear mActionsImpl here, so that we will create a new + // ActionsSuggestionsModel object with the new flag in the next request. + mActionsImpl = null; + mActionModelInUse = null; + } + }); } public TextClassifierImpl(Context context, TextClassificationConstants settings) { @@ -584,10 +596,14 @@ public final class TextClassifierImpl implements TextClassifier { final ParcelFileDescriptor pfd = ParcelFileDescriptor.open( new File(bestModel.getPath()), ParcelFileDescriptor.MODE_READ_ONLY); try { - if (pfd != null) { - mActionsImpl = new ActionsSuggestionsModel(pfd.getFd()); - mActionModelInUse = bestModel; + if (pfd == null) { + Log.d(LOG_TAG, "Failed to read the model file: " + bestModel.getPath()); + return null; } + ActionsModelParams params = mActionsModelParamsSupplier.get(); + mActionsImpl = new ActionsSuggestionsModel( + pfd.getFd(), params.getSerializedPreconditions(bestModel)); + mActionModelInUse = bestModel; } finally { maybeCloseAndLogError(pfd); } |
