/* * 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 android.provider.FontsContract.Columns; import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; import android.database.Cursor; import android.database.MatrixCursor; 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.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; 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; static class Font { public Font(int id, int fileId, int ttcIndex, String varSettings, int weight, int italic, int resultCode) { mId = id; mFileId = fileId; mTtcIndex = ttcIndex; mVarSettings = varSettings; mWeight = weight; mItalic = italic; mResultCode = resultCode; } public int getId() { return mId; } public int getTtcIndex() { return mTtcIndex; } public String getVarSettings() { return mVarSettings; } public int getWeight() { return mWeight; } public int getItalic() { return mItalic; } public int getResultCode() { return mResultCode; } public int getFileId() { return mFileId; } private int mId; private int mFileId; private int mTtcIndex; private String mVarSettings; private int mWeight; private int mItalic; private int mResultCode; }; public static final String BLOCKING_QUERY = "queryBlockingQuery"; public static final String NULL_FD_QUERY = "nullFdQuery"; private static Map QUERY_MAP; static { HashMap map = new HashMap<>(); int id = 0; map.put("singleFontFamily", new Font[] { new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, null, 400, 0, Columns.RESULT_CODE_OK), }); map.put("singleFontFamily2", new Font[] { 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); } private static Cursor buildCursor(Font[] in) { MatrixCursor cursor = new MatrixCursor(new String[] { Columns._ID, Columns.TTC_INDEX, Columns.VARIATION_SETTINGS, Columns.WEIGHT, Columns.ITALIC, Columns.RESULT_CODE, Columns.FILE_ID}); for (Font font : in) { MatrixCursor.RowBuilder builder = cursor.newRow(); builder.add(Columns._ID, font.getId()); builder.add(Columns.FILE_ID, font.getFileId()); builder.add(Columns.TTC_INDEX, font.getTtcIndex()); builder.add(Columns.VARIATION_SETTINGS, font.getVarSettings()); builder.add(Columns.WEIGHT, font.getWeight()); builder.add(Columns.ITALIC, font.getItalic()); builder.add(Columns.RESULT_CODE, font.getResultCode()); } return cursor; } public MockFontProvider() { } public static void prepareFontFiles(Context context) { final AssetManager mgr = context.getAssets(); for (String file : FONT_FILES) { try (InputStream is = mgr.open("fonts/" + file)) { Files.copy(is, getCopiedFile(context, file).toPath(), StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { throw new RuntimeException(e); } } } public static void cleanUpFontFiles(Context context) { for (String file : FONT_FILES) { getCopiedFile(context, file).delete(); } } public static File getCopiedFile(Context context, String path) { return new File(context.getFilesDir(), path); } @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) { return null; } } @Override public boolean onCreate() { return true; } @Override public String getType(Uri uri) { return "vnd.android.cursor.dir/vnd.android.provider.font"; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { final String query = selectionArgs[0]; if (query.equals(BLOCKING_QUERY)) { blockUntilSignal(); } return buildCursor(QUERY_MAP.get(query)); } @Override public Uri insert(Uri uri, ContentValues values) { throw new UnsupportedOperationException("insert is not supported."); } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { throw new UnsupportedOperationException("delete is not supported."); } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { throw new UnsupportedOperationException("update is not supported."); } }