blob: e931826d2455def66f9b1a24a71536e4270b62b3 [file] [log] [blame]
Clara Bayarrib0812a32016-10-20 10:42:13 +01001/*
2 * Copyright (C) 2017 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.provider;
17
Seigo Nonaka69754bf2017-04-04 17:27:52 -070018import android.annotation.IntDef;
19import android.annotation.IntRange;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
Clara Bayarrib0812a32016-10-20 10:42:13 +010022import android.content.ContentResolver;
23import android.content.ContentUris;
24import android.content.Context;
Clara Bayarri3c4be772017-02-07 15:33:40 +000025import android.content.pm.PackageInfo;
Clara Bayarrib0812a32016-10-20 10:42:13 +010026import android.content.pm.PackageManager;
Seigo Nonakaee4b6d82018-10-25 13:12:03 -070027import android.content.pm.PackageManager.NameNotFoundException;
Clara Bayarrib0812a32016-10-20 10:42:13 +010028import android.content.pm.ProviderInfo;
Clara Bayarri3c4be772017-02-07 15:33:40 +000029import android.content.pm.Signature;
Clara Bayarrib0812a32016-10-20 10:42:13 +010030import android.database.Cursor;
Clara Bayarri3c4be772017-02-07 15:33:40 +000031import android.graphics.Typeface;
Seigo Nonakaee4b6d82018-10-25 13:12:03 -070032import android.graphics.fonts.Font;
33import android.graphics.fonts.FontFamily;
34import android.graphics.fonts.FontStyle;
Seigo Nonaka69754bf2017-04-04 17:27:52 -070035import android.graphics.fonts.FontVariationAxis;
Clara Bayarrib0812a32016-10-20 10:42:13 +010036import android.net.Uri;
Seigo Nonaka848e83f2019-03-15 21:07:44 +000037import android.os.Build.VERSION_CODES;
Seigo Nonaka69754bf2017-04-04 17:27:52 -070038import android.os.CancellationSignal;
Clara Bayarrib0812a32016-10-20 10:42:13 +010039import android.os.Handler;
40import android.os.HandlerThread;
41import android.os.ParcelFileDescriptor;
42import android.os.Process;
Clara Bayarrib0812a32016-10-20 10:42:13 +010043import android.util.Log;
Seigo Nonakadaa8dfc2017-04-19 12:16:39 -070044import android.util.LruCache;
Clara Bayarrib0812a32016-10-20 10:42:13 +010045
46import com.android.internal.annotations.GuardedBy;
Clara Bayarri3c4be772017-02-07 15:33:40 +000047import com.android.internal.annotations.VisibleForTesting;
Seigo Nonaka69754bf2017-04-04 17:27:52 -070048import com.android.internal.util.Preconditions;
Clara Bayarrib0812a32016-10-20 10:42:13 +010049
Seigo Nonaka69754bf2017-04-04 17:27:52 -070050import java.io.FileInputStream;
Clara Bayarribacf2352017-02-10 15:44:35 +000051import java.io.IOException;
Seigo Nonaka69754bf2017-04-04 17:27:52 -070052import java.lang.annotation.Retention;
53import java.lang.annotation.RetentionPolicy;
54import java.nio.ByteBuffer;
55import java.nio.channels.FileChannel;
Clara Bayarrib0812a32016-10-20 10:42:13 +010056import java.util.ArrayList;
Clara Bayarrifb483cc2017-02-21 18:19:11 +000057import java.util.Arrays;
58import java.util.Collections;
59import java.util.Comparator;
Seigo Nonaka69754bf2017-04-04 17:27:52 -070060import java.util.HashMap;
Clara Bayarri3c4be772017-02-07 15:33:40 +000061import java.util.List;
Seigo Nonaka69754bf2017-04-04 17:27:52 -070062import java.util.Map;
Seigo Nonaka5a09c6432017-04-20 14:27:32 -070063import java.util.Set;
Seigo Nonakad9de8be22017-04-25 21:06:37 -070064import java.util.concurrent.TimeUnit;
Seigo Nonakaee4b6d82018-10-25 13:12:03 -070065import java.util.concurrent.atomic.AtomicBoolean;
66import java.util.concurrent.atomic.AtomicReference;
Seigo Nonakad9de8be22017-04-25 21:06:37 -070067import java.util.concurrent.locks.Condition;
68import java.util.concurrent.locks.Lock;
69import java.util.concurrent.locks.ReentrantLock;
Clara Bayarrib0812a32016-10-20 10:42:13 +010070
71/**
72 * Utility class to deal with Font ContentProviders.
73 */
74public class FontsContract {
75 private static final String TAG = "FontsContract";
76
77 /**
78 * Defines the constants used in a response from a Font Provider. The cursor returned from the
79 * query should have the ID column populated with the content uri ID for the resulting font.
80 * This should point to a real file or shared memory, as the client will mmap the given file
81 * descriptor. Pipes, sockets and other non-mmap-able file descriptors will fail to load in the
82 * client application.
83 */
84 public static final class Columns implements BaseColumns {
Clara Bayarri7fea2e22017-04-20 11:31:15 +010085
86 // Do not instantiate.
87 private Columns() {}
88
Clara Bayarrib0812a32016-10-20 10:42:13 +010089 /**
90 * Constant used to request data from a font provider. The cursor returned from the query
Seigo Nonaka43c20cf2017-04-10 11:03:24 -070091 * may populate this column with a long for the font file ID. The client will request a file
92 * descriptor to "file/FILE_ID" with this ID immediately under the top-level content URI. If
93 * not present, the client will request a file descriptor to the top-level URI with the
94 * given base font ID. Note that several results may return the same file ID, e.g. for TTC
95 * files with different indices.
96 */
97 public static final String FILE_ID = "file_id";
98 /**
99 * Constant used to request data from a font provider. The cursor returned from the query
Clara Bayarrib0812a32016-10-20 10:42:13 +0100100 * should have this column populated with an int for the ttc index for the resulting font.
101 */
102 public static final String TTC_INDEX = "font_ttc_index";
103 /**
104 * Constant used to request data from a font provider. The cursor returned from the query
105 * may populate this column with the font variation settings String information for the
106 * font.
107 */
108 public static final String VARIATION_SETTINGS = "font_variation_settings";
109 /**
Clara Bayarribacf2352017-02-10 15:44:35 +0000110 * Constant used to request data from a font provider. The cursor returned from the query
Seigo Nonakafe04aa82017-04-01 16:28:11 -0700111 * should have this column populated with the int weight for the resulting font. This value
112 * should be between 100 and 900. The most common values are 400 for regular weight and 700
113 * for bold weight.
114 */
115 public static final String WEIGHT = "font_weight";
116 /**
117 * Constant used to request data from a font provider. The cursor returned from the query
118 * should have this column populated with the int italic for the resulting font. This should
119 * be 0 for regular style and 1 for italic.
120 */
121 public static final String ITALIC = "font_italic";
122 /**
123 * Constant used to request data from a font provider. The cursor returned from the query
Clara Bayarribacf2352017-02-10 15:44:35 +0000124 * should have this column populated to indicate the result status of the
125 * query. This will be checked before any other data in the cursor. Possible values are
126 * {@link #RESULT_CODE_OK}, {@link #RESULT_CODE_FONT_NOT_FOUND},
Clara Bayarri5706a1b2017-04-12 11:29:19 +0100127 * {@link #RESULT_CODE_MALFORMED_QUERY} and {@link #RESULT_CODE_FONT_UNAVAILABLE} for system
128 * defined values. You may also define your own values in the 0x000010000..0xFFFF0000 range.
129 * If not present, {@link #RESULT_CODE_OK} will be assumed.
Clara Bayarribacf2352017-02-10 15:44:35 +0000130 */
131 public static final String RESULT_CODE = "result_code";
132
133 /**
134 * Constant used to represent a result was retrieved successfully. The given fonts will be
135 * attempted to retrieve immediately via
136 * {@link android.content.ContentProvider#openFile(Uri, String)}. See {@link #RESULT_CODE}.
137 */
138 public static final int RESULT_CODE_OK = 0;
139 /**
140 * Constant used to represent a result was not found. See {@link #RESULT_CODE}.
141 */
142 public static final int RESULT_CODE_FONT_NOT_FOUND = 1;
143 /**
144 * Constant used to represent a result was found, but cannot be provided at this moment. Use
145 * this to indicate, for example, that a font needs to be fetched from the network. See
146 * {@link #RESULT_CODE}.
147 */
148 public static final int RESULT_CODE_FONT_UNAVAILABLE = 2;
149 /**
150 * Constant used to represent that the query was not in a supported format by the provider.
151 * See {@link #RESULT_CODE}.
152 */
153 public static final int RESULT_CODE_MALFORMED_QUERY = 3;
Clara Bayarrib0812a32016-10-20 10:42:13 +0100154 }
155
Seigo Nonaka54084b62017-04-24 14:46:23 -0700156 private static final Object sLock = new Object();
157 @GuardedBy("sLock")
158 private static Handler sHandler;
159 @GuardedBy("sLock")
160 private static HandlerThread sThread;
161 @GuardedBy("sLock")
162 private static Set<String> sInQueueSet;
Clara Bayarrib0812a32016-10-20 10:42:13 +0100163
Seigo Nonaka54084b62017-04-24 14:46:23 -0700164 private volatile static Context sContext; // set once in setApplicationContextForResources
Clara Bayarrib0812a32016-10-20 10:42:13 +0100165
Seigo Nonakadaa8dfc2017-04-19 12:16:39 -0700166 private static final LruCache<String, Typeface> sTypefaceCache = new LruCache<>(16);
167
Seigo Nonaka54084b62017-04-24 14:46:23 -0700168 private FontsContract() {
169 }
170
Clara Bayarrib0812a32016-10-20 10:42:13 +0100171 /** @hide */
Seigo Nonaka54084b62017-04-24 14:46:23 -0700172 public static void setApplicationContextForResources(Context context) {
173 sContext = context.getApplicationContext();
Clara Bayarrib0812a32016-10-20 10:42:13 +0100174 }
175
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700176 /**
177 * Object represent a font entry in the family returned from {@link #fetchFonts}.
178 */
179 public static class FontInfo {
180 private final Uri mUri;
181 private final int mTtcIndex;
182 private final FontVariationAxis[] mAxes;
183 private final int mWeight;
184 private final boolean mItalic;
185 private final int mResultCode;
186
187 /**
188 * Creates a Font with all the information needed about a provided font.
189 * @param uri A URI associated to the font file.
190 * @param ttcIndex If providing a TTC_INDEX file, the index to point to. Otherwise, 0.
191 * @param axes If providing a variation font, the settings for it. May be null.
192 * @param weight An integer that indicates the font weight.
193 * @param italic A boolean that indicates the font is italic style or not.
194 * @param resultCode A boolean that indicates the font contents is ready.
195 */
196 /** @hide */
197 public FontInfo(@NonNull Uri uri, @IntRange(from = 0) int ttcIndex,
198 @Nullable FontVariationAxis[] axes, @IntRange(from = 1, to = 1000) int weight,
199 boolean italic, int resultCode) {
200 mUri = Preconditions.checkNotNull(uri);
201 mTtcIndex = ttcIndex;
202 mAxes = axes;
203 mWeight = weight;
204 mItalic = italic;
205 mResultCode = resultCode;
206 }
207
208 /**
209 * Returns a URI associated to this record.
210 */
211 public @NonNull Uri getUri() {
212 return mUri;
213 }
214
215 /**
216 * Returns the index to be used to access this font when accessing a TTC file.
217 */
218 public @IntRange(from = 0) int getTtcIndex() {
219 return mTtcIndex;
220 }
221
222 /**
223 * Returns the list of axes associated to this font.
224 */
225 public @Nullable FontVariationAxis[] getAxes() {
226 return mAxes;
227 }
228
229 /**
230 * Returns the weight value for this font.
231 */
232 public @IntRange(from = 1, to = 1000) int getWeight() {
233 return mWeight;
234 }
235
236 /**
237 * Returns whether this font is italic.
238 */
239 public boolean isItalic() {
240 return mItalic;
241 }
242
243 /**
244 * Returns result code.
245 *
246 * {@link FontsContract.Columns#RESULT_CODE}
247 */
248 public int getResultCode() {
249 return mResultCode;
250 }
251 }
252
253 /**
254 * Object returned from {@link #fetchFonts}.
255 */
256 public static class FontFamilyResult {
257 /**
258 * Constant represents that the font was successfully retrieved. Note that when this value
259 * is set and {@link #getFonts} returns an empty array, it means there were no fonts
260 * matching the given query.
261 */
262 public static final int STATUS_OK = 0;
263
264 /**
265 * Constant represents that the given certificate was not matched with the provider's
266 * signature. {@link #getFonts} returns null if this status was set.
267 */
268 public static final int STATUS_WRONG_CERTIFICATES = 1;
269
270 /**
271 * Constant represents that the provider returns unexpected data. {@link #getFonts} returns
272 * null if this status was set. For example, this value is set when the font provider
273 * gives invalid format of variation settings.
274 */
275 public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2;
276
Seigo Nonakaebecd7e2017-04-14 14:27:42 -0700277 /**
278 * Constant represents that the fetching font data was rejected by system. This happens if
279 * the passed context is restricted.
280 */
281 public static final int STATUS_REJECTED = 3;
282
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700283 /** @hide */
Jeff Sharkeyce8db992017-12-13 20:05:05 -0700284 @IntDef(prefix = { "STATUS_" }, value = {
285 STATUS_OK,
286 STATUS_WRONG_CERTIFICATES,
287 STATUS_UNEXPECTED_DATA_PROVIDED
288 })
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700289 @Retention(RetentionPolicy.SOURCE)
290 @interface FontResultStatus {}
291
292 private final @FontResultStatus int mStatusCode;
293 private final FontInfo[] mFonts;
294
295 /** @hide */
296 public FontFamilyResult(@FontResultStatus int statusCode, @Nullable FontInfo[] fonts) {
297 mStatusCode = statusCode;
298 mFonts = fonts;
299 }
300
301 public @FontResultStatus int getStatusCode() {
302 return mStatusCode;
303 }
304
305 public @NonNull FontInfo[] getFonts() {
306 return mFonts;
307 }
Clara Bayarri3c4be772017-02-07 15:33:40 +0000308 }
309
Seigo Nonaka54084b62017-04-24 14:46:23 -0700310 private static final int THREAD_RENEWAL_THRESHOLD_MS = 10000;
311
Seigo Nonakad9de8be22017-04-25 21:06:37 -0700312 private static final long SYNC_FONT_FETCH_TIMEOUT_MS = 500;
313
Clara Bayarrib0812a32016-10-20 10:42:13 +0100314 // We use a background thread to post the content resolving work for all requests on. This
315 // thread should be quit/stopped after all requests are done.
Seigo Nonaka54084b62017-04-24 14:46:23 -0700316 // TODO: Factor out to other class. Consider to switch MessageQueue.IdleHandler.
317 private static final Runnable sReplaceDispatcherThreadRunnable = new Runnable() {
Clara Bayarrib0812a32016-10-20 10:42:13 +0100318 @Override
319 public void run() {
Seigo Nonaka54084b62017-04-24 14:46:23 -0700320 synchronized (sLock) {
321 if (sThread != null) {
322 sThread.quitSafely();
323 sThread = null;
324 sHandler = null;
Clara Bayarrib0812a32016-10-20 10:42:13 +0100325 }
326 }
327 }
328 };
329
Clara Bayarribacf2352017-02-10 15:44:35 +0000330 /** @hide */
Seigo Nonakad9de8be22017-04-25 21:06:37 -0700331 public static Typeface getFontSync(FontRequest request) {
Seigo Nonaka5a09c6432017-04-20 14:27:32 -0700332 final String id = request.getIdentifier();
333 Typeface cachedTypeface = sTypefaceCache.get(id);
334 if (cachedTypeface != null) {
335 return cachedTypeface;
336 }
337
338 // Unfortunately the typeface is not available at this time, but requesting from the font
339 // provider takes too much time. For now, request the font data to ensure it is in the cache
340 // next time and return.
Seigo Nonaka54084b62017-04-24 14:46:23 -0700341 synchronized (sLock) {
342 if (sHandler == null) {
343 sThread = new HandlerThread("fonts", Process.THREAD_PRIORITY_BACKGROUND);
344 sThread.start();
345 sHandler = new Handler(sThread.getLooper());
Clara Bayarrib0812a32016-10-20 10:42:13 +0100346 }
Seigo Nonakad9de8be22017-04-25 21:06:37 -0700347 final Lock lock = new ReentrantLock();
348 final Condition cond = lock.newCondition();
349 final AtomicReference<Typeface> holder = new AtomicReference<>();
350 final AtomicBoolean waiting = new AtomicBoolean(true);
351 final AtomicBoolean timeout = new AtomicBoolean(false);
352
Seigo Nonaka54084b62017-04-24 14:46:23 -0700353 sHandler.post(() -> {
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700354 try {
Seigo Nonaka54084b62017-04-24 14:46:23 -0700355 FontFamilyResult result = fetchFonts(sContext, null, request);
Seigo Nonaka5a09c6432017-04-20 14:27:32 -0700356 if (result.getStatusCode() == FontFamilyResult.STATUS_OK) {
Seigo Nonaka54084b62017-04-24 14:46:23 -0700357 Typeface typeface = buildTypeface(sContext, null, result.getFonts());
Seigo Nonaka5a09c6432017-04-20 14:27:32 -0700358 if (typeface != null) {
359 sTypefaceCache.put(id, typeface);
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700360 }
Seigo Nonakad9de8be22017-04-25 21:06:37 -0700361 holder.set(typeface);
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700362 }
Seigo Nonaka5a09c6432017-04-20 14:27:32 -0700363 } catch (NameNotFoundException e) {
364 // Ignore.
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700365 }
Seigo Nonakad9de8be22017-04-25 21:06:37 -0700366 lock.lock();
367 try {
368 if (!timeout.get()) {
369 waiting.set(false);
370 cond.signal();
371 }
372 } finally {
373 lock.unlock();
374 }
Clara Bayarrib0812a32016-10-20 10:42:13 +0100375 });
Seigo Nonaka54084b62017-04-24 14:46:23 -0700376 sHandler.removeCallbacks(sReplaceDispatcherThreadRunnable);
377 sHandler.postDelayed(sReplaceDispatcherThreadRunnable, THREAD_RENEWAL_THRESHOLD_MS);
Seigo Nonakad9de8be22017-04-25 21:06:37 -0700378
379 long remaining = TimeUnit.MILLISECONDS.toNanos(SYNC_FONT_FETCH_TIMEOUT_MS);
380 lock.lock();
381 try {
382 if (!waiting.get()) {
383 return holder.get();
384 }
385 for (;;) {
386 try {
387 remaining = cond.awaitNanos(remaining);
388 } catch (InterruptedException e) {
389 // do nothing.
390 }
391 if (!waiting.get()) {
392 return holder.get();
393 }
394 if (remaining <= 0) {
395 timeout.set(true);
396 Log.w(TAG, "Remote font fetch timed out: " +
397 request.getProviderAuthority() + "/" + request.getQuery());
398 return null;
399 }
400 }
401 } finally {
402 lock.unlock();
403 }
Clara Bayarrib0812a32016-10-20 10:42:13 +0100404 }
405 }
406
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700407 /**
Seigo Nonaka0b73a422017-04-11 15:59:58 -0700408 * Interface used to receive asynchronously fetched typefaces.
409 */
410 public static class FontRequestCallback {
411 /**
412 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
413 * provider was not found on the device.
414 */
Seigo Nonaka54084b62017-04-24 14:46:23 -0700415 public static final int FAIL_REASON_PROVIDER_NOT_FOUND = -1;
Seigo Nonaka0b73a422017-04-11 15:59:58 -0700416 /**
417 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
418 * provider must be authenticated and the given certificates do not match its signature.
419 */
Seigo Nonaka54084b62017-04-24 14:46:23 -0700420 public static final int FAIL_REASON_WRONG_CERTIFICATES = -2;
Seigo Nonaka0b73a422017-04-11 15:59:58 -0700421 /**
422 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
423 * returned by the provider was not loaded properly.
424 */
425 public static final int FAIL_REASON_FONT_LOAD_ERROR = -3;
426 /**
427 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
428 * provider did not return any results for the given query.
429 */
430 public static final int FAIL_REASON_FONT_NOT_FOUND = Columns.RESULT_CODE_FONT_NOT_FOUND;
431 /**
432 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
433 * provider found the queried font, but it is currently unavailable.
434 */
435 public static final int FAIL_REASON_FONT_UNAVAILABLE = Columns.RESULT_CODE_FONT_UNAVAILABLE;
436 /**
437 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
438 * query was not supported by the provider.
439 */
440 public static final int FAIL_REASON_MALFORMED_QUERY = Columns.RESULT_CODE_MALFORMED_QUERY;
441
442 /** @hide */
Jeff Sharkeyce8db992017-12-13 20:05:05 -0700443 @IntDef(prefix = { "FAIL_" }, value = {
444 FAIL_REASON_PROVIDER_NOT_FOUND,
445 FAIL_REASON_FONT_LOAD_ERROR,
446 FAIL_REASON_FONT_NOT_FOUND,
447 FAIL_REASON_FONT_UNAVAILABLE,
448 FAIL_REASON_MALFORMED_QUERY
449 })
Seigo Nonaka0b73a422017-04-11 15:59:58 -0700450 @Retention(RetentionPolicy.SOURCE)
451 @interface FontRequestFailReason {}
452
453 public FontRequestCallback() {}
454
455 /**
Jeff Sharkey67f9d502017-08-05 13:49:13 -0600456 * Called then a Typeface request done via {@link #requestFonts} is complete. Note that this
Seigo Nonaka5a09c6432017-04-20 14:27:32 -0700457 * method will not be called if {@link #onTypefaceRequestFailed(int)} is called instead.
Seigo Nonaka0b73a422017-04-11 15:59:58 -0700458 * @param typeface The Typeface object retrieved.
459 */
460 public void onTypefaceRetrieved(Typeface typeface) {}
461
462 /**
Jeff Sharkey67f9d502017-08-05 13:49:13 -0600463 * Called when a Typeface request done via {@link #requestFonts}} fails.
Seigo Nonaka0b73a422017-04-11 15:59:58 -0700464 * @param reason One of {@link #FAIL_REASON_PROVIDER_NOT_FOUND},
465 * {@link #FAIL_REASON_FONT_NOT_FOUND},
466 * {@link #FAIL_REASON_FONT_LOAD_ERROR},
467 * {@link #FAIL_REASON_FONT_UNAVAILABLE} or
Clara Bayarri5706a1b2017-04-12 11:29:19 +0100468 * {@link #FAIL_REASON_MALFORMED_QUERY} if returned by the system. May also be
469 * a positive value greater than 0 defined by the font provider as an
470 * additional error code. Refer to the provider's documentation for more
471 * information on possible returned error codes.
Seigo Nonaka0b73a422017-04-11 15:59:58 -0700472 */
473 public void onTypefaceRequestFailed(@FontRequestFailReason int reason) {}
474 }
475
476 /**
477 * Create a typeface object given a font request. The font will be asynchronously fetched,
478 * therefore the result is delivered to the given callback. See {@link FontRequest}.
479 * Only one of the methods in callback will be invoked, depending on whether the request
480 * succeeds or fails. These calls will happen on the caller thread.
Seigo Nonakadaa8dfc2017-04-19 12:16:39 -0700481 *
482 * Note that the result Typeface may be cached internally and the same instance will be returned
483 * the next time you call this method with the same request. If you want to bypass this cache,
484 * use {@link #fetchFonts} and {@link #buildTypeface} instead.
485 *
Seigo Nonaka0b73a422017-04-11 15:59:58 -0700486 * @param context A context to be used for fetching from font provider.
487 * @param request A {@link FontRequest} object that identifies the provider and query for the
488 * request. May not be null.
Seigo Nonaka0b73a422017-04-11 15:59:58 -0700489 * @param handler A handler to be processed the font fetching.
Seigo Nonaka8ea62b02017-04-25 19:39:23 -0700490 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. If
491 * the operation is canceled, then {@link
492 * android.os.OperationCanceledException} will be thrown.
493 * @param callback A callback that will be triggered when results are obtained. May not be null.
Seigo Nonaka0b73a422017-04-11 15:59:58 -0700494 */
Seigo Nonaka8ea62b02017-04-25 19:39:23 -0700495 public static void requestFonts(@NonNull Context context, @NonNull FontRequest request,
496 @NonNull Handler handler, @Nullable CancellationSignal cancellationSignal,
497 @NonNull FontRequestCallback callback) {
Seigo Nonaka0b73a422017-04-11 15:59:58 -0700498
499 final Handler callerThreadHandler = new Handler();
Seigo Nonakadaa8dfc2017-04-19 12:16:39 -0700500 final Typeface cachedTypeface = sTypefaceCache.get(request.getIdentifier());
501 if (cachedTypeface != null) {
502 callerThreadHandler.post(() -> callback.onTypefaceRetrieved(cachedTypeface));
503 return;
504 }
505
Seigo Nonaka0b73a422017-04-11 15:59:58 -0700506 handler.post(() -> {
Seigo Nonaka0b73a422017-04-11 15:59:58 -0700507 FontFamilyResult result;
508 try {
Seigo Nonaka8ea62b02017-04-25 19:39:23 -0700509 result = fetchFonts(context, cancellationSignal, request);
Seigo Nonaka0b73a422017-04-11 15:59:58 -0700510 } catch (NameNotFoundException e) {
511 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
512 FontRequestCallback.FAIL_REASON_PROVIDER_NOT_FOUND));
513 return;
514 }
515
Seigo Nonakadaa8dfc2017-04-19 12:16:39 -0700516 // Same request might be dispatched during fetchFonts. Check the cache again.
517 final Typeface anotherCachedTypeface = sTypefaceCache.get(request.getIdentifier());
518 if (anotherCachedTypeface != null) {
519 callerThreadHandler.post(() -> callback.onTypefaceRetrieved(anotherCachedTypeface));
520 return;
521 }
522
Seigo Nonaka0b73a422017-04-11 15:59:58 -0700523 if (result.getStatusCode() != FontFamilyResult.STATUS_OK) {
524 switch (result.getStatusCode()) {
525 case FontFamilyResult.STATUS_WRONG_CERTIFICATES:
526 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
527 FontRequestCallback.FAIL_REASON_WRONG_CERTIFICATES));
528 return;
529 case FontFamilyResult.STATUS_UNEXPECTED_DATA_PROVIDED:
530 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
531 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR));
532 return;
533 default:
534 // fetchFont returns unexpected status type. Fallback to load error.
535 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
536 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR));
537 return;
538 }
539 }
540
541 final FontInfo[] fonts = result.getFonts();
542 if (fonts == null || fonts.length == 0) {
543 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
544 FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND));
545 return;
546 }
547 for (final FontInfo font : fonts) {
548 if (font.getResultCode() != Columns.RESULT_CODE_OK) {
549 // We proceed if all font entry is ready to use. Otherwise report the first
550 // error.
551 final int resultCode = font.getResultCode();
552 if (resultCode < 0) {
553 // Negative values are reserved for internal errors. Fallback to load error.
554 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
555 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR));
556 } else {
557 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
558 resultCode));
559 }
560 return;
561 }
562 }
563
Seigo Nonaka8ea62b02017-04-25 19:39:23 -0700564 final Typeface typeface = buildTypeface(context, cancellationSignal, fonts);
Seigo Nonaka0b73a422017-04-11 15:59:58 -0700565 if (typeface == null) {
566 // Something went wrong during reading font files. This happens if the given font
567 // file is an unsupported font type.
568 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
569 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR));
570 return;
571 }
572
Seigo Nonakadaa8dfc2017-04-19 12:16:39 -0700573 sTypefaceCache.put(request.getIdentifier(), typeface);
Seigo Nonaka0b73a422017-04-11 15:59:58 -0700574 callerThreadHandler.post(() -> callback.onTypefaceRetrieved(typeface));
575 });
576 }
577
578 /**
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700579 * Fetch fonts given a font request.
580 *
581 * @param context A {@link Context} to be used for fetching fonts.
582 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. If
583 * the operation is canceled, then {@link
584 * android.os.OperationCanceledException} will be thrown when the
585 * query is executed.
586 * @param request A {@link FontRequest} object that identifies the provider and query for the
587 * request.
588 *
589 * @return {@link FontFamilyResult}
590 *
591 * @throws NameNotFoundException If requested package or authority was not found in system.
592 */
593 public static @NonNull FontFamilyResult fetchFonts(
594 @NonNull Context context, @Nullable CancellationSignal cancellationSignal,
595 @NonNull FontRequest request) throws NameNotFoundException {
Seigo Nonakaebecd7e2017-04-14 14:27:42 -0700596 if (context.isRestricted()) {
597 // TODO: Should we allow if the peer process is system or myself?
598 return new FontFamilyResult(FontFamilyResult.STATUS_REJECTED, null);
599 }
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700600 ProviderInfo providerInfo = getProvider(context.getPackageManager(), request);
601 if (providerInfo == null) {
602 return new FontFamilyResult(FontFamilyResult.STATUS_WRONG_CERTIFICATES, null);
603
604 }
605 try {
606 FontInfo[] fonts = getFontFromProvider(
607 context, request, providerInfo.authority, cancellationSignal);
608 return new FontFamilyResult(FontFamilyResult.STATUS_OK, fonts);
Seigo Nonaka0f07dd52017-04-28 12:53:31 -0700609 } catch (IllegalArgumentException e) {
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700610 return new FontFamilyResult(FontFamilyResult.STATUS_UNEXPECTED_DATA_PROVIDED, null);
611 }
612 }
613
614 /**
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700615 * Build a Typeface from an array of {@link FontInfo}
616 *
617 * Results that are marked as not ready will be skipped.
618 *
619 * @param context A {@link Context} that will be used to fetch the font contents.
620 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. If
621 * the operation is canceled, then {@link
622 * android.os.OperationCanceledException} will be thrown.
623 * @param fonts An array of {@link FontInfo} to be used to create a Typeface.
624 * @return A Typeface object. Returns null if typeface creation fails.
625 */
626 public static Typeface buildTypeface(@NonNull Context context,
627 @Nullable CancellationSignal cancellationSignal, @NonNull FontInfo[] fonts) {
Seigo Nonakaebecd7e2017-04-14 14:27:42 -0700628 if (context.isRestricted()) {
629 // TODO: Should we allow if the peer process is system or myself?
630 return null;
631 }
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700632 final Map<Uri, ByteBuffer> uriBuffer =
633 prepareFontData(context, fonts, cancellationSignal);
Seigo Nonakad9de8be22017-04-25 21:06:37 -0700634 if (uriBuffer.isEmpty()) {
635 return null;
636 }
Seigo Nonakaee4b6d82018-10-25 13:12:03 -0700637
638 FontFamily.Builder familyBuilder = null;
639 for (FontInfo fontInfo : fonts) {
640 final ByteBuffer buffer = uriBuffer.get(fontInfo.getUri());
641 if (buffer == null) {
642 continue;
643 }
644 try {
Seigo Nonaka848e83f2019-03-15 21:07:44 +0000645 final Font font = new Font.Builder(buffer)
Seigo Nonakaee4b6d82018-10-25 13:12:03 -0700646 .setWeight(fontInfo.getWeight())
647 .setSlant(fontInfo.isItalic()
648 ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT)
649 .setTtcIndex(fontInfo.getTtcIndex())
650 .setFontVariationSettings(fontInfo.getAxes())
651 .build();
652 if (familyBuilder == null) {
653 familyBuilder = new FontFamily.Builder(font);
654 } else {
Seigo Nonaka2a424a72019-01-11 11:36:20 -0800655 try {
656 familyBuilder.addFont(font);
657 } catch (IllegalArgumentException e) {
Seigo Nonaka848e83f2019-03-15 21:07:44 +0000658 if (context.getApplicationInfo().targetSdkVersion <= VERSION_CODES.P) {
659 // Surpress the IllegalArgumentException for keeping the backward
660 // compatibility.
661 continue;
662 }
663 throw e;
Seigo Nonaka2a424a72019-01-11 11:36:20 -0800664 }
Seigo Nonakaee4b6d82018-10-25 13:12:03 -0700665 }
666 } catch (IOException e) {
667 continue;
668 }
669 }
670 if (familyBuilder == null) {
671 return null;
672 }
Seigo Nonaka68d1d5a2018-12-03 19:48:49 -0800673
674 final FontFamily family = familyBuilder.build();
675
676 final FontStyle normal = new FontStyle(FontStyle.FONT_WEIGHT_NORMAL,
677 FontStyle.FONT_SLANT_UPRIGHT);
678 Font bestFont = family.getFont(0);
679 int bestScore = normal.getMatchScore(bestFont.getStyle());
680 for (int i = 1; i < family.getSize(); ++i) {
681 final Font candidate = family.getFont(i);
682 final int score = normal.getMatchScore(candidate.getStyle());
683 if (score < bestScore) {
684 bestFont = candidate;
685 bestScore = score;
686 }
687 }
688 return new Typeface.CustomFallbackBuilder(family).setStyle(bestFont.getStyle()).build();
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700689 }
690
691 /**
692 * A helper function to create a mapping from {@link Uri} to {@link ByteBuffer}.
693 *
694 * Skip if the file contents is not ready to be read.
695 *
696 * @param context A {@link Context} to be used for resolving content URI in
697 * {@link FontInfo}.
698 * @param fonts An array of {@link FontInfo}.
699 * @return A map from {@link Uri} to {@link ByteBuffer}.
700 */
701 private static Map<Uri, ByteBuffer> prepareFontData(Context context, FontInfo[] fonts,
702 CancellationSignal cancellationSignal) {
703 final HashMap<Uri, ByteBuffer> out = new HashMap<>();
704 final ContentResolver resolver = context.getContentResolver();
705
706 for (FontInfo font : fonts) {
707 if (font.getResultCode() != Columns.RESULT_CODE_OK) {
708 continue;
709 }
710
711 final Uri uri = font.getUri();
712 if (out.containsKey(uri)) {
713 continue;
714 }
715
716 ByteBuffer buffer = null;
717 try (final ParcelFileDescriptor pfd =
Seigo Nonakad9de8be22017-04-25 21:06:37 -0700718 resolver.openFileDescriptor(uri, "r", cancellationSignal)) {
719 if (pfd != null) {
720 try (final FileInputStream fis =
721 new FileInputStream(pfd.getFileDescriptor())) {
722 final FileChannel fileChannel = fis.getChannel();
723 final long size = fileChannel.size();
724 buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);
725 } catch (IOException e) {
726 // ignore
727 }
728 }
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700729 } catch (IOException e) {
730 // ignore
731 }
732
733 // TODO: try other approach?, e.g. read all contents instead of mmap.
734
735 out.put(uri, buffer);
736 }
737 return Collections.unmodifiableMap(out);
738 }
739
Clara Bayarri3c4be772017-02-07 15:33:40 +0000740 /** @hide */
741 @VisibleForTesting
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700742 public static @Nullable ProviderInfo getProvider(
743 PackageManager packageManager, FontRequest request) throws NameNotFoundException {
Clara Bayarri3c4be772017-02-07 15:33:40 +0000744 String providerAuthority = request.getProviderAuthority();
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700745 ProviderInfo info = packageManager.resolveContentProvider(providerAuthority, 0);
Clara Bayarri3c4be772017-02-07 15:33:40 +0000746 if (info == null) {
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700747 throw new NameNotFoundException("No package found for authority: " + providerAuthority);
Clara Bayarri3c4be772017-02-07 15:33:40 +0000748 }
749
750 if (!info.packageName.equals(request.getProviderPackage())) {
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700751 throw new NameNotFoundException("Found content provider " + providerAuthority
752 + ", but package was not " + request.getProviderPackage());
Clara Bayarri3c4be772017-02-07 15:33:40 +0000753 }
754 // Trust system apps without signature checks
755 if (info.applicationInfo.isSystemApp()) {
756 return info;
757 }
758
Clara Bayarrifb483cc2017-02-21 18:19:11 +0000759 List<byte[]> signatures;
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700760 PackageInfo packageInfo = packageManager.getPackageInfo(info.packageName,
761 PackageManager.GET_SIGNATURES);
762 signatures = convertToByteArrayList(packageInfo.signatures);
763 Collections.sort(signatures, sByteArrayComparator);
764
Clara Bayarri3c4be772017-02-07 15:33:40 +0000765 List<List<byte[]>> requestCertificatesList = request.getCertificates();
766 for (int i = 0; i < requestCertificatesList.size(); ++i) {
Clara Bayarrifb483cc2017-02-21 18:19:11 +0000767 // Make a copy so we can sort it without modifying the incoming data.
768 List<byte[]> requestSignatures = new ArrayList<>(requestCertificatesList.get(i));
769 Collections.sort(requestSignatures, sByteArrayComparator);
770 if (equalsByteArrayList(signatures, requestSignatures)) {
Clara Bayarri3c4be772017-02-07 15:33:40 +0000771 return info;
772 }
773 }
Clara Bayarri3c4be772017-02-07 15:33:40 +0000774 return null;
775 }
776
Clara Bayarrifb483cc2017-02-21 18:19:11 +0000777 private static final Comparator<byte[]> sByteArrayComparator = (l, r) -> {
778 if (l.length != r.length) {
779 return l.length - r.length;
780 }
781 for (int i = 0; i < l.length; ++i) {
782 if (l[i] != r[i]) {
783 return l[i] - r[i];
784 }
785 }
786 return 0;
787 };
788
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700789 private static boolean equalsByteArrayList(
790 List<byte[]> signatures, List<byte[]> requestSignatures) {
Clara Bayarrifb483cc2017-02-21 18:19:11 +0000791 if (signatures.size() != requestSignatures.size()) {
792 return false;
793 }
794 for (int i = 0; i < signatures.size(); ++i) {
795 if (!Arrays.equals(signatures.get(i), requestSignatures.get(i))) {
796 return false;
797 }
798 }
799 return true;
800 }
801
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700802 private static List<byte[]> convertToByteArrayList(Signature[] signatures) {
Clara Bayarrifb483cc2017-02-21 18:19:11 +0000803 List<byte[]> shas = new ArrayList<>();
Clara Bayarri3c4be772017-02-07 15:33:40 +0000804 for (int i = 0; i < signatures.length; ++i) {
805 shas.add(signatures[i].toByteArray());
806 }
807 return shas;
808 }
809
Clara Bayarri3c4be772017-02-07 15:33:40 +0000810 /** @hide */
811 @VisibleForTesting
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700812 public static @NonNull FontInfo[] getFontFromProvider(
813 Context context, FontRequest request, String authority,
Seigo Nonaka0f07dd52017-04-28 12:53:31 -0700814 CancellationSignal cancellationSignal) {
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700815 ArrayList<FontInfo> result = new ArrayList<>();
Seigo Nonaka43c20cf2017-04-10 11:03:24 -0700816 final Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
Clara Bayarri3c4be772017-02-07 15:33:40 +0000817 .authority(authority)
Clara Bayarrib0812a32016-10-20 10:42:13 +0100818 .build();
Seigo Nonaka43c20cf2017-04-10 11:03:24 -0700819 final Uri fileBaseUri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
820 .authority(authority)
821 .appendPath("file")
822 .build();
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700823 try (Cursor cursor = context.getContentResolver().query(uri, new String[] { Columns._ID,
Seigo Nonaka43c20cf2017-04-10 11:03:24 -0700824 Columns.FILE_ID, Columns.TTC_INDEX, Columns.VARIATION_SETTINGS,
Seigo Nonaka54084b62017-04-24 14:46:23 -0700825 Columns.WEIGHT, Columns.ITALIC, Columns.RESULT_CODE },
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700826 "query = ?", new String[] { request.getQuery() }, null, cancellationSignal);) {
Clara Bayarrib0812a32016-10-20 10:42:13 +0100827 // TODO: Should we restrict the amount of fonts that can be returned?
828 // TODO: Write documentation explaining that all results should be from the same family.
829 if (cursor != null && cursor.getCount() > 0) {
Clara Bayarribacf2352017-02-10 15:44:35 +0000830 final int resultCodeColumnIndex = cursor.getColumnIndex(Columns.RESULT_CODE);
Clara Bayarrib0812a32016-10-20 10:42:13 +0100831 result = new ArrayList<>();
Clara Bayarribacf2352017-02-10 15:44:35 +0000832 final int idColumnIndex = cursor.getColumnIndexOrThrow(Columns._ID);
Seigo Nonaka43c20cf2017-04-10 11:03:24 -0700833 final int fileIdColumnIndex = cursor.getColumnIndex(Columns.FILE_ID);
Clara Bayarrib0812a32016-10-20 10:42:13 +0100834 final int ttcIndexColumnIndex = cursor.getColumnIndex(Columns.TTC_INDEX);
835 final int vsColumnIndex = cursor.getColumnIndex(Columns.VARIATION_SETTINGS);
Seigo Nonakafe04aa82017-04-01 16:28:11 -0700836 final int weightColumnIndex = cursor.getColumnIndex(Columns.WEIGHT);
837 final int italicColumnIndex = cursor.getColumnIndex(Columns.ITALIC);
Clara Bayarrib0812a32016-10-20 10:42:13 +0100838 while (cursor.moveToNext()) {
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700839 int resultCode = resultCodeColumnIndex != -1
Clara Bayarribacf2352017-02-10 15:44:35 +0000840 ? cursor.getInt(resultCodeColumnIndex) : Columns.RESULT_CODE_OK;
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700841 final int ttcIndex = ttcIndexColumnIndex != -1
842 ? cursor.getInt(ttcIndexColumnIndex) : 0;
843 final String variationSettings = vsColumnIndex != -1
844 ? cursor.getString(vsColumnIndex) : null;
845
Seigo Nonaka43c20cf2017-04-10 11:03:24 -0700846 Uri fileUri;
847 if (fileIdColumnIndex == -1) {
848 long id = cursor.getLong(idColumnIndex);
849 fileUri = ContentUris.withAppendedId(uri, id);
850 } else {
851 long id = cursor.getLong(fileIdColumnIndex);
852 fileUri = ContentUris.withAppendedId(fileBaseUri, id);
853 }
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700854 int weight;
855 boolean italic;
856 if (weightColumnIndex != -1 && italicColumnIndex != -1) {
857 weight = cursor.getInt(weightColumnIndex);
858 italic = cursor.getInt(italicColumnIndex) == 1;
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700859 } else {
860 weight = Typeface.Builder.NORMAL_WEIGHT;
861 italic = false;
Clara Bayarrib0812a32016-10-20 10:42:13 +0100862 }
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700863 FontVariationAxis[] axes =
864 FontVariationAxis.fromFontVariationSettings(variationSettings);
865 result.add(new FontInfo(fileUri, ttcIndex, axes, weight, italic, resultCode));
Clara Bayarrib0812a32016-10-20 10:42:13 +0100866 }
867 }
868 }
Seigo Nonaka69754bf2017-04-04 17:27:52 -0700869 return result.toArray(new FontInfo[0]);
Clara Bayarrib0812a32016-10-20 10:42:13 +0100870 }
871}