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 17 package android.support.v4.provider; 18 19 import android.content.ContentProvider; 20 import android.content.ContentUris; 21 import android.content.ContentValues; 22 import android.content.Context; 23 import android.content.res.AssetManager; 24 import android.database.Cursor; 25 import android.database.MatrixCursor; 26 import android.net.Uri; 27 import android.os.ParcelFileDescriptor; 28 import android.support.v4.provider.FontsContractCompat.Columns; 29 30 import java.io.File; 31 import java.io.FileNotFoundException; 32 import java.io.FileOutputStream; 33 import java.io.IOException; 34 import java.io.InputStream; 35 import java.util.Collections; 36 import java.util.HashMap; 37 import java.util.Map; 38 39 /** 40 * Provides a test Content Provider implementing {@link FontsContractCompat}. 41 */ 42 public class MockFontProvider extends ContentProvider { 43 static final String[] FONT_FILES = { 44 "samplefont.ttf", "large_a.ttf", "large_b.ttf", "large_c.ttf", "large_d.ttf" 45 }; 46 private static final int SAMPLE_FONT_FILE_0_ID = 0; 47 private static final int LARGE_A_FILE_ID = 1; 48 private static final int LARGE_B_FILE_ID = 2; 49 private static final int LARGE_C_FILE_ID = 3; 50 private static final int LARGE_D_FILE_ID = 4; 51 52 static final String SINGLE_FONT_FAMILY_QUERY = "singleFontFamily"; 53 static final String SINGLE_FONT_FAMILY2_QUERY = "singleFontFamily2"; 54 static final String NOT_FOUND_QUERY = "notFound"; 55 static final String UNAVAILABLE_QUERY = "unavailable"; 56 static final String MALFORMED_QUERY = "malformed"; 57 static final String NOT_FOUND_SECOND_QUERY = "notFoundSecond"; 58 static final String NOT_FOUND_THIRD_QUERY = "notFoundThird"; 59 static final String NEGATIVE_ERROR_CODE_QUERY = "negativeCode"; 60 static final String MANDATORY_FIELDS_ONLY_QUERY = "mandatoryFields"; 61 static final String STYLE_TEST_QUERY = "styleTest"; 62 63 static class Font { Font(int id, int fileId, int ttcIndex, String varSettings, int weight, int italic, int resultCode, boolean returnAllFields)64 Font(int id, int fileId, int ttcIndex, String varSettings, int weight, int italic, 65 int resultCode, boolean returnAllFields) { 66 mId = id; 67 mFileId = fileId; 68 mTtcIndex = ttcIndex; 69 mVarSettings = varSettings; 70 mWeight = weight; 71 mItalic = italic; 72 mResultCode = resultCode; 73 mReturnAllFields = returnAllFields; 74 } 75 getId()76 public int getId() { 77 return mId; 78 } 79 getTtcIndex()80 public int getTtcIndex() { 81 return mTtcIndex; 82 } 83 getVarSettings()84 public String getVarSettings() { 85 return mVarSettings; 86 } 87 getWeight()88 public int getWeight() { 89 return mWeight; 90 } 91 getItalic()92 public int getItalic() { 93 return mItalic; 94 } 95 getResultCode()96 public int getResultCode() { 97 return mResultCode; 98 } 99 getFileId()100 public int getFileId() { 101 return mFileId; 102 } 103 isReturnAllFields()104 public boolean isReturnAllFields() { 105 return mReturnAllFields; 106 } 107 108 private final int mId; 109 private final int mFileId; 110 private final int mTtcIndex; 111 private final String mVarSettings; 112 private final int mWeight; 113 private final int mItalic; 114 private final int mResultCode; 115 private final boolean mReturnAllFields; 116 }; 117 118 private static final Map<String, Font[]> QUERY_MAP; 119 static { 120 HashMap<String, Font[]> map = new HashMap<>(); 121 int id = 1; 122 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), })123 map.put(SINGLE_FONT_FAMILY_QUERY, new Font[] { 124 new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, "'wght' 100", 400, 0, 125 Columns.RESULT_CODE_OK, true), 126 }); 127 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), })128 map.put(SINGLE_FONT_FAMILY2_QUERY, new Font[] { 129 new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, "'wght' 100", 700, 1, 130 Columns.RESULT_CODE_OK, true), 131 }); 132 map.put(NOT_FOUND_QUERY, new Font[] { new Font(0, 0, 0, null, 400, 0, Columns.RESULT_CODE_FONT_NOT_FOUND, true), })133 map.put(NOT_FOUND_QUERY, new Font[] { 134 new Font(0, 0, 0, null, 400, 0, Columns.RESULT_CODE_FONT_NOT_FOUND, true), 135 }); 136 map.put(UNAVAILABLE_QUERY, new Font[] { new Font(0, 0, 0, null, 400, 0, Columns.RESULT_CODE_FONT_UNAVAILABLE, true), })137 map.put(UNAVAILABLE_QUERY, new Font[] { 138 new Font(0, 0, 0, null, 400, 0, Columns.RESULT_CODE_FONT_UNAVAILABLE, true), 139 }); 140 map.put(MALFORMED_QUERY, new Font[] { new Font(0, 0, 0, null, 400, 0, Columns.RESULT_CODE_MALFORMED_QUERY, true), })141 map.put(MALFORMED_QUERY, new Font[] { 142 new Font(0, 0, 0, null, 400, 0, Columns.RESULT_CODE_MALFORMED_QUERY, true), 143 }); 144 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), })145 map.put(NOT_FOUND_SECOND_QUERY, new Font[] { 146 new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, null, 700, 0, Columns.RESULT_CODE_OK, 147 true), 148 new Font(0, 0, 0, null, 400, 0, Columns.RESULT_CODE_FONT_NOT_FOUND, true), 149 }); 150 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), })151 map.put(NOT_FOUND_THIRD_QUERY, new Font[] { 152 new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, null, 700, 0, Columns.RESULT_CODE_OK, 153 true), 154 new Font(0, 0, 0, null, 400, 0, Columns.RESULT_CODE_FONT_NOT_FOUND, true), 155 new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, null, 700, 0, Columns.RESULT_CODE_OK, 156 true), 157 }); 158 map.put(NEGATIVE_ERROR_CODE_QUERY, new Font[] { new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, null, 700, 0, -5, true), })159 map.put(NEGATIVE_ERROR_CODE_QUERY, new Font[] { 160 new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, null, 700, 0, -5, true), 161 }); 162 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), })163 map.put(MANDATORY_FIELDS_ONLY_QUERY, new Font[] { 164 new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, null, 400, 0, 165 Columns.RESULT_CODE_OK, false), 166 }); 167 map.put(STYLE_TEST_QUERY, new Font[] { new Font(id++, LARGE_A_FILE_ID, 0, null, 400, 0 , Columns.RESULT_CODE_OK, true), new Font(id++, LARGE_B_FILE_ID, 0, null, 400, 1 , Columns.RESULT_CODE_OK, true), new Font(id++, LARGE_C_FILE_ID, 0, null, 700, 0 , Columns.RESULT_CODE_OK, true), new Font(id++, LARGE_D_FILE_ID, 0, null, 700, 1 , Columns.RESULT_CODE_OK, true), })168 map.put(STYLE_TEST_QUERY, new Font[] { 169 new Font(id++, LARGE_A_FILE_ID, 0, null, 400, 0 /* normal */, 170 Columns.RESULT_CODE_OK, true), 171 new Font(id++, LARGE_B_FILE_ID, 0, null, 400, 1 /* italic */, 172 Columns.RESULT_CODE_OK, true), 173 new Font(id++, LARGE_C_FILE_ID, 0, null, 700, 0 /* normal */, 174 Columns.RESULT_CODE_OK, true), 175 new Font(id++, LARGE_D_FILE_ID, 0, null, 700, 1 /* italic */, 176 Columns.RESULT_CODE_OK, true), 177 }); 178 179 QUERY_MAP = Collections.unmodifiableMap(map); 180 } 181 buildCursor(Font[] in)182 private static Cursor buildCursor(Font[] in) { 183 if (!in[0].mReturnAllFields) { 184 MatrixCursor cursor = new MatrixCursor(new String[] { Columns._ID, Columns.FILE_ID }); 185 for (Font font : in) { 186 cursor.addRow(new Object[] { font.getId(), font.getFileId() }); 187 } 188 return cursor; 189 } 190 MatrixCursor cursor = new MatrixCursor(new String[] { 191 Columns._ID, Columns.TTC_INDEX, Columns.VARIATION_SETTINGS, Columns.WEIGHT, 192 Columns.ITALIC, Columns.RESULT_CODE, Columns.FILE_ID}); 193 for (Font font : in) { 194 cursor.addRow( 195 new Object[] { font.getId(), font.getTtcIndex(), font.getVarSettings(), 196 font.getWeight(), font.getItalic(), font.getResultCode(), font.getFileId() }); 197 } 198 return cursor; 199 } 200 prepareFontFiles(Context context)201 public static void prepareFontFiles(Context context) { 202 final AssetManager mgr = context.getAssets(); 203 for (String file : FONT_FILES) { 204 InputStream is = null; 205 try { 206 is = mgr.open("fonts/" + file); 207 copy(is, getCopiedFile(context, file)); 208 } catch (IOException e) { 209 throw new RuntimeException(e); 210 } finally { 211 if (is != null) { 212 try { 213 is.close(); 214 } catch (IOException e) { 215 // Do nothing. 216 } 217 } 218 } 219 } 220 } 221 222 /** 223 * The caller is responsible for closing the given InputStream. 224 */ copy(InputStream is, File file)225 private static void copy(InputStream is, File file) throws IOException { 226 FileOutputStream fos = null; 227 try { 228 fos = new FileOutputStream(file, false); 229 byte[] buffer = new byte[1024]; 230 int readLen; 231 while ((readLen = is.read(buffer)) != -1) { 232 fos.write(buffer, 0, readLen); 233 } 234 } finally { 235 if (fos != null) { 236 fos.close(); 237 } 238 } 239 } 240 cleanUpFontFiles(Context context)241 public static void cleanUpFontFiles(Context context) { 242 for (String file : FONT_FILES) { 243 getCopiedFile(context, file).delete(); 244 } 245 } 246 getCopiedFile(Context context, String path)247 public static File getCopiedFile(Context context, String path) { 248 return new File(context.getFilesDir(), path); 249 } 250 251 @Override openFile(Uri uri, String mode)252 public ParcelFileDescriptor openFile(Uri uri, String mode) { 253 final int id = (int) ContentUris.parseId(uri); 254 final File targetFile = getCopiedFile(getContext(), FONT_FILES[id]); 255 try { 256 return ParcelFileDescriptor.open(targetFile, ParcelFileDescriptor.MODE_READ_ONLY); 257 } catch (FileNotFoundException e) { 258 throw new RuntimeException( 259 "Failed to found font file. You might forget call prepareFontFiles in setUp"); 260 } 261 } 262 263 @Override onCreate()264 public boolean onCreate() { 265 return true; 266 } 267 268 @Override getType(Uri uri)269 public String getType(Uri uri) { 270 return "vnd.android.cursor.dir/vnd.android.provider.font"; 271 } 272 273 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)274 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 275 String sortOrder) { 276 return buildCursor(QUERY_MAP.get(selectionArgs[0])); 277 } 278 279 @Override insert(Uri uri, ContentValues values)280 public Uri insert(Uri uri, ContentValues values) { 281 throw new UnsupportedOperationException("insert is not supported."); 282 } 283 284 @Override delete(Uri uri, String selection, String[] selectionArgs)285 public int delete(Uri uri, String selection, String[] selectionArgs) { 286 throw new UnsupportedOperationException("delete is not supported."); 287 } 288 289 @Override update(Uri uri, ContentValues values, String selection, String[] selectionArgs)290 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 291 throw new UnsupportedOperationException("update is not supported."); 292 } 293 } 294