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

Commit 6c0dd10a authored by Seigo Nonaka's avatar Seigo Nonaka Committed by android-build-merger
Browse files

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

am: 1c661d19

Change-Id: I29b540d331a40ad47ad0561ecefb1269cb76a188
parents 927353aa 1c661d19
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;
            }