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