/* * 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 androidx.core.provider; import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.res.AssetManager; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import android.os.ParcelFileDescriptor; import androidx.core.provider.FontsContractCompat.Columns; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * Provides a test Content Provider implementing {@link FontsContractCompat}. */ public class MockFontProvider extends ContentProvider { public static final String AUTHORITY = "androidx.core.provider.fonts.font"; static final String[] FONT_FILES = { "samplefont.ttf", "large_a.ttf", "large_b.ttf", "large_c.ttf", "large_d.ttf" }; public static final int INVALID_FONT_FILE_ID = -1; private static final int SAMPLE_FONT_FILE_0_ID = 0; private static final int LARGE_A_FILE_ID = 1; private static final int LARGE_B_FILE_ID = 2; private static final int LARGE_C_FILE_ID = 3; private static final int LARGE_D_FILE_ID = 4; static final String SINGLE_FONT_FAMILY_QUERY = "singleFontFamily"; static final String SINGLE_FONT_FAMILY2_QUERY = "singleFontFamily2"; static final String NOT_FOUND_QUERY = "notFound"; static final String UNAVAILABLE_QUERY = "unavailable"; static final String MALFORMED_QUERY = "malformed"; static final String NOT_FOUND_SECOND_QUERY = "notFoundSecond"; static final String NOT_FOUND_THIRD_QUERY = "notFoundThird"; static final String NEGATIVE_ERROR_CODE_QUERY = "negativeCode"; static final String MANDATORY_FIELDS_ONLY_QUERY = "mandatoryFields"; static final String STYLE_TEST_QUERY = "styleTest"; static final String INVALID_URI = "invalidURI"; static class Font { Font(int id, int fileId, int ttcIndex, String varSettings, int weight, int italic, int resultCode, boolean returnAllFields) { mId = id; mFileId = fileId; mTtcIndex = ttcIndex; mVarSettings = varSettings; mWeight = weight; mItalic = italic; mResultCode = resultCode; mReturnAllFields = returnAllFields; } 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; } public boolean isReturnAllFields() { return mReturnAllFields; } private final int mId; private final int mFileId; private final int mTtcIndex; private final String mVarSettings; private final int mWeight; private final int mItalic; private final int mResultCode; private final boolean mReturnAllFields; }; private static final Map QUERY_MAP; static { HashMap map = new HashMap<>(); int id = 1; map.put(SINGLE_FONT_FAMILY_QUERY, new Font[] { new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, "'wght' 100", 400, 0, Columns.RESULT_CODE_OK, true), }); map.put(SINGLE_FONT_FAMILY2_QUERY, new Font[] { new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, "'wght' 100", 700, 1, Columns.RESULT_CODE_OK, true), }); map.put(NOT_FOUND_QUERY, new Font[] { new Font(0, 0, 0, null, 400, 0, Columns.RESULT_CODE_FONT_NOT_FOUND, true), }); map.put(UNAVAILABLE_QUERY, new Font[] { new Font(0, 0, 0, null, 400, 0, Columns.RESULT_CODE_FONT_UNAVAILABLE, true), }); map.put(MALFORMED_QUERY, new Font[] { new Font(0, 0, 0, null, 400, 0, Columns.RESULT_CODE_MALFORMED_QUERY, true), }); map.put(NOT_FOUND_SECOND_QUERY, new Font[] { new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, null, 700, 0, Columns.RESULT_CODE_OK, true), new Font(0, 0, 0, null, 400, 0, Columns.RESULT_CODE_FONT_NOT_FOUND, true), }); map.put(NOT_FOUND_THIRD_QUERY, new Font[] { new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, null, 700, 0, Columns.RESULT_CODE_OK, true), new Font(0, 0, 0, null, 400, 0, Columns.RESULT_CODE_FONT_NOT_FOUND, true), new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, null, 700, 0, Columns.RESULT_CODE_OK, true), }); map.put(NEGATIVE_ERROR_CODE_QUERY, new Font[] { new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, null, 700, 0, -5, true), }); map.put(MANDATORY_FIELDS_ONLY_QUERY, new Font[] { new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, null, 400, 0, Columns.RESULT_CODE_OK, false), }); map.put(STYLE_TEST_QUERY, new Font[] { new Font(id++, LARGE_A_FILE_ID, 0, null, 400, 0 /* normal */, Columns.RESULT_CODE_OK, true), new Font(id++, LARGE_B_FILE_ID, 0, null, 400, 1 /* italic */, Columns.RESULT_CODE_OK, true), new Font(id++, LARGE_C_FILE_ID, 0, null, 700, 0 /* normal */, Columns.RESULT_CODE_OK, true), new Font(id++, LARGE_D_FILE_ID, 0, null, 700, 1 /* italic */, Columns.RESULT_CODE_OK, true), }); map.put(INVALID_URI, new Font[] { new Font(id++, INVALID_FONT_FILE_ID, 0, null, 400, 0, Columns.RESULT_CODE_OK, true), }); QUERY_MAP = Collections.unmodifiableMap(map); } private static Cursor buildCursor(Font[] in) { if (!in[0].mReturnAllFields) { MatrixCursor cursor = new MatrixCursor(new String[] { Columns._ID, Columns.FILE_ID }); for (Font font : in) { cursor.addRow(new Object[] { font.getId(), font.getFileId() }); } return cursor; } 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) { cursor.addRow( new Object[] { font.getId(), font.getTtcIndex(), font.getVarSettings(), font.getWeight(), font.getItalic(), font.getResultCode(), font.getFileId() }); } return cursor; } public static void prepareFontFiles(Context context) { final AssetManager mgr = context.getAssets(); for (String file : FONT_FILES) { InputStream is = null; try { is = mgr.open("fonts/" + file); File copied = getCopiedFile(context, file); File parent = copied.getParentFile(); if (!parent.isDirectory()) { parent.mkdirs(); parent.setReadable(true, false); parent.setExecutable(true, false); } copy(is, copied); copied.setReadable(true, false); } catch (IOException e) { throw new RuntimeException(e); } finally { if (is != null) { try { is.close(); } catch (IOException e) { // Do nothing. } } } } } /** * The caller is responsible for closing the given InputStream. */ private static void copy(InputStream is, File file) throws IOException { FileOutputStream fos = null; try { fos = new FileOutputStream(file, false); byte[] buffer = new byte[1024]; int readLen; while ((readLen = is.read(buffer)) != -1) { fos.write(buffer, 0, readLen); } } finally { if (fos != null) { fos.close(); } } } public static void cleanUpFontFiles(Context context) { for (String file : FONT_FILES) { getCopiedFile(context, file).delete(); } } public static File getCopiedFile(Context context, String path) { final File cacheDir = new File(context.getFilesDir(), "fontCache"); return new File(cacheDir, path); } @Override public ParcelFileDescriptor openFile(Uri uri, String mode) { final int id = (int) ContentUris.parseId(uri); if (id < 0) { 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"); } } @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) { return buildCursor(QUERY_MAP.get(selectionArgs[0])); } @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."); } }