Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1c661d19 authored by Seigo Nonaka's avatar Seigo Nonaka Committed by Android (Google) Code Review
Browse files

Merge "Load font file from remote provider synchronously." into oc-dev

parents 57dc8328 d9de8be2
Loading
Loading
Loading
Loading
+68 −16
Original line number Diff line number Diff line
@@ -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,16 +358,51 @@ 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();
            }
        }
    }

    /**
     * Interface used to receive asynchronously fetched typefaces.
@@ -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,14 +697,20 @@ public class FontsContract {

            ByteBuffer buffer = null;
            try (final ParcelFileDescriptor pfd =
                    resolver.openFileDescriptor(uri, "r", cancellationSignal);
                    final FileInputStream fis = new FileInputStream(pfd.getFileDescriptor())) {
                    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
            }

            // TODO: try other approach?, e.g. read all contents instead of mmap.

+37 −9
Original line number Diff line number Diff line
@@ -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();
    }
}
+73 −9
Original line number Diff line number Diff line
@@ -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
+1 −1
Original line number Diff line number Diff line
@@ -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;
            }