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

Commit 5b96e55d authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Cache the Typeface based on the FontRequest." into oc-dev

parents 435f1dc7 daa8dfc6
Loading
Loading
Loading
Loading
+22 −1
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.ResultReceiver;
import android.util.Log;
import android.util.LruCache;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -181,6 +182,8 @@ public class FontsContract {
    @GuardedBy("mLock")
    private HandlerThread mThread;

    private static final LruCache<String, Typeface> sTypefaceCache = new LruCache<>(16);

    /** @hide */
    public FontsContract(Context context) {
        mContext = context.getApplicationContext();
@@ -476,6 +479,11 @@ public class FontsContract {
     * therefore the result is delivered to the given callback. See {@link FontRequest}.
     * Only one of the methods in callback will be invoked, depending on whether the request
     * succeeds or fails. These calls will happen on the caller thread.
     *
     * Note that the result Typeface may be cached internally and the same instance will be returned
     * the next time you call this method with the same request. If you want to bypass this cache,
     * use {@link #fetchFonts} and {@link #buildTypeface} instead.
     *
     * @param context A context to be used for fetching from font provider.
     * @param request A {@link FontRequest} object that identifies the provider and query for the
     *                request. May not be null.
@@ -486,8 +494,13 @@ public class FontsContract {
            @NonNull FontRequestCallback callback, @NonNull Handler handler) {

        final Handler callerThreadHandler = new Handler();
        final Typeface cachedTypeface = sTypefaceCache.get(request.getIdentifier());
        if (cachedTypeface != null) {
            callerThreadHandler.post(() -> callback.onTypefaceRetrieved(cachedTypeface));
            return;
        }

        handler.post(() -> {
            // TODO: Cache the result.
            FontFamilyResult result;
            try {
                result = fetchFonts(context, null /* cancellation signal */, request);
@@ -497,6 +510,13 @@ public class FontsContract {
                return;
            }

            // Same request might be dispatched during fetchFonts. Check the cache again.
            final Typeface anotherCachedTypeface = sTypefaceCache.get(request.getIdentifier());
            if (anotherCachedTypeface != null) {
                callerThreadHandler.post(() -> callback.onTypefaceRetrieved(anotherCachedTypeface));
                return;
            }

            if (result.getStatusCode() != FontFamilyResult.STATUS_OK) {
                switch (result.getStatusCode()) {
                    case FontFamilyResult.STATUS_WRONG_CERTIFICATES:
@@ -547,6 +567,7 @@ public class FontsContract {
                return;
            }

            sTypefaceCache.put(request.getIdentifier(), typeface);
            callerThreadHandler.post(() -> callback.onTypefaceRetrieved(typeface));
        });
    }
+5 −0
Original line number Diff line number Diff line
@@ -1266,6 +1266,11 @@
            <meta-data android:name="com.android.frameworks.coretests.reference" android:resource="@xml/metadata" />
        </provider>

        <provider android:name="android.provider.MockFontProvider"
                  android:authorities="android.provider.fonts.font"
                  android:exported="false"
                  android:multiprocess="true" />

        <!-- Application components used for content tests -->
        <provider android:name="android.content.MemoryFileProvider"
                android:authorities="android.content.MemoryFileProvider"
+696 B

File added.

No diff preview for this file type.

+177 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright (C) 2017 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">

  <GlyphOrder>
    <GlyphID id="0" name=".notdef"/>
    <GlyphID id="1" name="a"/>
  </GlyphOrder>

  <head>
    <tableVersion value="1.0"/>
    <fontRevision value="1.0"/>
    <checkSumAdjustment value="0x640cdb2f"/>
    <magicNumber value="0x5f0f3cf5"/>
    <flags value="00000000 00000011"/>
    <unitsPerEm value="1000"/>
    <created value="Fri Mar 17 07:26:00 2017"/>
    <macStyle value="00000000 00000000"/>
    <lowestRecPPEM value="7"/>
    <fontDirectionHint value="2"/>
    <glyphDataFormat value="0"/>
  </head>

  <hhea>
    <tableVersion value="1.0"/>
    <ascent value="1000"/>
    <descent value="-200"/>
    <lineGap value="0"/>
    <caretSlopeRise value="1"/>
    <caretSlopeRun value="0"/>
    <caretOffset value="0"/>
    <reserved0 value="0"/>
    <reserved1 value="0"/>
    <reserved2 value="0"/>
    <reserved3 value="0"/>
    <metricDataFormat value="0"/>
  </hhea>

  <maxp>
    <tableVersion value="0x10000"/>
    <maxZones value="0"/>
    <maxTwilightPoints value="0"/>
    <maxStorage value="0"/>
    <maxFunctionDefs value="0"/>
    <maxInstructionDefs value="0"/>
    <maxStackElements value="0"/>
    <maxSizeOfInstructions value="0"/>
    <maxComponentElements value="0"/>
  </maxp>

  <OS_2>
    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
         will be recalculated by the compiler -->
    <version value="3"/>
    <xAvgCharWidth value="594"/>
    <usWeightClass value="400"/>
    <usWidthClass value="5"/>
    <fsType value="00000000 00001000"/>
    <ySubscriptXSize value="650"/>
    <ySubscriptYSize value="600"/>
    <ySubscriptXOffset value="0"/>
    <ySubscriptYOffset value="75"/>
    <ySuperscriptXSize value="650"/>
    <ySuperscriptYSize value="600"/>
    <ySuperscriptXOffset value="0"/>
    <ySuperscriptYOffset value="350"/>
    <yStrikeoutSize value="50"/>
    <yStrikeoutPosition value="300"/>
    <sFamilyClass value="0"/>
    <panose>
      <bFamilyType value="0"/>
      <bSerifStyle value="0"/>
      <bWeight value="5"/>
      <bProportion value="0"/>
      <bContrast value="0"/>
      <bStrokeVariation value="0"/>
      <bArmStyle value="0"/>
      <bLetterForm value="0"/>
      <bMidline value="0"/>
      <bXHeight value="0"/>
    </panose>
    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
    <achVendID value="UKWN"/>
    <fsSelection value="00000000 01000000"/>
    <usFirstCharIndex value="32"/>
    <usLastCharIndex value="122"/>
    <sTypoAscender value="800"/>
    <sTypoDescender value="-200"/>
    <sTypoLineGap value="200"/>
    <usWinAscent value="1000"/>
    <usWinDescent value="200"/>
    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
    <sxHeight value="500"/>
    <sCapHeight value="700"/>
    <usDefaultChar value="0"/>
    <usBreakChar value="32"/>
    <usMaxContext value="0"/>
  </OS_2>

  <hmtx>
    <mtx name=".notdef" width="500" lsb="93"/>
    <mtx name="a" width="500" lsb="93"/>
  </hmtx>

  <cmap>
    <tableVersion version="0"/>
    <cmap_format_4 platformID="3" platEncID="10" language="0">
      <map code="0x0061" name="a" />
    </cmap_format_4>
  </cmap>

  <loca>
    <!-- The 'loca' table will be calculated by the compiler -->
  </loca>

  <glyf>
    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
    <TTGlyph name="a" xMin="0" yMin="0" xMax="0" yMax="0" />
  </glyf>

  <name>
    <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
      Sample Font
    </namerecord>
    <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
      Regular
    </namerecord>
    <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
      Sample Font
    </namerecord>
    <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
      SampleFont-Regular
    </namerecord>
    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
      Sample Font
    </namerecord>
    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
      Regular
    </namerecord>
    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
      Sample Font
    </namerecord>
    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
      SampleFont-Regular
    </namerecord>
  </name>

  <post>
    <formatType value="3.0"/>
    <italicAngle value="0.0"/>
    <underlinePosition value="-75"/>
    <underlineThickness value="50"/>
    <isFixedPitch value="0"/>
    <minMemType42 value="0"/>
    <maxMemType42 value="0"/>
    <minMemType1 value="0"/>
    <maxMemType1 value="0"/>
  </post>

</ttFont>
+175 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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 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.graphics.Typeface;
import android.graphics.fonts.FontRequest;
import android.provider.FontsContract;
import android.provider.FontsContract.FontFamilyResult;
import android.provider.FontsContract.FontInfo;
import android.provider.FontsContract.Columns;
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 org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

@SmallTest
@RunWith(AndroidJUnit4.class)
public class FontsContractE2ETest {
    private static final String AUTHORITY = "android.provider.fonts.font";
    private static final String PACKAGE = "com.android.frameworks.coretests";

    // Signature to be used for authentication to access content provider.
    // In this test case, the content provider and consumer live in the same package, self package's
    // signature works.
    private static List<List<byte[]>> SIGNATURE;
    static {
        final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
        try {
            PackageManager manager = context.getPackageManager();
            PackageInfo info = manager.getPackageInfo(
                    context.getPackageName(), PackageManager.GET_SIGNATURES);
            ArrayList<byte[]> out = new ArrayList<>();
            for (Signature sig : info.signatures) {
                out.add(sig.toByteArray());
            }
            SIGNATURE = new ArrayList<>();
            SIGNATURE.add(out);
        } catch (PackageManager.NameNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    @Before
    public void setUp() {
        MockFontProvider.prepareFontFiles(
                InstrumentationRegistry.getInstrumentation().getTargetContext());
    }

    @After
    public void tearDown() {
        MockFontProvider.cleanUpFontFiles(
                InstrumentationRegistry.getInstrumentation().getTargetContext());
    }

    private static class TestCallback extends FontsContract.FontRequestCallback {
        private Typeface mTypeface;

        private int mSuccessCallCount;
        private int mFailedCallCount;

        public void onTypefaceRetrieved(Typeface typeface) {
            mTypeface = typeface;
            mSuccessCallCount++;
        }

        public void onTypefaceRequestFailed(int reason) {
            mFailedCallCount++;
        }

        public Typeface getTypeface() {
            return mTypeface;
        }

        public int getSuccessCallCount() {
            return mSuccessCallCount;
        }

        public int getFailedCallCount() {
            return mFailedCallCount;
        }
    }

    @Test
    public void typefaceCacheTest() throws NameNotFoundException {
        Instrumentation inst = InstrumentationRegistry.getInstrumentation();
        Context ctx = inst.getTargetContext();

        final TestCallback callback = new TestCallback();
        inst.runOnMainSync(() -> {
            FontRequest request = new FontRequest(
                    AUTHORITY, PACKAGE, "singleFontFamily", SIGNATURE);
            FontsContract.requestFont(ctx, request, callback, new Handler());
        });
        inst.waitForIdleSync();
        assertEquals(1, callback.getSuccessCallCount());
        assertEquals(0, callback.getFailedCallCount());
        assertNotNull(callback.getTypeface());

        final TestCallback callback2 = new TestCallback();
        inst.runOnMainSync(() -> {
            FontRequest request = new FontRequest(
                    AUTHORITY, PACKAGE, "singleFontFamily", SIGNATURE);
            FontsContract.requestFont(ctx, request, callback2, new Handler());
        });
        inst.waitForIdleSync();
        assertEquals(1, callback2.getSuccessCallCount());
        assertEquals(0, callback2.getFailedCallCount());
        assertSame(callback.getTypeface(), callback2.getTypeface());

        final TestCallback callback3 = new TestCallback();
        inst.runOnMainSync(() -> {
            FontRequest request = new FontRequest(
                    AUTHORITY, PACKAGE, "singleFontFamily2", SIGNATURE);
            FontsContract.requestFont(ctx, request, callback3, new Handler());
        });
        inst.waitForIdleSync();
        assertEquals(1, callback3.getSuccessCallCount());
        assertEquals(0, callback3.getFailedCallCount());
        assertNotSame(callback.getTypeface(), callback3.getTypeface());
    }

    @Test
    public void typefaceNotCacheTest() throws NameNotFoundException {
        Instrumentation inst = InstrumentationRegistry.getInstrumentation();
        Context ctx = inst.getTargetContext();

        FontRequest request = new FontRequest(
                AUTHORITY, PACKAGE, "singleFontFamily", SIGNATURE);
        FontFamilyResult result = FontsContract.fetchFonts(
                ctx, null /* cancellation signal */, request);
        assertEquals(FontFamilyResult.STATUS_OK, result.getStatusCode());
        Typeface typeface = FontsContract.buildTypeface(
                ctx, null /* cancellation signal */, result.getFonts());

        FontFamilyResult result2 = FontsContract.fetchFonts(
                ctx, null /* cancellation signal */, request);
        assertEquals(FontFamilyResult.STATUS_OK, result2.getStatusCode());
        Typeface typeface2 = FontsContract.buildTypeface(
                ctx, null /* cancellation signal */, result2.getFonts());

        // Neighter fetchFonts nor buildTypeface should cache the Typeface.
        assertNotSame(typeface, typeface2);
    }
}
Loading