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 package android.provider;
17 
18 import static android.provider.FontsContract.Columns;
19 
20 import android.content.ContentProvider;
21 import android.content.ContentUris;
22 import android.content.ContentValues;
23 import android.content.Context;
24 import android.content.res.AssetFileDescriptor;
25 import android.content.res.AssetManager;
26 import android.database.Cursor;
27 import android.database.MatrixCursor;
28 import android.graphics.fonts.FontVariationAxis;
29 import android.net.Uri;
30 import android.os.CancellationSignal;
31 import android.os.ParcelFileDescriptor;
32 import android.util.ArraySet;
33 import android.util.SparseArray;
34 
35 import java.io.File;
36 import java.io.FileNotFoundException;
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.nio.file.Files;
40 import java.nio.file.StandardCopyOption;
41 import java.util.Collections;
42 import java.util.HashMap;
43 import java.util.Map;
44 import java.util.concurrent.TimeUnit;
45 import java.util.concurrent.locks.Condition;
46 import java.util.concurrent.locks.Lock;
47 import java.util.concurrent.locks.ReentrantLock;
48 
49 import com.android.internal.annotations.GuardedBy;
50 
51 public class MockFontProvider extends ContentProvider {
52     final static String AUTHORITY = "android.provider.fonts.font";
53 
54     private static final long BLOCKING_TIMEOUT_MS = 10000;  // 10 sec
55     private static final Lock sLock = new ReentrantLock();
56     private static final Condition sCond = sLock.newCondition();
57     @GuardedBy("sLock")
58     private static boolean sSignaled;
59 
blockUntilSignal()60     private static void blockUntilSignal() {
61         long remaining = TimeUnit.MILLISECONDS.toNanos(BLOCKING_TIMEOUT_MS);
62         sLock.lock();
63         try {
64             sSignaled = false;
65             while (!sSignaled) {
66                 try {
67                     remaining = sCond.awaitNanos(remaining);
68                 } catch (InterruptedException e) {
69                     // do nothing.
70                 }
71                 if (sSignaled) {
72                     return;
73                 }
74                 if (remaining <= 0) {
75                     // Timed out
76                     throw new RuntimeException("Timeout during waiting");
77                 }
78             }
79         } finally {
80             sLock.unlock();
81         }
82     }
83 
unblock()84     public static void unblock() {
85         sLock.lock();
86         try {
87             sSignaled = true;
88             sCond.signal();
89         } finally {
90             sLock.unlock();
91         }
92     }
93 
94     final static String[] FONT_FILES = {
95         "samplefont1.ttf",
96     };
97     private static final int NO_FILE_ID = 255;
98     private static final int SAMPLE_FONT_FILE_0_ID = 0;
99 
100     static class Font {
Font(int id, int fileId, int ttcIndex, String varSettings, int weight, int italic, int resultCode)101         public Font(int id, int fileId, int ttcIndex, String varSettings, int weight, int italic,
102                 int resultCode) {
103             mId = id;
104             mFileId = fileId;
105             mTtcIndex = ttcIndex;
106             mVarSettings = varSettings;
107             mWeight = weight;
108             mItalic = italic;
109             mResultCode = resultCode;
110         }
111 
getId()112         public int getId() {
113             return mId;
114         }
115 
getTtcIndex()116         public int getTtcIndex() {
117             return mTtcIndex;
118         }
119 
getVarSettings()120         public String getVarSettings() {
121             return mVarSettings;
122         }
123 
getWeight()124         public int getWeight() {
125             return mWeight;
126         }
127 
getItalic()128         public int getItalic() {
129             return mItalic;
130         }
131 
getResultCode()132         public int getResultCode() {
133             return mResultCode;
134         }
135 
getFileId()136         public int getFileId() {
137             return mFileId;
138         }
139 
140         private int mId;
141         private int mFileId;
142         private int mTtcIndex;
143         private String mVarSettings;
144         private int mWeight;
145         private int mItalic;
146         private int mResultCode;
147     };
148 
149     public static final String BLOCKING_QUERY = "queryBlockingQuery";
150     public static final String NULL_FD_QUERY = "nullFdQuery";
151 
152     private static Map<String, Font[]> QUERY_MAP;
153     static {
154         HashMap<String, Font[]> map = new HashMap<>();
155         int id = 0;
156 
157         map.put("singleFontFamily", new Font[] {
158             new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, null, 400, 0, Columns.RESULT_CODE_OK),
159         });
160 
161         map.put("singleFontFamily2", new Font[] {
162             new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, null, 700, 0, Columns.RESULT_CODE_OK),
163         });
164 
map.put(BLOCKING_QUERY, new Font[] { new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, null, 700, 0, Columns.RESULT_CODE_OK), })165         map.put(BLOCKING_QUERY, new Font[] {
166             new Font(id++, SAMPLE_FONT_FILE_0_ID, 0, null, 700, 0, Columns.RESULT_CODE_OK),
167         });
168 
map.put(NULL_FD_QUERY, new Font[] { new Font(id++, NO_FILE_ID, 0, null, 700, 0, Columns.RESULT_CODE_OK), })169         map.put(NULL_FD_QUERY, new Font[] {
170             new Font(id++, NO_FILE_ID, 0, null, 700, 0, Columns.RESULT_CODE_OK),
171         });
172 
173         QUERY_MAP = Collections.unmodifiableMap(map);
174     }
175 
buildCursor(Font[] in)176     private static Cursor buildCursor(Font[] in) {
177         MatrixCursor cursor = new MatrixCursor(new String[] {
178                 Columns._ID, Columns.TTC_INDEX, Columns.VARIATION_SETTINGS, Columns.WEIGHT,
179                 Columns.ITALIC, Columns.RESULT_CODE, Columns.FILE_ID});
180         for (Font font : in) {
181             MatrixCursor.RowBuilder builder = cursor.newRow();
182             builder.add(Columns._ID, font.getId());
183             builder.add(Columns.FILE_ID, font.getFileId());
184             builder.add(Columns.TTC_INDEX, font.getTtcIndex());
185             builder.add(Columns.VARIATION_SETTINGS, font.getVarSettings());
186             builder.add(Columns.WEIGHT, font.getWeight());
187             builder.add(Columns.ITALIC, font.getItalic());
188             builder.add(Columns.RESULT_CODE, font.getResultCode());
189         }
190         return cursor;
191     }
192 
MockFontProvider()193     public MockFontProvider() {
194     }
195 
prepareFontFiles(Context context)196     public static void prepareFontFiles(Context context) {
197         final AssetManager mgr = context.getAssets();
198         for (String file : FONT_FILES) {
199             try (InputStream is = mgr.open("fonts/" + file)) {
200                 Files.copy(is, getCopiedFile(context, file).toPath(),
201                         StandardCopyOption.REPLACE_EXISTING);
202             } catch (IOException e) {
203                 throw new RuntimeException(e);
204             }
205         }
206     }
207 
cleanUpFontFiles(Context context)208     public static void cleanUpFontFiles(Context context) {
209         for (String file : FONT_FILES) {
210             getCopiedFile(context, file).delete();
211         }
212     }
213 
getCopiedFile(Context context, String path)214     public static File getCopiedFile(Context context, String path) {
215         return new File(context.getFilesDir(), path);
216     }
217 
218     @Override
openFile(Uri uri, String mode)219     public ParcelFileDescriptor openFile(Uri uri, String mode) {
220         final int id = (int)ContentUris.parseId(uri);
221         if (id == NO_FILE_ID) {
222             return null;
223         }
224         final File targetFile = getCopiedFile(getContext(), FONT_FILES[id]);
225         try {
226             return ParcelFileDescriptor.open(targetFile, ParcelFileDescriptor.MODE_READ_ONLY);
227         } catch (FileNotFoundException e) {
228             return null;
229         }
230     }
231 
232     @Override
onCreate()233     public boolean onCreate() {
234         return true;
235     }
236 
237     @Override
getType(Uri uri)238     public String getType(Uri uri) {
239         return "vnd.android.cursor.dir/vnd.android.provider.font";
240     }
241 
242     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)243     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
244             String sortOrder) {
245         final String query = selectionArgs[0];
246         if (query.equals(BLOCKING_QUERY)) {
247             blockUntilSignal();
248         }
249         return buildCursor(QUERY_MAP.get(query));
250     }
251 
252     @Override
insert(Uri uri, ContentValues values)253     public Uri insert(Uri uri, ContentValues values) {
254         throw new UnsupportedOperationException("insert is not supported.");
255     }
256 
257     @Override
delete(Uri uri, String selection, String[] selectionArgs)258     public int delete(Uri uri, String selection, String[] selectionArgs) {
259         throw new UnsupportedOperationException("delete is not supported.");
260     }
261 
262     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)263     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
264         throw new UnsupportedOperationException("update is not supported.");
265     }
266 }
267