blob: 8d122adf84a04fcbef9eafd02641851bfab670ff [file] [log] [blame]
/*
* 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 com.android.textclassifier;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.provider.Settings;
import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.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. */
public final class ActionsModelParamsSupplier
implements Supplier<ActionsModelParamsSupplier.ActionsModelParams> {
private static final String TAG = "ActionsModelParamsSupplier";
@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) {
TcLog.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) {
// TcLog.w(TAG, "ActionsModelParams.Parse, invalid model version");
// return ActionsModelParams.INVALID;
// }
// String locales = keyValueListParser.getString(KEY_REQUIRED_LOCALES, null);
// if (locales == null) {
// TcLog.w(TAG, "ActionsModelParams.Parse, invalid locales");
// return ActionsModelParams.INVALID;
// }
// String serializedPreconditionsStr =
// keyValueListParser.getString(KEY_SERIALIZED_PRECONDITIONS, null);
// if (serializedPreconditionsStr == null) {
// TcLog.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) {
// TcLog.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) {
TcLog.w(
TAG,
String.format(
"Not applying mSerializedPreconditions, required version=%d,"
+ " actual=%d",
mRequiredModelVersion, modelInUse.getVersion()));
return null;
}
if (!Objects.equals(modelInUse.getSupportedLocalesStr(), mRequiredModelLocales)) {
TcLog.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);
}
@Override
public void onChange(boolean selfChange) {
if (mOnChangedListener.get() != null) {
mOnChangedListener.get().run();
}
}
}
}