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
17package androidx.core.graphics;
18
19import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20
21import android.content.ContentResolver;
22import android.content.Context;
23import android.content.res.Resources;
24import android.net.Uri;
25import android.os.CancellationSignal;
26import android.os.ParcelFileDescriptor;
27import android.os.Process;
28import android.util.Log;
29
30import androidx.annotation.Nullable;
31import androidx.annotation.RequiresApi;
32import androidx.annotation.RestrictTo;
33
34import java.io.Closeable;
35import java.io.File;
36import java.io.FileInputStream;
37import java.io.FileOutputStream;
38import java.io.IOException;
39import java.io.InputStream;
40import java.nio.ByteBuffer;
41import java.nio.channels.FileChannel;
42
43/**
44 * Utility methods for TypefaceCompat.
45 * @hide
46 */
47@RestrictTo(LIBRARY_GROUP)
48public class TypefaceCompatUtil {
49    private static final String TAG = "TypefaceCompatUtil";
50
51    private TypefaceCompatUtil() {}  // Do not instantiate.
52
53    private static final String CACHE_FILE_PREFIX = ".font";
54
55    /**
56     * Creates a temp file.
57     *
58     * Returns null if failed to create temp file.
59     */
60    @Nullable
61    public static File getTempFile(Context context) {
62        final String prefix = CACHE_FILE_PREFIX + Process.myPid() + "-" + Process.myTid() + "-";
63        for (int i = 0; i < 100; ++i) {
64            final File file = new File(context.getCacheDir(), prefix + i);
65            try {
66                if (file.createNewFile()) {
67                    return file;
68                }
69            } catch (IOException e) {
70                // ignore. Try next file.
71            }
72        }
73        return null;
74    }
75
76    /**
77     * Copy the file contents to the direct byte buffer.
78     */
79    @Nullable
80    @RequiresApi(19)
81    private static ByteBuffer mmap(File file) {
82        try (FileInputStream fis = new FileInputStream(file)) {
83            FileChannel channel = fis.getChannel();
84            final long size = channel.size();
85            return channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
86        } catch (IOException e) {
87            return null;
88        }
89    }
90
91    /**
92     * Copy the file contents to the direct byte buffer.
93     */
94    @Nullable
95    @RequiresApi(19)
96    public static ByteBuffer mmap(Context context, CancellationSignal cancellationSignal, Uri uri) {
97        final ContentResolver resolver = context.getContentResolver();
98        try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r", cancellationSignal)) {
99            if (pfd == null) {
100                return null;
101            }
102            try (FileInputStream fis = new FileInputStream(pfd.getFileDescriptor())) {
103                FileChannel channel = fis.getChannel();
104                final long size = channel.size();
105                return channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
106            }
107        } catch (IOException e) {
108            return null;
109        }
110    }
111
112    /**
113     * Copy the resource contents to the direct byte buffer.
114     */
115    @Nullable
116    @RequiresApi(19)
117    public static ByteBuffer copyToDirectBuffer(Context context, Resources res, int id) {
118        File tmpFile = getTempFile(context);
119        if (tmpFile == null) {
120            return null;
121        }
122        try {
123            if (!copyToFile(tmpFile, res, id)) {
124                return null;
125            }
126            return mmap(tmpFile);
127        } finally {
128            tmpFile.delete();
129        }
130    }
131
132    /**
133     * Copy the input stream contents to file.
134     */
135    public static boolean copyToFile(File file, InputStream is) {
136        FileOutputStream os = null;
137        try {
138            os = new FileOutputStream(file, false);
139            byte[] buffer = new byte[1024];
140            int readLen;
141            while ((readLen = is.read(buffer)) != -1) {
142                os.write(buffer, 0, readLen);
143            }
144            return true;
145        } catch (IOException e) {
146            Log.e(TAG, "Error copying resource contents to temp file: " + e.getMessage());
147            return false;
148        } finally {
149            closeQuietly(os);
150        }
151    }
152
153    /**
154     * Copy the resource contents to file.
155     */
156    public static boolean copyToFile(File file, Resources res, int id) {
157        InputStream is = null;
158        try {
159            is = res.openRawResource(id);
160            return copyToFile(file, is);
161        } finally {
162            closeQuietly(is);
163        }
164    }
165
166    public static void closeQuietly(Closeable c) {
167        if (c != null) {
168            try {
169                c.close();
170            } catch (IOException e) {
171            }
172        }
173    }
174}
175