FontRequestEmojiCompatConfig.java revision a55160adc4ac7481c48512c7c146e7875a380021
1/* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.text.emoji; 18 19import android.content.Context; 20import android.graphics.Typeface; 21import android.os.Bundle; 22import android.os.ParcelFileDescriptor; 23import android.support.annotation.NonNull; 24import android.support.annotation.RequiresApi; 25import android.support.annotation.RestrictTo; 26import android.support.v4.graphics.TypefaceCompat; 27import android.support.v4.graphics.TypefaceCompat.FontRequestCallback; 28import android.support.v4.graphics.fonts.FontResult; 29import android.support.v4.os.ResultReceiver; 30import android.support.v4.provider.FontRequest; 31import android.support.v4.provider.FontsContractCompat; 32import android.support.v4.provider.FontsContractInternal; 33import android.support.v4.util.Preconditions; 34 35import java.io.FileInputStream; 36import java.io.IOException; 37import java.nio.ByteBuffer; 38import java.nio.channels.FileChannel; 39import java.util.Arrays; 40import java.util.List; 41 42/** 43 * {@link EmojiCompat.Config} implementation that asynchronously fetches the required font and the 44 * metadata using a {@link FontRequest}. FontRequest should be constructed to fetch an EmojiCompat 45 * compatible emoji font. 46 * <p/> 47 * See {@link FontsContractCompat.FontRequestCallback#onTypefaceRequestFailed(int)} for more 48 * information about the cases where the font loading can fail. 49 */ 50public class FontRequestEmojiCompatConfig extends EmojiCompat.Config { 51 52 /** 53 * @param context Context instance, cannot be {@code null} 54 * @param request {@link FontRequest} to fetch the font asynchronously, cannot be {@code null} 55 */ 56 public FontRequestEmojiCompatConfig(@NonNull Context context, @NonNull FontRequest request) { 57 super(new FontRequestMetadataLoader(context, request)); 58 } 59 60 /** 61 * @hide 62 */ 63 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 64 public FontRequestEmojiCompatConfig(@NonNull Context context, @NonNull FontRequest request, 65 @NonNull FontsContractInternal fontsContract) { 66 super(new FontRequestMetadataLoader(context, request, fontsContract)); 67 } 68 69 70 /** 71 * MetadataLoader implementation that uses FontsContractInternal and TypefaceCompat to load a 72 * given FontRequest. 73 */ 74 private static class FontRequestMetadataLoader implements EmojiCompat.MetadataLoader { 75 private final Context mContext; 76 private final FontRequest mRequest; 77 private final FontsContractInternal mFontsContract; 78 79 FontRequestMetadataLoader(@NonNull Context context, @NonNull FontRequest request) { 80 this(context, request, new FontsContractInternal(context)); 81 } 82 83 FontRequestMetadataLoader(@NonNull Context context, @NonNull FontRequest request, 84 @NonNull FontsContractInternal fontsContract) { 85 Preconditions.checkNotNull(context, "Context cannot be null"); 86 Preconditions.checkNotNull(request, "FontRequest cannot be null"); 87 mContext = context.getApplicationContext(); 88 mRequest = request; 89 mFontsContract = fontsContract; 90 } 91 92 @Override 93 @RequiresApi(19) 94 public void load(@NonNull final EmojiCompat.LoaderCallback loaderCallback) { 95 Preconditions.checkNotNull(loaderCallback, "LoaderCallback cannot be null"); 96 final ResultReceiver receiver = new ResultReceiver(null) { 97 @Override 98 public void onReceiveResult(final int resultCode, final Bundle resultData) { 99 receiveResult(loaderCallback, resultCode, resultData); 100 } 101 }; 102 try { 103 mFontsContract.getFont(mRequest, receiver); 104 } catch (Throwable throwable) { 105 loaderCallback.onFailed(throwable); 106 } 107 } 108 109 @RequiresApi(19) 110 private void receiveResult(final EmojiCompat.LoaderCallback loaderCallback, 111 final int resultCode, final Bundle resultData) { 112 try { 113 if (resultCode != FontsContractCompat.Columns.RESULT_CODE_OK) { 114 throwException(resultCode); 115 } 116 117 if (resultData == null) { 118 throwException(FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND); 119 } 120 121 final List<FontResult> fontResults = resultData.getParcelableArrayList( 122 FontsContractInternal.PARCEL_FONT_RESULTS); 123 if (fontResults == null || fontResults.isEmpty()) { 124 throwException(FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND); 125 } 126 127 final InitRunnable runnable = new InitRunnable(mContext, fontResults.get(0), 128 loaderCallback); 129 final Thread thread = new Thread(runnable); 130 thread.setDaemon(false); 131 thread.start(); 132 } catch (Throwable t) { 133 loaderCallback.onFailed(t); 134 } 135 } 136 } 137 138 /** 139 * Runnable used to create the Typeface and MetadataRepo from a given FontResult. 140 */ 141 @RequiresApi(19) 142 private static class InitRunnable implements Runnable { 143 private final EmojiCompat.LoaderCallback mLoaderCallback; 144 private final Context mContext; 145 private final FontResult mFontResult; 146 147 private InitRunnable(final Context context, 148 final FontResult fontResult, 149 final EmojiCompat.LoaderCallback loaderCallback) { 150 mContext = context; 151 mFontResult = fontResult; 152 mLoaderCallback = loaderCallback; 153 } 154 155 @Override 156 public void run() { 157 try { 158 final ParcelFileDescriptor dupFd = mFontResult.getFileDescriptor().dup(); 159 // this one will close fd that is in mFontResult 160 final Typeface typeface = TypefaceCompat.createTypeface(mContext, 161 Arrays.asList(mFontResult)); 162 if (typeface == null) { 163 throwException(FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR); 164 } 165 // this one will close dupFd 166 final MetadataRepo metadataRepo = createMetadataRepo(typeface, dupFd); 167 mLoaderCallback.onLoaded(metadataRepo); 168 } catch (Throwable t) { 169 mLoaderCallback.onFailed(t); 170 } 171 } 172 173 private MetadataRepo createMetadataRepo(final Typeface typeface, 174 final ParcelFileDescriptor parcelFileDescriptor) throws IOException { 175 try (ParcelFileDescriptor pfd = parcelFileDescriptor; 176 FileInputStream inputStream = new FileInputStream(pfd.getFileDescriptor())) { 177 final FileChannel fileChannel = inputStream.getChannel(); 178 final ByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, 179 fileChannel.size()); 180 final MetadataRepo metadataRepo = MetadataRepo.create(typeface, buffer); 181 return metadataRepo; 182 } 183 } 184 } 185 186 private static void throwException(int code) { 187 throw new RuntimeException("Cannot load metadata, error code:" + Integer.toString(code)); 188 } 189} 190