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