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 */
16package android.provider;
17
18import static android.provider.FontsContract.Columns;
19
20import android.content.ContentProvider;
21import android.content.ContentUris;
22import android.content.ContentValues;
23import android.content.Context;
24import android.content.res.AssetFileDescriptor;
25import android.content.res.AssetManager;
26import android.database.Cursor;
27import android.database.MatrixCursor;
28import android.graphics.fonts.FontVariationAxis;
29import android.net.Uri;
30import android.os.CancellationSignal;
31import android.os.ParcelFileDescriptor;
32import android.util.ArraySet;
33import android.util.SparseArray;
34
35import java.io.File;
36import java.io.FileNotFoundException;
37import java.io.IOException;
38import java.io.InputStream;
39import java.nio.file.Files;
40import java.nio.file.StandardCopyOption;
41import java.util.Collections;
42import java.util.HashMap;
43import java.util.Map;
44import java.util.concurrent.TimeUnit;
45import java.util.concurrent.locks.Condition;
46import java.util.concurrent.locks.Lock;
47import java.util.concurrent.locks.ReentrantLock;
48
49import com.android.internal.annotations.GuardedBy;
50
51public 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
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
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 {
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
112        public int getId() {
113            return mId;
114        }
115
116        public int getTtcIndex() {
117            return mTtcIndex;
118        }
119
120        public String getVarSettings() {
121            return mVarSettings;
122        }
123
124        public int getWeight() {
125            return mWeight;
126        }
127
128        public int getItalic() {
129            return mItalic;
130        }
131
132        public int getResultCode() {
133            return mResultCode;
134        }
135
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
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
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
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
193    public MockFontProvider() {
194    }
195
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
208    public static void cleanUpFontFiles(Context context) {
209        for (String file : FONT_FILES) {
210            getCopiedFile(context, file).delete();
211        }
212    }
213
214    public static File getCopiedFile(Context context, String path) {
215        return new File(context.getFilesDir(), path);
216    }
217
218    @Override
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
233    public boolean onCreate() {
234        return true;
235    }
236
237    @Override
238    public String getType(Uri uri) {
239        return "vnd.android.cursor.dir/vnd.android.provider.font";
240    }
241
242    @Override
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
253    public Uri insert(Uri uri, ContentValues values) {
254        throw new UnsupportedOperationException("insert is not supported.");
255    }
256
257    @Override
258    public int delete(Uri uri, String selection, String[] selectionArgs) {
259        throw new UnsupportedOperationException("delete is not supported.");
260    }
261
262    @Override
263    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
264        throw new UnsupportedOperationException("update is not supported.");
265    }
266}
267