FontRequestEmojiCompatConfig.java revision f69ef36b9ff270c87e41177551ef4692f9aff965
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.RestrictTo; 25import android.support.v4.graphics.TypefaceCompat; 26import android.support.v4.graphics.TypefaceCompat.FontRequestCallback; 27import android.support.v4.graphics.fonts.FontResult; 28import android.support.v4.os.ResultReceiver; 29import android.support.v4.provider.FontRequest; 30import android.support.v4.provider.FontsContractCompat; 31import android.support.v4.provider.FontsContractInternal; 32import android.support.v4.util.Preconditions; 33 34import java.io.FileInputStream; 35import java.io.IOException; 36import java.nio.ByteBuffer; 37import java.nio.channels.FileChannel; 38import java.util.Arrays; 39import java.util.List; 40 41/** 42 * {@link EmojiCompat.Config} implementation that asynchronously fetches the required font and the 43 * metadata using a {@link FontRequest}. FontRequest should be constructed to fetch an EmojiCompat 44 * compatible emoji font. 45 * <p/> 46 * See {@link FontsContractCompat.FontRequestCallback#onTypefaceRequestFailed(int)} for more 47 * information about the cases where the font loading can fail. 48 */ 49public class FontRequestEmojiCompatConfig extends EmojiCompat.Config { 50 51 /** 52 * @param context Context instance, cannot be {@code null} 53 * @param request {@link FontRequest} to fetch the font asynchronously, cannot be {@code null} 54 */ 55 public FontRequestEmojiCompatConfig(@NonNull Context context, @NonNull FontRequest request) { 56 super(new FontRequestMetadataLoader(context, request)); 57 } 58 59 /** 60 * @hide 61 */ 62 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 63 public FontRequestEmojiCompatConfig(@NonNull Context context, @NonNull FontRequest request, 64 @NonNull FontsContractInternal fontsContract) { 65 super(new FontRequestMetadataLoader(context, request, fontsContract)); 66 } 67 68 69 /** 70 * MetadataLoader implementation that uses FontsContractInternal and TypefaceCompat to load a 71 * given FontRequest. 72 */ 73 private static class FontRequestMetadataLoader implements EmojiCompat.MetadataLoader { 74 private final Context mContext; 75 private final FontRequest mRequest; 76 private final FontsContractInternal mFontsContract; 77 78 FontRequestMetadataLoader(@NonNull Context context, @NonNull FontRequest request) { 79 this(context, request, new FontsContractInternal(context)); 80 } 81 82 FontRequestMetadataLoader(@NonNull Context context, @NonNull FontRequest request, 83 @NonNull FontsContractInternal fontsContract) { 84 Preconditions.checkNotNull(context, "Context cannot be null"); 85 Preconditions.checkNotNull(request, "FontRequest cannot be null"); 86 mContext = context.getApplicationContext(); 87 mRequest = request; 88 mFontsContract = fontsContract; 89 } 90 91 @Override 92 public void load(@NonNull final EmojiCompat.LoaderCallback loaderCallback) { 93 Preconditions.checkNotNull(loaderCallback, "LoaderCallback cannot be null"); 94 final ResultReceiver receiver = new ResultReceiver(null) { 95 @Override 96 public void onReceiveResult(final int resultCode, final Bundle resultData) { 97 receiveResult(loaderCallback, resultCode, resultData); 98 } 99 }; 100 try { 101 mFontsContract.getFont(mRequest, receiver); 102 } catch (Throwable throwable) { 103 loaderCallback.onFailed(throwable); 104 } 105 } 106 107 private void receiveResult(final EmojiCompat.LoaderCallback loaderCallback, 108 final int resultCode, final Bundle resultData) { 109 try { 110 if (resultCode != FontsContractCompat.Columns.RESULT_CODE_OK) { 111 throwException(resultCode); 112 } 113 114 if (resultData == null) { 115 throwException(FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND); 116 } 117 118 final List<FontResult> fontResults = resultData.getParcelableArrayList( 119 FontsContractInternal.PARCEL_FONT_RESULTS); 120 if (fontResults == null || fontResults.isEmpty()) { 121 throwException(FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND); 122 } 123 124 final InitRunnable runnable = new InitRunnable(mContext, fontResults.get(0), 125 loaderCallback); 126 final Thread thread = new Thread(runnable); 127 thread.setDaemon(false); 128 thread.start(); 129 } catch (Throwable t) { 130 loaderCallback.onFailed(t); 131 } 132 } 133 } 134 135 /** 136 * Runnable used to create the Typeface and MetadataRepo from a given FontResult. 137 */ 138 private static class InitRunnable implements Runnable { 139 private final EmojiCompat.LoaderCallback mLoaderCallback; 140 private final Context mContext; 141 private final FontResult mFontResult; 142 143 private InitRunnable(final Context context, 144 final FontResult fontResult, 145 final EmojiCompat.LoaderCallback loaderCallback) { 146 mContext = context; 147 mFontResult = fontResult; 148 mLoaderCallback = loaderCallback; 149 } 150 151 @Override 152 public void run() { 153 try { 154 final ParcelFileDescriptor dupFd = mFontResult.getFileDescriptor().dup(); 155 // this one will close fd that is in mFontResult 156 final Typeface typeface = TypefaceCompat.createTypeface(mContext, 157 Arrays.asList(mFontResult)).getTypeface(); 158 if (typeface == null) { 159 throwException(FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR); 160 } 161 // this one will close dupFd 162 final MetadataRepo metadataRepo = createMetadataRepo(typeface, dupFd); 163 mLoaderCallback.onLoaded(metadataRepo); 164 } catch (Throwable t) { 165 mLoaderCallback.onFailed(t); 166 } 167 } 168 169 private MetadataRepo createMetadataRepo(final Typeface typeface, 170 final ParcelFileDescriptor parcelFileDescriptor) throws IOException { 171 try (ParcelFileDescriptor pfd = parcelFileDescriptor; 172 FileInputStream inputStream = new FileInputStream(pfd.getFileDescriptor())) { 173 final FileChannel fileChannel = inputStream.getChannel(); 174 final ByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, 175 fileChannel.size()); 176 final MetadataRepo metadataRepo = MetadataRepo.create(typeface, buffer); 177 return metadataRepo; 178 } 179 } 180 } 181 182 private static void throwException(int code) { 183 throw new RuntimeException("Cannot load metadata, error code:" + Integer.toString(code)); 184 } 185} 186