Commit 78f2ec8b authored by Natalie Chouinard's avatar Natalie Chouinard Committed by Commit Bot

[GoogleSans] Implement getUniqueNameLookupTable

Implement programmatic font fetch (in addition to the existing XML
prefetch) to respond to requests from the renderer for which fonts are
expected to be available locally on-device from GMS Core.

Bug: 1111148
Change-Id: I4443114c0b04f8c46d48576fe42df00bbb198617
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2404954
Commit-Queue: Natalie Chouinard <chouinard@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarDominik Röttsches <drott@chromium.org>
Reviewed-by: default avatarSky Malice <skym@chromium.org>
Cr-Commit-Position: refs/heads/master@{#806845}
parent 978a780d
...@@ -16,7 +16,6 @@ import androidx.core.provider.FontsContractCompat; ...@@ -16,7 +16,6 @@ import androidx.core.provider.FontsContractCompat;
import androidx.core.provider.FontsContractCompat.FontFamilyResult; import androidx.core.provider.FontsContractCompat.FontFamilyResult;
import androidx.core.provider.FontsContractCompat.FontInfo; import androidx.core.provider.FontsContractCompat.FontInfo;
import org.chromium.base.Consumer;
import org.chromium.base.ContextUtils; import org.chromium.base.ContextUtils;
import org.chromium.base.Log; import org.chromium.base.Log;
import org.chromium.base.task.PostTask; import org.chromium.base.task.PostTask;
...@@ -24,14 +23,18 @@ import org.chromium.base.task.TaskTraits; ...@@ -24,14 +23,18 @@ import org.chromium.base.task.TaskTraits;
import org.chromium.blink.mojom.AndroidFontLookup; import org.chromium.blink.mojom.AndroidFontLookup;
import org.chromium.content.R; import org.chromium.content.R;
import org.chromium.mojo.bindings.ExecutorFactory; import org.chromium.mojo.bindings.ExecutorFactory;
import org.chromium.mojo.system.Core;
import org.chromium.mojo.system.MojoException; import org.chromium.mojo.system.MojoException;
import org.chromium.mojo.system.impl.CoreImpl; import org.chromium.mojo.system.impl.CoreImpl;
import org.chromium.mojo_base.mojom.File; import org.chromium.mojo_base.mojom.File;
import org.chromium.services.service_manager.InterfaceFactory; import org.chromium.services.service_manager.InterfaceFactory;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
/** /**
...@@ -45,7 +48,10 @@ public class AndroidFontLookupImpl implements AndroidFontLookup { ...@@ -45,7 +48,10 @@ public class AndroidFontLookupImpl implements AndroidFontLookup {
private final Context mAppContext; private final Context mAppContext;
private FontsContractWrapper mFontsContract = new FontsContractWrapper(); private FontsContractWrapper mFontsContract = new FontsContractWrapper();
/** /**
* Map from ICU case folded full font name to GMS Core font provider query format. * Map from ICU case folded full font names to corresponding GMS Core font provider query.
*
* This collection of Android Downloadable fonts should match the fonts listed in
* preloaded_fonts.xml.
*/ */
private Map<String, String> mFullFontNameToQuery = createFullFontNameToQueryMap(); private Map<String, String> mFullFontNameToQuery = createFullFontNameToQueryMap();
...@@ -54,86 +60,109 @@ public class AndroidFontLookupImpl implements AndroidFontLookup { ...@@ -54,86 +60,109 @@ public class AndroidFontLookupImpl implements AndroidFontLookup {
} }
/** /**
* Synchronously gets a list of unique font names that are available from GMS Core and can be * Verifies which fonts are available from GMS Core and can be fetched quickly, and
* fetched quickly. * asynchronously responds with that list. These fonts should have already been preloaded via
* the "preloaded_fonts" AndroidManifest directive. This second programmatic prefetch request is
* necessary to confirm whether those fonts were successfully downloaded and are now available.
*
* TODO(chouinard): Consider requiring the returned list to be sorted.
* *
* @param callback The callback to be called with the list of font names. * @param callback The callback to be called with the list of available fonts.
*/ */
@Override @Override
public void getUniqueNameLookupTable(GetUniqueNameLookupTableResponse callback) { public void getUniqueNameLookupTable(GetUniqueNameLookupTableResponse callback) {
// TODO(crbug.com/1111148): Verify which fonts were successfully preloaded and are available // Get executor associated with the current thread for running Mojo callback.
// on-device to populate this list. Executor executor = ExecutorFactory.getExecutorForCurrentThread(CoreImpl.getInstance());
String[] results = new String[0];
callback.call(results); PostTask.postTask(TaskTraits.BEST_EFFORT, () -> {
Set<String> expectedFonts = mFullFontNameToQuery.keySet();
List<String> availableFonts = new ArrayList<>();
for (String fontName : expectedFonts) {
if (tryFetchFont(fontName) != null) {
availableFonts.add(fontName);
}
}
String[] results = availableFonts.toArray(new String[availableFonts.size()]);
executor.execute(() -> callback.call(results));
});
} }
/** /**
* Fetches the requested font from GMS Core on a background thread. * Fetches the requested font from GMS Core on a background thread.
* *
* @param fontUniqueName The unique full font name requested. * @param fontUniqueName The unique full font name requested.
* @param callback The callback to be called with the resulting font file handle, or null if the * @param callback The callback to be called with the resulting opened font file handle, or null
* font file is not available. * if the font file is not available. Caller is responsible for closing file when done.
*/ */
@Override @Override
public void matchLocalFontByUniqueName( public void matchLocalFontByUniqueName(
String fontUniqueName, MatchLocalFontByUniqueNameResponse callback) { String fontUniqueName, MatchLocalFontByUniqueNameResponse callback) {
// Get executor associated with the current thread for running Mojo callback.
Core core = CoreImpl.getInstance();
Executor executor = ExecutorFactory.getExecutorForCurrentThread(core);
// Post synchronous font request to background worker thread.
PostTask.postTask(TaskTraits.USER_BLOCKING, () -> {
ParcelFileDescriptor fileDescriptor = tryFetchFont(fontUniqueName);
if (fileDescriptor == null) {
executor.execute(() -> callback.call(null));
return;
}
// Wrap file descriptor as an opened Mojo file handle.
File file = new File();
file.fd = core.wrapFileDescriptor(fileDescriptor);
file.async = false;
executor.execute(() -> callback.call(file));
});
}
private ParcelFileDescriptor tryFetchFont(String fontUniqueName) {
String query = mFullFontNameToQuery.get(fontUniqueName); String query = mFullFontNameToQuery.get(fontUniqueName);
if (query == null) { if (query == null) {
Log.d(TAG, "Query format not found for full font name: %s.", fontUniqueName); Log.d(TAG, "Query format not found for full font name: %s", fontUniqueName);
callback.call(null); return null;
return;
} }
FontRequest request = new FontRequest("com.google.android.gms.fonts", FontRequest request = new FontRequest("com.google.android.gms.fonts",
"com.google.android.gms", query, R.array.com_google_android_gms_fonts_certs); "com.google.android.gms", query, R.array.com_google_android_gms_fonts_certs);
// Get executor associated with the current thread for running Mojo callback.
Executor executor = ExecutorFactory.getExecutorForCurrentThread(CoreImpl.getInstance());
Consumer<File> consumer = (File file) -> {
executor.execute(() -> callback.call(file));
};
// Execute synchronous font request on background worker thread.
PostTask.postTask(TaskTraits.USER_BLOCKING, () -> { tryFetchFont(request, consumer); });
}
private void tryFetchFont(FontRequest request, Consumer<File> consumer) {
try { try {
FontFamilyResult fontFamilyResult = FontFamilyResult fontFamilyResult =
mFontsContract.fetchFonts(mAppContext, null, request); mFontsContract.fetchFonts(mAppContext, null, request);
if (fontFamilyResult.getStatusCode() != FontFamilyResult.STATUS_OK) { if (fontFamilyResult.getStatusCode() != FontFamilyResult.STATUS_OK) {
Log.d(TAG, "Font fetch failed with status code %d.", Log.d(TAG, "Font fetch failed with status code: %d",
fontFamilyResult.getStatusCode()); fontFamilyResult.getStatusCode());
consumer.accept(null); return null;
return;
} }
FontInfo[] fontInfos = fontFamilyResult.getFonts(); FontInfo[] fontInfos = fontFamilyResult.getFonts();
if (fontInfos.length != 1) { if (fontInfos.length != 1) {
Log.d(TAG, "Font fetch did not return a unique result: length = %d", Log.d(TAG, "Font fetch did not return a unique result: length = %d",
fontInfos.length); fontInfos.length);
consumer.accept(null); return null;
return;
} }
FontInfo fontInfo = fontInfos[0]; FontInfo fontInfo = fontInfos[0];
if (fontInfo.getResultCode() != FontsContractCompat.Columns.RESULT_CODE_OK) { if (fontInfo.getResultCode() != FontsContractCompat.Columns.RESULT_CODE_OK) {
Log.d(TAG, "Returned font has failed status code: %d.", fontInfo.getResultCode()); Log.d(TAG, "Returned font has failed status code: %d", fontInfo.getResultCode());
consumer.accept(null); return null;
return;
} }
ContentResolver contentResolver = mAppContext.getContentResolver(); ContentResolver contentResolver = mAppContext.getContentResolver();
ParcelFileDescriptor fileDescriptor = ParcelFileDescriptor fileDescriptor =
contentResolver.openFileDescriptor(fontInfo.getUri(), READ_ONLY_MODE); contentResolver.openFileDescriptor(fontInfo.getUri(), READ_ONLY_MODE);
File file = new File(); if (fileDescriptor == null) {
file.fd = CoreImpl.getInstance().wrapFileDescriptor(fileDescriptor); Log.d(TAG, "Unable to open font file at: %s", fontInfo.getUri());
file.async = false; }
consumer.accept(file); return fileDescriptor;
} catch (NameNotFoundException | IOException | OutOfMemoryError e) { } catch (NameNotFoundException | IOException | OutOfMemoryError e) {
Log.d(TAG, "Failed to get font with: %s", e.toString()); Log.d(TAG, "Failed to get font with: %s", e.toString());
consumer.accept(null); return null;
} }
} }
......
...@@ -4,8 +4,7 @@ ...@@ -4,8 +4,7 @@
package org.chromium.content.browser.font; package org.chromium.content.browser.font;
import static junit.framework.Assert.assertEquals; import static org.mockito.AdditionalMatchers.aryEq;
import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.ArgumentMatchers.isNull;
...@@ -43,6 +42,7 @@ import org.mockito.stubbing.OngoingStubbing; ...@@ -43,6 +42,7 @@ import org.mockito.stubbing.OngoingStubbing;
import org.chromium.base.test.BaseJUnit4ClassRunner; import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.blink.mojom.AndroidFontLookup; import org.chromium.blink.mojom.AndroidFontLookup;
import org.chromium.blink.mojom.AndroidFontLookup.GetUniqueNameLookupTableResponse;
import org.chromium.blink.mojom.AndroidFontLookup.MatchLocalFontByUniqueNameResponse; import org.chromium.blink.mojom.AndroidFontLookup.MatchLocalFontByUniqueNameResponse;
import org.chromium.content_public.browser.test.NativeLibraryTestUtils; import org.chromium.content_public.browser.test.NativeLibraryTestUtils;
import org.chromium.content_public.browser.test.util.CriteriaHelper; import org.chromium.content_public.browser.test.util.CriteriaHelper;
...@@ -57,7 +57,9 @@ public final class AndroidFontLookupImplTest { ...@@ -57,7 +57,9 @@ public final class AndroidFontLookupImplTest {
private static final String FONT_QUERY = "name=Foo&weight=400"; private static final String FONT_QUERY = "name=Foo&weight=400";
private static final String AUTHORITY = "com.google.android.gms.fonts"; private static final String AUTHORITY = "com.google.android.gms.fonts";
private static final Uri URI = Uri.parse("content://com.google.android.gms.fonts/123"); private static final Uri URI = Uri.parse("content://com.google.android.gms.fonts/123");
private static final Uri URI2 = Uri.parse("content://com.google.android.gms.fonts/456");
private static final int FD = 42; private static final int FD = 42;
private static final int FD2 = 43;
private static final long RUN_LOOP_TIMEOUT_MS = 50; private static final long RUN_LOOP_TIMEOUT_MS = 50;
@Rule @Rule
...@@ -67,8 +69,12 @@ public final class AndroidFontLookupImplTest { ...@@ -67,8 +69,12 @@ public final class AndroidFontLookupImplTest {
private FontsContractWrapper mMockFontsContractWrapper; private FontsContractWrapper mMockFontsContractWrapper;
@Mock @Mock
private ParcelFileDescriptor mMockFileDescriptor; private ParcelFileDescriptor mMockFileDescriptor;
@Mock
private ParcelFileDescriptor mMockFileDescriptor2;
private Context mMockContext; private Context mMockContext;
@Mock
private GetUniqueNameLookupTableResponse mGetUniqueNameLookupTableCallback;
@Mock @Mock
private MatchLocalFontByUniqueNameResponse mMatchLocalFontByUniqueNameCallback; private MatchLocalFontByUniqueNameResponse mMatchLocalFontByUniqueNameCallback;
...@@ -83,11 +89,17 @@ public final class AndroidFontLookupImplTest { ...@@ -83,11 +89,17 @@ public final class AndroidFontLookupImplTest {
MockContentResolver resolver = new MockContentResolver(); MockContentResolver resolver = new MockContentResolver();
MockContext mockContext = new MockContext(); MockContext mockContext = new MockContext();
when(mMockFileDescriptor.detachFd()).thenReturn(FD); when(mMockFileDescriptor.detachFd()).thenReturn(FD);
when(mMockFileDescriptor2.detachFd()).thenReturn(FD2);
resolver.addProvider(AUTHORITY, new MockContentProvider(mockContext) { resolver.addProvider(AUTHORITY, new MockContentProvider(mockContext) {
@Override @Override
public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts) { public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts) {
assertEquals(URI, url); if (url.equals(URI)) {
return new AssetFileDescriptor(mMockFileDescriptor, 0, -1); return new AssetFileDescriptor(mMockFileDescriptor, 0, -1);
} else if (url.equals(URI2)) {
return new AssetFileDescriptor(mMockFileDescriptor2, 0, -1);
} else {
return null;
}
} }
}); });
mMockContext = new IsolatedContext(resolver, mockContext); mMockContext = new IsolatedContext(resolver, mockContext);
...@@ -98,10 +110,75 @@ public final class AndroidFontLookupImplTest { ...@@ -98,10 +110,75 @@ public final class AndroidFontLookupImplTest {
ImmutableMap.of(FULL_FONT_NAME, FONT_QUERY)); ImmutableMap.of(FULL_FONT_NAME, FONT_QUERY));
} }
@SmallTest
@Test
public void testGetUniqueNameLookupTable_Empty() throws NameNotFoundException {
String[] expected = new String[0];
FontFamilyResult result = new FontFamilyResult(FontFamilyResult.STATUS_OK, new FontInfo[0]);
whenFetchFontsWith(FONT_QUERY).thenReturn(result);
mAndroidFontLookup.getUniqueNameLookupTable(mGetUniqueNameLookupTableCallback);
mMojoTestRule.runLoop(RUN_LOOP_TIMEOUT_MS);
verify(mGetUniqueNameLookupTableCallback).call(aryEq(expected));
}
@SmallTest
@Test
public void testGetUniqueNameLookupTable_Available() throws NameNotFoundException {
String[] expected = new String[] {FULL_FONT_NAME};
FontInfo fontInfo = new FontInfo(URI, 0, 400, false, Columns.RESULT_CODE_OK);
FontFamilyResult result =
new FontFamilyResult(FontFamilyResult.STATUS_OK, new FontInfo[] {fontInfo});
whenFetchFontsWith(FONT_QUERY).thenReturn(result);
mAndroidFontLookup.getUniqueNameLookupTable(mGetUniqueNameLookupTableCallback);
mMojoTestRule.runLoop(RUN_LOOP_TIMEOUT_MS);
verify(mGetUniqueNameLookupTableCallback).call(aryEq(expected));
}
@SmallTest
@Test
public void testGetUniqueNameLookupTable_MultipleFonts() throws NameNotFoundException {
String fullFontName2 = "bar";
String fontQuery2 = "name=Bar&weight=400";
String fullFontName3 = "bar bold";
String fontQuery3 = "name=Bar&weight=700";
String[] expected = new String[] {FULL_FONT_NAME, fullFontName2};
mAndroidFontLookup.setFullFontNameToQueryMapForTest(ImmutableMap.of(
FULL_FONT_NAME, FONT_QUERY, fullFontName2, fontQuery2, fullFontName3, fontQuery3));
// Foo is available.
FontInfo fontInfo = new FontInfo(URI, 0, 400, false, Columns.RESULT_CODE_OK);
FontFamilyResult result =
new FontFamilyResult(FontFamilyResult.STATUS_OK, new FontInfo[] {fontInfo});
whenFetchFontsWith(FONT_QUERY).thenReturn(result);
// Bar is available.
FontInfo fontInfo2 = new FontInfo(URI2, 0, 400, false, Columns.RESULT_CODE_OK);
FontFamilyResult result2 =
new FontFamilyResult(FontFamilyResult.STATUS_OK, new FontInfo[] {fontInfo2});
whenFetchFontsWith(fontQuery2).thenReturn(result2);
// Bar Bold is not available.
FontFamilyResult result3 =
new FontFamilyResult(FontFamilyResult.STATUS_OK, new FontInfo[0]);
whenFetchFontsWith(fontQuery3).thenReturn(result3);
mAndroidFontLookup.getUniqueNameLookupTable(mGetUniqueNameLookupTableCallback);
mMojoTestRule.runLoop(RUN_LOOP_TIMEOUT_MS);
verify(mGetUniqueNameLookupTableCallback).call(aryEq(expected));
}
@SmallTest @SmallTest
@Test @Test
public void testMatchLocalFontByUniqueName_UnsupportedFontName() { public void testMatchLocalFontByUniqueName_UnsupportedFontName() {
mAndroidFontLookup.matchLocalFontByUniqueName("Bar", mMatchLocalFontByUniqueNameCallback); mAndroidFontLookup.matchLocalFontByUniqueName("bar", mMatchLocalFontByUniqueNameCallback);
mMojoTestRule.runLoop(RUN_LOOP_TIMEOUT_MS); mMojoTestRule.runLoop(RUN_LOOP_TIMEOUT_MS);
verify(mMatchLocalFontByUniqueNameCallback, verify(mMatchLocalFontByUniqueNameCallback,
......
...@@ -11,8 +11,6 @@ import "mojo/public/mojom/base/file.mojom"; ...@@ -11,8 +11,6 @@ import "mojo/public/mojom/base/file.mojom";
interface AndroidFontLookup { interface AndroidFontLookup {
// Returns a list of ICU case folded full font names available to be fetched // Returns a list of ICU case folded full font names available to be fetched
// locally from on-device storage, without a network roundtrip. // locally from on-device storage, without a network roundtrip.
// TODO(crbug.com/1111148): Complete implementation of this method in
// AndroidFontLookupImpl.java. Currently returns empty list.
GetUniqueNameLookupTable() GetUniqueNameLookupTable()
=> (array<string> available_unique_font_names); => (array<string> available_unique_font_names);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment