summaryrefslogtreecommitdiff
path: root/core/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/view/textclassifier/ActionsModelParamsSupplier.java208
-rw-r--r--core/java/android/view/textclassifier/ModelFileManager.java9
-rw-r--r--core/java/android/view/textclassifier/TextClassifierImpl.java22
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);
}