• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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  */
16 
17 package com.android.textclassifier.common;
18 
19 import android.content.res.AssetFileDescriptor;
20 import android.content.res.AssetManager;
21 import android.os.LocaleList;
22 import android.os.ParcelFileDescriptor;
23 import com.android.textclassifier.common.ModelType.ModelTypeDef;
24 import com.android.textclassifier.common.logging.ResultIdUtils.ModelInfo;
25 import com.google.android.textclassifier.ActionsSuggestionsModel;
26 import com.google.android.textclassifier.AnnotatorModel;
27 import com.google.android.textclassifier.LangIdModel;
28 import com.google.common.annotations.VisibleForTesting;
29 import com.google.common.base.Function;
30 import com.google.common.base.Optional;
31 import com.google.common.base.Preconditions;
32 import com.google.common.collect.ImmutableList;
33 import java.io.File;
34 import java.io.IOException;
35 import java.util.Arrays;
36 import java.util.List;
37 import java.util.Locale;
38 import java.util.Objects;
39 import java.util.stream.Collectors;
40 import javax.annotation.Nullable;
41 
42 /** Describes TextClassifier model files on disk. */
43 public class ModelFile {
44   public static final String LANGUAGE_INDEPENDENT = "*";
45 
46   @ModelTypeDef public final String modelType;
47   public final String absolutePath;
48   public final int version;
49   public final LocaleList supportedLocales;
50   public final boolean languageIndependent;
51   public final boolean isAsset;
52 
createFromRegularFile(File file, @ModelTypeDef String modelType)53   public static ModelFile createFromRegularFile(File file, @ModelTypeDef String modelType)
54       throws IOException {
55     ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
56     try (AssetFileDescriptor afd = new AssetFileDescriptor(pfd, 0, file.length())) {
57       return createFromAssetFileDescriptor(
58           file.getAbsolutePath(), modelType, afd, /* isAsset= */ false);
59     }
60   }
61 
createFromAsset( AssetManager assetManager, String absolutePath, @ModelTypeDef String modelType)62   public static ModelFile createFromAsset(
63       AssetManager assetManager, String absolutePath, @ModelTypeDef String modelType)
64       throws IOException {
65     try (AssetFileDescriptor assetFileDescriptor = assetManager.openFd(absolutePath)) {
66       return createFromAssetFileDescriptor(
67           absolutePath, modelType, assetFileDescriptor, /* isAsset= */ true);
68     }
69   }
70 
createFromAssetFileDescriptor( String absolutePath, @ModelTypeDef String modelType, AssetFileDescriptor assetFileDescriptor, boolean isAsset)71   private static ModelFile createFromAssetFileDescriptor(
72       String absolutePath,
73       @ModelTypeDef String modelType,
74       AssetFileDescriptor assetFileDescriptor,
75       boolean isAsset) {
76     ModelInfoFetcher modelInfoFetcher = ModelInfoFetcher.create(modelType);
77     return new ModelFile(
78         modelType,
79         absolutePath,
80         modelInfoFetcher.getVersion(assetFileDescriptor),
81         modelInfoFetcher.getSupportedLocales(assetFileDescriptor),
82         isAsset);
83   }
84 
85   @VisibleForTesting
ModelFile( @odelTypeDef String modelType, String absolutePath, int version, String supportedLocaleTags, boolean isAsset)86   public ModelFile(
87       @ModelTypeDef String modelType,
88       String absolutePath,
89       int version,
90       String supportedLocaleTags,
91       boolean isAsset) {
92     this.modelType = modelType;
93     this.absolutePath = absolutePath;
94     this.version = version;
95     this.languageIndependent = LANGUAGE_INDEPENDENT.equals(supportedLocaleTags);
96     this.supportedLocales =
97         languageIndependent
98             ? LocaleList.getEmptyLocaleList()
99             : LocaleList.forLanguageTags(supportedLocaleTags);
100     this.isAsset = isAsset;
101   }
102 
103   /** Returns if this model file is preferred to the given one. */
isPreferredTo(@ullable ModelFile model)104   public boolean isPreferredTo(@Nullable ModelFile model) {
105     // A model is preferred to no model.
106     if (model == null) {
107       return true;
108     }
109 
110     // A language-specific model is preferred to a language independent
111     // model.
112     if (!languageIndependent && model.languageIndependent) {
113       return true;
114     }
115     if (languageIndependent && !model.languageIndependent) {
116       return false;
117     }
118 
119     // A higher-version model is preferred.
120     if (version > model.version) {
121       return true;
122     }
123     return false;
124   }
125 
126   /** Returns whether the language supports any language in the given ranges. */
isAnyLanguageSupported(List<Locale.LanguageRange> languageRanges)127   public boolean isAnyLanguageSupported(List<Locale.LanguageRange> languageRanges) {
128     Preconditions.checkNotNull(languageRanges);
129     if (languageIndependent) {
130       return true;
131     }
132     List<String> supportedLocaleTags = Arrays.asList(supportedLocales.toLanguageTags().split(","));
133     return Locale.lookupTag(languageRanges, supportedLocaleTags) != null;
134   }
135 
open(AssetManager assetManager)136   public AssetFileDescriptor open(AssetManager assetManager) throws IOException {
137     if (isAsset) {
138       return assetManager.openFd(absolutePath);
139     }
140     File file = new File(absolutePath);
141     ParcelFileDescriptor parcelFileDescriptor =
142         ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
143     return new AssetFileDescriptor(parcelFileDescriptor, 0, file.length());
144   }
145 
canWrite()146   public boolean canWrite() {
147     if (isAsset) {
148       return false;
149     }
150     return new File(absolutePath).canWrite();
151   }
152 
delete()153   public boolean delete() {
154     if (isAsset) {
155       throw new IllegalStateException("asset is read-only, deleting it is not allowed.");
156     }
157     return new File(absolutePath).delete();
158   }
159 
160   @Override
equals(Object o)161   public boolean equals(Object o) {
162     if (this == o) {
163       return true;
164     }
165     if (!(o instanceof ModelFile)) {
166       return false;
167     }
168     ModelFile modelFile = (ModelFile) o;
169     return version == modelFile.version
170         && languageIndependent == modelFile.languageIndependent
171         && isAsset == modelFile.isAsset
172         && Objects.equals(modelType, modelFile.modelType)
173         && Objects.equals(absolutePath, modelFile.absolutePath)
174         && Objects.equals(supportedLocales, modelFile.supportedLocales);
175   }
176 
177   @Override
hashCode()178   public int hashCode() {
179     return Objects.hash(
180         modelType, absolutePath, version, supportedLocales, languageIndependent, isAsset);
181   }
182 
toModelInfo()183   public ModelInfo toModelInfo() {
184     return new ModelInfo(
185         version, languageIndependent ? LANGUAGE_INDEPENDENT : supportedLocales.toLanguageTags());
186   }
187 
188   @Override
toString()189   public String toString() {
190     return String.format(
191         Locale.US,
192         "ModelFile { type=%s path=%s version=%d locales=%s isAsset=%b}",
193         modelType,
194         absolutePath,
195         version,
196         languageIndependent ? LANGUAGE_INDEPENDENT : supportedLocales.toLanguageTags(),
197         isAsset);
198   }
199 
toModelInfos(Optional<ModelFile>.... modelFiles)200   public static ImmutableList<Optional<ModelInfo>> toModelInfos(Optional<ModelFile>... modelFiles) {
201     return Arrays.stream(modelFiles)
202         .map(modelFile -> modelFile.transform(ModelFile::toModelInfo))
203         .collect(Collectors.collectingAndThen(Collectors.toList(), ImmutableList::copyOf));
204   }
205 
206   /** Fetch metadata of a model file. */
207   private static class ModelInfoFetcher {
208     private final Function<AssetFileDescriptor, Integer> versionFetcher;
209     private final Function<AssetFileDescriptor, String> supportedLocalesFetcher;
210 
ModelInfoFetcher( Function<AssetFileDescriptor, Integer> versionFetcher, Function<AssetFileDescriptor, String> supportedLocalesFetcher)211     private ModelInfoFetcher(
212         Function<AssetFileDescriptor, Integer> versionFetcher,
213         Function<AssetFileDescriptor, String> supportedLocalesFetcher) {
214       this.versionFetcher = versionFetcher;
215       this.supportedLocalesFetcher = supportedLocalesFetcher;
216     }
217 
getVersion(AssetFileDescriptor assetFileDescriptor)218     int getVersion(AssetFileDescriptor assetFileDescriptor) {
219       return versionFetcher.apply(assetFileDescriptor);
220     }
221 
getSupportedLocales(AssetFileDescriptor assetFileDescriptor)222     String getSupportedLocales(AssetFileDescriptor assetFileDescriptor) {
223       return supportedLocalesFetcher.apply(assetFileDescriptor);
224     }
225 
create(@odelTypeDef String modelType)226     static ModelInfoFetcher create(@ModelTypeDef String modelType) {
227       switch (modelType) {
228         case ModelType.ANNOTATOR:
229           return new ModelInfoFetcher(AnnotatorModel::getVersion, AnnotatorModel::getLocales);
230         case ModelType.ACTIONS_SUGGESTIONS:
231           return new ModelInfoFetcher(
232               ActionsSuggestionsModel::getVersion, ActionsSuggestionsModel::getLocales);
233         case ModelType.LANG_ID:
234           return new ModelInfoFetcher(
235               LangIdModel::getVersion, afd -> ModelFile.LANGUAGE_INDEPENDENT);
236         default: // fall out
237       }
238       throw new IllegalStateException("Unsupported model types");
239     }
240   }
241 }
242