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