summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/provider/FontsContract.java84
-rw-r--r--core/tests/coretests/src/android/provider/FontsContractE2ETest.java46
-rw-r--r--core/tests/coretests/src/android/provider/MockFontProvider.java82
-rw-r--r--graphics/java/android/graphics/Typeface.java2
4 files changed, 179 insertions, 35 deletions
diff --git a/core/java/android/provider/FontsContract.java b/core/java/android/provider/FontsContract.java
index 4621763df5a7..f6ad17d24dc2 100644
--- a/core/java/android/provider/FontsContract.java
+++ b/core/java/android/provider/FontsContract.java
@@ -65,6 +65,12 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
/**
* Utility class to deal with Font ContentProviders.
@@ -303,6 +309,8 @@ public class FontsContract {
private static final int THREAD_RENEWAL_THRESHOLD_MS = 10000;
+ private static final long SYNC_FONT_FETCH_TIMEOUT_MS = 500;
+
// We use a background thread to post the content resolving work for all requests on. This
// thread should be quit/stopped after all requests are done.
// TODO: Factor out to other class. Consider to switch MessageQueue.IdleHandler.
@@ -314,14 +322,13 @@ public class FontsContract {
sThread.quitSafely();
sThread = null;
sHandler = null;
- sInQueueSet = null;
}
}
}
};
/** @hide */
- public static Typeface getFontOrWarmUpCache(FontRequest request) {
+ public static Typeface getFontSync(FontRequest request) {
final String id = request.getIdentifier();
Typeface cachedTypeface = sTypefaceCache.get(id);
if (cachedTypeface != null) {
@@ -336,16 +343,14 @@ public class FontsContract {
sThread = new HandlerThread("fonts", Process.THREAD_PRIORITY_BACKGROUND);
sThread.start();
sHandler = new Handler(sThread.getLooper());
- sInQueueSet = new ArraySet<>();
- }
- if (sInQueueSet.contains(id)) {
- return null; // Already requested.
}
- sInQueueSet.add(id);
+ final Lock lock = new ReentrantLock();
+ final Condition cond = lock.newCondition();
+ final AtomicReference<Typeface> holder = new AtomicReference<>();
+ final AtomicBoolean waiting = new AtomicBoolean(true);
+ final AtomicBoolean timeout = new AtomicBoolean(false);
+
sHandler.post(() -> {
- synchronized (sLock) {
- sInQueueSet.remove(id);
- }
try {
FontFamilyResult result = fetchFonts(sContext, null, request);
if (result.getStatusCode() == FontFamilyResult.STATUS_OK) {
@@ -353,15 +358,50 @@ public class FontsContract {
if (typeface != null) {
sTypefaceCache.put(id, typeface);
}
+ holder.set(typeface);
}
} catch (NameNotFoundException e) {
// Ignore.
}
+ lock.lock();
+ try {
+ if (!timeout.get()) {
+ waiting.set(false);
+ cond.signal();
+ }
+ } finally {
+ lock.unlock();
+ }
});
sHandler.removeCallbacks(sReplaceDispatcherThreadRunnable);
sHandler.postDelayed(sReplaceDispatcherThreadRunnable, THREAD_RENEWAL_THRESHOLD_MS);
+
+ long remaining = TimeUnit.MILLISECONDS.toNanos(SYNC_FONT_FETCH_TIMEOUT_MS);
+ lock.lock();
+ try {
+ if (!waiting.get()) {
+ return holder.get();
+ }
+ for (;;) {
+ try {
+ remaining = cond.awaitNanos(remaining);
+ } catch (InterruptedException e) {
+ // do nothing.
+ }
+ if (!waiting.get()) {
+ return holder.get();
+ }
+ if (remaining <= 0) {
+ timeout.set(true);
+ Log.w(TAG, "Remote font fetch timed out: " +
+ request.getProviderAuthority() + "/" + request.getQuery());
+ return null;
+ }
+ }
+ } finally {
+ lock.unlock();
+ }
}
- return null;
}
/**
@@ -594,6 +634,9 @@ public class FontsContract {
}
final Map<Uri, ByteBuffer> uriBuffer =
prepareFontData(context, fonts, cancellationSignal);
+ if (uriBuffer.isEmpty()) {
+ return null;
+ }
return new Typeface.Builder(fonts, uriBuffer)
.setFallback(fallbackFontName)
.setWeight(weight)
@@ -621,6 +664,9 @@ public class FontsContract {
}
final Map<Uri, ByteBuffer> uriBuffer =
prepareFontData(context, fonts, cancellationSignal);
+ if (uriBuffer.isEmpty()) {
+ return null;
+ }
return new Typeface.Builder(fonts, uriBuffer).build();
}
@@ -651,11 +697,17 @@ public class FontsContract {
ByteBuffer buffer = null;
try (final ParcelFileDescriptor pfd =
- resolver.openFileDescriptor(uri, "r", cancellationSignal);
- final FileInputStream fis = new FileInputStream(pfd.getFileDescriptor())) {
- final FileChannel fileChannel = fis.getChannel();
- final long size = fileChannel.size();
- buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);
+ resolver.openFileDescriptor(uri, "r", cancellationSignal)) {
+ if (pfd != null) {
+ try (final FileInputStream fis =
+ new FileInputStream(pfd.getFileDescriptor())) {
+ final FileChannel fileChannel = fis.getChannel();
+ final long size = fileChannel.size();
+ buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);
+ } catch (IOException e) {
+ // ignore
+ }
+ }
} catch (IOException e) {
// ignore
}
diff --git a/core/tests/coretests/src/android/provider/FontsContractE2ETest.java b/core/tests/coretests/src/android/provider/FontsContractE2ETest.java
index 78c7f4d8716b..51916ffce726 100644
--- a/core/tests/coretests/src/android/provider/FontsContractE2ETest.java
+++ b/core/tests/coretests/src/android/provider/FontsContractE2ETest.java
@@ -18,27 +18,28 @@ package android.provider;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
import android.app.Instrumentation;
-import android.content.pm.Signature;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PackageInfo;
import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
import android.graphics.Typeface;
import android.graphics.fonts.FontRequest;
-import android.provider.FontsContract;
+import android.os.Handler;
+import android.provider.FontsContract.Columns;
import android.provider.FontsContract.FontFamilyResult;
import android.provider.FontsContract.FontInfo;
-import android.provider.FontsContract.Columns;
+import android.provider.FontsContract;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import android.os.Handler;
-import java.util.List;
import java.util.ArrayList;
+import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -172,4 +173,31 @@ public class FontsContractE2ETest {
// Neighter fetchFonts nor buildTypeface should cache the Typeface.
assertNotSame(typeface, typeface2);
}
+
+ @Test
+ public void typefaceNullFdTest() throws NameNotFoundException {
+ Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+ Context ctx = inst.getTargetContext();
+
+ FontRequest request = new FontRequest(
+ AUTHORITY, PACKAGE, MockFontProvider.NULL_FD_QUERY, SIGNATURE);
+ FontFamilyResult result = FontsContract.fetchFonts(
+ ctx, null /* cancellation signal */, request);
+ assertNull(FontsContract.buildTypeface(
+ ctx, null /* cancellation signal */, result.getFonts()));
+ }
+
+ @Test
+ public void getFontSyncTest() {
+ FontRequest request = new FontRequest(AUTHORITY, PACKAGE, "singleFontFamily", SIGNATURE);
+ assertNotNull(FontsContract.getFontSync(request));
+ }
+
+ @Test
+ public void getFontSyncTest_timeout() {
+ FontRequest request = new FontRequest(
+ AUTHORITY, PACKAGE, MockFontProvider.BLOCKING_QUERY, SIGNATURE);
+ assertNull(FontsContract.getFontSync(request));
+ MockFontProvider.unblock();
+ }
}
diff --git a/core/tests/coretests/src/android/provider/MockFontProvider.java b/core/tests/coretests/src/android/provider/MockFontProvider.java
index 339d5c3f7fa0..ad5b130ca90d 100644
--- a/core/tests/coretests/src/android/provider/MockFontProvider.java
+++ b/core/tests/coretests/src/android/provider/MockFontProvider.java
@@ -29,26 +29,73 @@ import android.graphics.fonts.FontVariationAxis;
import android.net.Uri;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
+import android.util.ArraySet;
import android.util.SparseArray;
-import java.util.Collections;
-import java.util.Map;
-import java.util.HashMap;
import java.io.File;
-import java.nio.file.Files;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
-import java.io.FileNotFoundException;
+import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import com.android.internal.annotations.GuardedBy;
public class MockFontProvider extends ContentProvider {
final static String AUTHORITY = "android.provider.fonts.font";
+ private static final long BLOCKING_TIMEOUT_MS = 10000; // 10 sec
+ private static final Lock sLock = new ReentrantLock();
+ private static final Condition sCond = sLock.newCondition();
+ @GuardedBy("sLock")
+ private static boolean sSignaled;
+
+ private static void blockUntilSignal() {
+ long remaining = TimeUnit.MILLISECONDS.toNanos(BLOCKING_TIMEOUT_MS);
+ sLock.lock();
+ try {
+ sSignaled = false;
+ while (!sSignaled) {
+ try {
+ remaining = sCond.awaitNanos(remaining);
+ } catch (InterruptedException e) {
+ // do nothing.
+ }
+ if (sSignaled) {
+ return;
+ }
+ if (remaining <= 0) {
+ // Timed out
+ throw new RuntimeException("Timeout during waiting");
+ }
+ }
+ } finally {
+ sLock.unlock();
+ }
+ }
+
+ public static void unblock() {
+ sLock.lock();
+ try {
+ sSignaled = true;
+ sCond.signal();
+ } finally {
+ sLock.unlock();
+ }
+ }
+
final static String[] FONT_FILES = {
"samplefont1.ttf",
};
+ private static final int NO_FILE_ID = 255;
private static final int SAMPLE_FONT_FILE_0_ID = 0;
- private static final int SAMPLE_FONT_FILE_1_ID = 1;
static class Font {
public Font(int id, int fileId, int ttcIndex, String varSettings, int weight, int italic,
@@ -99,6 +146,9 @@ public class MockFontProvider extends ContentProvider {
private int mResultCode;
};
+ public static final String BLOCKING_QUERY = "queryBlockingQuery";
+ public static final String NULL_FD_QUERY = "nullFdQuery";
+
private static Map<String, Font[]> QUERY_MAP;
static {
HashMap<String, Font[]> map = new HashMap<>();
@@ -112,6 +162,14 @@ public class MockFontProvider extends ContentProvider {
new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, null, 700, 0, Columns.RESULT_CODE_OK),
});
+ map.put(BLOCKING_QUERY, new Font[] {
+ new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, null, 700, 0, Columns.RESULT_CODE_OK),
+ });
+
+ map.put(NULL_FD_QUERY, new Font[] {
+ new Font(id++, NO_FILE_ID, 0, null, 700, 0, Columns.RESULT_CODE_OK),
+ });
+
QUERY_MAP = Collections.unmodifiableMap(map);
}
@@ -160,12 +218,14 @@ public class MockFontProvider extends ContentProvider {
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) {
final int id = (int)ContentUris.parseId(uri);
+ if (id == NO_FILE_ID) {
+ return null;
+ }
final File targetFile = getCopiedFile(getContext(), FONT_FILES[id]);
try {
return ParcelFileDescriptor.open(targetFile, ParcelFileDescriptor.MODE_READ_ONLY);
} catch (FileNotFoundException e) {
- throw new RuntimeException(
- "Failed to found font file. You might forget call prepareFontFiles in setUp");
+ return null;
}
}
@@ -182,7 +242,11 @@ public class MockFontProvider extends ContentProvider {
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
- return buildCursor(QUERY_MAP.get(selectionArgs[0]));
+ final String query = selectionArgs[0];
+ if (query.equals(BLOCKING_QUERY)) {
+ blockUntilSignal();
+ }
+ return buildCursor(QUERY_MAP.get(query));
}
@Override
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index aedf6a723861..60b1542671f4 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -218,7 +218,7 @@ public class Typeface {
// default font instead (nothing we can do now).
FontRequest request = new FontRequest(providerEntry.getAuthority(),
providerEntry.getPackage(), providerEntry.getQuery(), certs);
- Typeface typeface = FontsContract.getFontOrWarmUpCache(request);
+ Typeface typeface = FontsContract.getFontSync(request);
return typeface == null ? DEFAULT : typeface;
}