blob: 31645672f04992d0980f00c004b303defd3eab6f [file] [log] [blame]
Tony Mak20fe1872019-03-22 15:35:15 +00001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package android.view.textclassifier;
17
18import android.annotation.Nullable;
19import android.content.ContentResolver;
20import android.content.Context;
21import android.database.ContentObserver;
22import android.provider.Settings;
23import android.text.TextUtils;
24import android.util.Base64;
25import android.util.KeyValueListParser;
26
27import com.android.internal.annotations.GuardedBy;
28import com.android.internal.annotations.VisibleForTesting;
29import com.android.internal.util.Preconditions;
30
31import java.lang.ref.WeakReference;
32import java.util.Objects;
33import java.util.function.Supplier;
34
35/**
36 * Parses the {@link Settings.Global#TEXT_CLASSIFIER_ACTION_MODEL_PARAMS} flag.
37 *
38 * @hide
39 */
40public final class ActionsModelParamsSupplier implements
41 Supplier<ActionsModelParamsSupplier.ActionsModelParams> {
42 private static final String TAG = TextClassifier.DEFAULT_LOG_TAG;
43
44 @VisibleForTesting
45 static final String KEY_REQUIRED_MODEL_VERSION = "required_model_version";
46 @VisibleForTesting
47 static final String KEY_REQUIRED_LOCALES = "required_locales";
48 @VisibleForTesting
49 static final String KEY_SERIALIZED_PRECONDITIONS = "serialized_preconditions";
50
51 private final Context mAppContext;
52 private final SettingsObserver mSettingsObserver;
53
54 private final Object mLock = new Object();
55 private final Runnable mOnChangedListener;
56 @Nullable
57 @GuardedBy("mLock")
58 private ActionsModelParams mActionsModelParams;
59 @GuardedBy("mLock")
60 private boolean mParsed = true;
61
62 public ActionsModelParamsSupplier(Context context, @Nullable Runnable onChangedListener) {
Abodunrinwa Tokie8492692019-07-01 19:41:44 +010063 final Context appContext = Preconditions.checkNotNull(context).getApplicationContext();
64 // Some contexts don't have an app context.
65 mAppContext = appContext != null ? appContext : context;
Tony Mak20fe1872019-03-22 15:35:15 +000066 mOnChangedListener = onChangedListener == null ? () -> {} : onChangedListener;
67 mSettingsObserver = new SettingsObserver(mAppContext, () -> {
68 synchronized (mLock) {
69 Log.v(TAG, "Settings.Global.TEXT_CLASSIFIER_ACTION_MODEL_PARAMS is updated");
70 mParsed = true;
71 mOnChangedListener.run();
72 }
73 });
74 }
75
76 /**
77 * Returns the parsed actions params or {@link ActionsModelParams#INVALID} if the value is
78 * invalid.
79 */
80 @Override
81 public ActionsModelParams get() {
82 synchronized (mLock) {
83 if (mParsed) {
84 mActionsModelParams = parse(mAppContext.getContentResolver());
85 mParsed = false;
86 }
87 }
88 return mActionsModelParams;
89 }
90
91 private ActionsModelParams parse(ContentResolver contentResolver) {
92 String settingStr = Settings.Global.getString(contentResolver,
93 Settings.Global.TEXT_CLASSIFIER_ACTION_MODEL_PARAMS);
94 if (TextUtils.isEmpty(settingStr)) {
95 return ActionsModelParams.INVALID;
96 }
97 try {
98 KeyValueListParser keyValueListParser = new KeyValueListParser(',');
99 keyValueListParser.setString(settingStr);
100 int version = keyValueListParser.getInt(KEY_REQUIRED_MODEL_VERSION, -1);
101 if (version == -1) {
102 Log.w(TAG, "ActionsModelParams.Parse, invalid model version");
103 return ActionsModelParams.INVALID;
104 }
105 String locales = keyValueListParser.getString(KEY_REQUIRED_LOCALES, null);
106 if (locales == null) {
107 Log.w(TAG, "ActionsModelParams.Parse, invalid locales");
108 return ActionsModelParams.INVALID;
109 }
110 String serializedPreconditionsStr =
111 keyValueListParser.getString(KEY_SERIALIZED_PRECONDITIONS, null);
112 if (serializedPreconditionsStr == null) {
113 Log.w(TAG, "ActionsModelParams.Parse, invalid preconditions");
114 return ActionsModelParams.INVALID;
115 }
116 byte[] serializedPreconditions =
117 Base64.decode(serializedPreconditionsStr, Base64.NO_WRAP);
118 return new ActionsModelParams(version, locales, serializedPreconditions);
119 } catch (Throwable t) {
120 Log.e(TAG, "Invalid TEXT_CLASSIFIER_ACTION_MODEL_PARAMS, ignore", t);
121 }
122 return ActionsModelParams.INVALID;
123 }
124
125 @Override
126 protected void finalize() throws Throwable {
127 try {
128 mAppContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
129 } finally {
130 super.finalize();
131 }
132 }
133
134 /**
135 * Represents the parsed result.
136 */
137 public static final class ActionsModelParams {
138
139 public static final ActionsModelParams INVALID =
140 new ActionsModelParams(-1, "", new byte[0]);
141
142 /**
143 * The required model version to apply {@code mSerializedPreconditions}.
144 */
145 private final int mRequiredModelVersion;
146
147 /**
148 * The required model locales to apply {@code mSerializedPreconditions}.
149 */
150 private final String mRequiredModelLocales;
151
152 /**
153 * The serialized params that will be applied to the model file, if all requirements are
154 * met. Do not modify.
155 */
156 private final byte[] mSerializedPreconditions;
157
158 public ActionsModelParams(int requiredModelVersion, String requiredModelLocales,
159 byte[] serializedPreconditions) {
160 mRequiredModelVersion = requiredModelVersion;
161 mRequiredModelLocales = Preconditions.checkNotNull(requiredModelLocales);
162 mSerializedPreconditions = Preconditions.checkNotNull(serializedPreconditions);
163 }
164
165 /**
166 * Returns the serialized preconditions. Returns {@code null} if the the model in use does
167 * not meet all the requirements listed in the {@code ActionsModelParams} or the params
168 * are invalid.
169 */
170 @Nullable
171 public byte[] getSerializedPreconditions(ModelFileManager.ModelFile modelInUse) {
172 if (this == INVALID) {
173 return null;
174 }
175 if (modelInUse.getVersion() != mRequiredModelVersion) {
176 Log.w(TAG, String.format(
177 "Not applying mSerializedPreconditions, required version=%d, actual=%d",
178 mRequiredModelVersion, modelInUse.getVersion()));
179 return null;
180 }
181 if (!Objects.equals(modelInUse.getSupportedLocalesStr(), mRequiredModelLocales)) {
182 Log.w(TAG, String.format(
183 "Not applying mSerializedPreconditions, required locales=%s, actual=%s",
184 mRequiredModelLocales, modelInUse.getSupportedLocalesStr()));
185 return null;
186 }
187 return mSerializedPreconditions;
188 }
189 }
190
191 private static final class SettingsObserver extends ContentObserver {
192
193 private final WeakReference<Runnable> mOnChangedListener;
194
195 SettingsObserver(Context appContext, Runnable listener) {
196 super(null);
197 mOnChangedListener = new WeakReference<>(listener);
198 appContext.getContentResolver().registerContentObserver(
199 Settings.Global.getUriFor(Settings.Global.TEXT_CLASSIFIER_ACTION_MODEL_PARAMS),
200 false /* notifyForDescendants */,
201 this);
202 }
203
204 public void onChange(boolean selfChange) {
205 if (mOnChangedListener.get() != null) {
206 mOnChangedListener.get().run();
207 }
208 }
209 }
210}