diff options
Diffstat (limited to 'graphics')
-rw-r--r-- | graphics/java/android/graphics/FontFamily.java | 20 | ||||
-rw-r--r-- | graphics/java/android/graphics/GraphicsStatsService.java | 566 | ||||
-rw-r--r-- | graphics/java/android/graphics/HardwareRenderer.java | 5 | ||||
-rw-r--r-- | graphics/java/android/graphics/fonts/Font.java | 104 |
4 files changed, 644 insertions, 51 deletions
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java index 447f043392c2..f50de1665453 100644 --- a/graphics/java/android/graphics/FontFamily.java +++ b/graphics/java/android/graphics/FontFamily.java @@ -19,6 +19,7 @@ package android.graphics; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.AssetManager; +import android.graphics.fonts.Font; import android.graphics.fonts.FontVariationAxis; import android.os.Build; import android.text.TextUtils; @@ -195,18 +196,13 @@ public class FontFamily { if (mBuilderPtr == 0) { throw new IllegalStateException("Unable to call addFontFromAsset after freezing."); } - if (axes != null) { - for (FontVariationAxis axis : axes) { - nAddAxisValue(mBuilderPtr, axis.getOpenTypeTagValue(), axis.getStyleValue()); - } - } - return nAddFontFromAssetManager(mBuilderPtr, mgr, path, cookie, isAsset, ttcIndex, weight, - isItalic); - } - // TODO: Remove once internal user stop using private API. - private static boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex) { - return nAddFont(builderPtr, font, ttcIndex, -1, -1); + try { + ByteBuffer buffer = Font.Builder.createBuffer(mgr, path, isAsset, cookie); + return addFontFromBuffer(buffer, ttcIndex, axes, weight, isItalic); + } catch (IOException e) { + return false; + } } private static native long nInitBuilder(String langs, int variant); @@ -225,8 +221,6 @@ public class FontFamily { int weight, int isItalic); private static native boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font, int ttcIndex, int weight, int isItalic); - private static native boolean nAddFontFromAssetManager(long builderPtr, AssetManager mgr, - String path, int cookie, boolean isAsset, int ttcIndex, int weight, int isItalic); // The added axis values are only valid for the next nAddFont* method call. @CriticalNative diff --git a/graphics/java/android/graphics/GraphicsStatsService.java b/graphics/java/android/graphics/GraphicsStatsService.java new file mode 100644 index 000000000000..8dfd6ee92a9a --- /dev/null +++ b/graphics/java/android/graphics/GraphicsStatsService.java @@ -0,0 +1,566 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics; + +import android.annotation.SystemApi; +import android.app.AlarmManager; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.os.Environment; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.RemoteException; +import android.os.SharedMemory; +import android.os.Trace; +import android.os.UserHandle; +import android.system.ErrnoException; +import android.util.Log; +import android.view.IGraphicsStats; +import android.view.IGraphicsStatsCallback; + +import com.android.internal.util.DumpUtils; +import com.android.internal.util.FastPrintWriter; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.HashSet; +import java.util.TimeZone; + +/** + * This service's job is to collect aggregate rendering profile data. It + * does this by allowing rendering processes to request an ashmem buffer + * to place their stats into. + * + * Buffers are rotated on a daily (in UTC) basis and only the 3 most-recent days + * are kept. + * + * The primary consumer of this is incident reports and automated metric checking. It is not + * intended for end-developer consumption, for that we have gfxinfo. + * + * Buffer rotation process: + * 1) Alarm fires + * 2) onRotateGraphicsStatsBuffer() is sent to all active processes + * 3) Upon receiving the callback, the process will stop using the previous ashmem buffer and + * request a new one. + * 4) When that request is received we now know that the ashmem region is no longer in use so + * it gets queued up for saving to disk and a new ashmem region is created and returned + * for the process to use. + * + * @hide */ +public class GraphicsStatsService extends IGraphicsStats.Stub { + public static final String GRAPHICS_STATS_SERVICE = "graphicsstats"; + + private static final String TAG = "GraphicsStatsService"; + + private static final int SAVE_BUFFER = 1; + private static final int DELETE_OLD = 2; + + private static final int AID_STATSD = 1066; // Statsd uid is set to 1066 forever. + + // This isn't static because we need this to happen after registerNativeMethods, however + // the class is loaded (and thus static ctor happens) before that occurs. + private final int mAshmemSize = nGetAshmemSize(); + private final byte[] mZeroData = new byte[mAshmemSize]; + + private final Context mContext; + private final AppOpsManager mAppOps; + private final AlarmManager mAlarmManager; + private final Object mLock = new Object(); + private ArrayList<ActiveBuffer> mActive = new ArrayList<>(); + private File mGraphicsStatsDir; + private final Object mFileAccessLock = new Object(); + private Handler mWriteOutHandler; + private boolean mRotateIsScheduled = false; + + @SystemApi + public GraphicsStatsService(Context context) { + mContext = context; + mAppOps = context.getSystemService(AppOpsManager.class); + mAlarmManager = context.getSystemService(AlarmManager.class); + File systemDataDir = new File(Environment.getDataDirectory(), "system"); + mGraphicsStatsDir = new File(systemDataDir, "graphicsstats"); + mGraphicsStatsDir.mkdirs(); + if (!mGraphicsStatsDir.exists()) { + throw new IllegalStateException("Graphics stats directory does not exist: " + + mGraphicsStatsDir.getAbsolutePath()); + } + HandlerThread bgthread = new HandlerThread("GraphicsStats-disk", + Process.THREAD_PRIORITY_BACKGROUND); + bgthread.start(); + + mWriteOutHandler = new Handler(bgthread.getLooper(), new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case SAVE_BUFFER: + saveBuffer((HistoricalBuffer) msg.obj); + break; + case DELETE_OLD: + deleteOldBuffers(); + break; + } + return true; + } + }); + nativeInit(); + } + + /** + * Current rotation policy is to rotate at midnight UTC. We don't specify RTC_WAKEUP because + * rotation can be delayed if there's otherwise no activity. However exact is used because + * we don't want the system to delay it by TOO much. + */ + private void scheduleRotateLocked() { + if (mRotateIsScheduled) { + return; + } + mRotateIsScheduled = true; + Calendar calendar = normalizeDate(System.currentTimeMillis()); + calendar.add(Calendar.DATE, 1); + mAlarmManager.setExact(AlarmManager.RTC, calendar.getTimeInMillis(), TAG, this::onAlarm, + mWriteOutHandler); + } + + private void onAlarm() { + // We need to make a copy since some of the callbacks won't be proxy and thus + // can result in a re-entrant acquisition of mLock that would result in a modification + // of mActive during iteration. + ActiveBuffer[] activeCopy; + synchronized (mLock) { + mRotateIsScheduled = false; + scheduleRotateLocked(); + activeCopy = mActive.toArray(new ActiveBuffer[0]); + } + for (ActiveBuffer active : activeCopy) { + try { + active.mCallback.onRotateGraphicsStatsBuffer(); + } catch (RemoteException e) { + Log.w(TAG, String.format("Failed to notify '%s' (pid=%d) to rotate buffers", + active.mInfo.mPackageName, active.mPid), e); + } + } + // Give a few seconds for everyone to rotate before doing the cleanup + mWriteOutHandler.sendEmptyMessageDelayed(DELETE_OLD, 10000); + } + + @Override + public ParcelFileDescriptor requestBufferForProcess(String packageName, + IGraphicsStatsCallback token) throws RemoteException { + int uid = Binder.getCallingUid(); + int pid = Binder.getCallingPid(); + ParcelFileDescriptor pfd = null; + long callingIdentity = Binder.clearCallingIdentity(); + try { + mAppOps.checkPackage(uid, packageName); + PackageInfo info = mContext.getPackageManager().getPackageInfoAsUser( + packageName, + 0, + UserHandle.getUserId(uid)); + synchronized (mLock) { + pfd = requestBufferForProcessLocked(token, uid, pid, packageName, + info.getLongVersionCode()); + } + } catch (PackageManager.NameNotFoundException ex) { + throw new RemoteException("Unable to find package: '" + packageName + "'"); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + return pfd; + } + + // If lastFullDay is true, pullGraphicsStats returns stats for the last complete day/24h period + // that does not include today. If lastFullDay is false, pullGraphicsStats returns stats for the + // current day. + // This method is invoked from native code only. + @SuppressWarnings({"UnusedDeclaration"}) + private void pullGraphicsStats(boolean lastFullDay, long pulledData) throws RemoteException { + int uid = Binder.getCallingUid(); + + // DUMP and PACKAGE_USAGE_STATS permissions are required to invoke this method. + // TODO: remove exception for statsd daemon after required permissions are granted. statsd + // TODO: should have these permissions granted by data/etc/platform.xml, but it does not. + if (uid != AID_STATSD) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new FastPrintWriter(sw); + if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) { + pw.flush(); + throw new RemoteException(sw.toString()); + } + } + + long callingIdentity = Binder.clearCallingIdentity(); + try { + pullGraphicsStatsImpl(lastFullDay, pulledData); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + private void pullGraphicsStatsImpl(boolean lastFullDay, long pulledData) { + long targetDay; + if (lastFullDay) { + // Get stats from yesterday. Stats stay constant, because the day is over. + targetDay = normalizeDate(System.currentTimeMillis() - 86400000).getTimeInMillis(); + } else { + // Get stats from today. Stats may change as more apps are run today. + targetDay = normalizeDate(System.currentTimeMillis()).getTimeInMillis(); + } + + // Find active buffers for targetDay. + ArrayList<HistoricalBuffer> buffers; + synchronized (mLock) { + buffers = new ArrayList<>(mActive.size()); + for (int i = 0; i < mActive.size(); i++) { + ActiveBuffer buffer = mActive.get(i); + if (buffer.mInfo.mStartTime == targetDay) { + try { + buffers.add(new HistoricalBuffer(buffer)); + } catch (IOException ex) { + // Ignore + } + } + } + } + + // Dump active and historic buffers for targetDay in a serialized + // GraphicsStatsServiceDumpProto proto. + long dump = nCreateDump(-1, true); + try { + synchronized (mFileAccessLock) { + HashSet<File> skipList = dumpActiveLocked(dump, buffers); + buffers.clear(); + String subPath = String.format("%d", targetDay); + File dateDir = new File(mGraphicsStatsDir, subPath); + if (dateDir.exists()) { + for (File pkg : dateDir.listFiles()) { + for (File version : pkg.listFiles()) { + File data = new File(version, "total"); + if (skipList.contains(data)) { + continue; + } + nAddToDump(dump, data.getAbsolutePath()); + } + } + } + } + } finally { + nFinishDumpInMemory(dump, pulledData, lastFullDay); + } + } + + private ParcelFileDescriptor requestBufferForProcessLocked(IGraphicsStatsCallback token, + int uid, int pid, String packageName, long versionCode) throws RemoteException { + ActiveBuffer buffer = fetchActiveBuffersLocked(token, uid, pid, packageName, versionCode); + scheduleRotateLocked(); + return buffer.getPfd(); + } + + private Calendar normalizeDate(long timestamp) { + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + calendar.setTimeInMillis(timestamp); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar; + } + + private File pathForApp(BufferInfo info) { + String subPath = String.format("%d/%s/%d/total", + normalizeDate(info.mStartTime).getTimeInMillis(), info.mPackageName, + info.mVersionCode); + return new File(mGraphicsStatsDir, subPath); + } + + private void saveBuffer(HistoricalBuffer buffer) { + if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, + "saving graphicsstats for " + buffer.mInfo.mPackageName); + } + synchronized (mFileAccessLock) { + File path = pathForApp(buffer.mInfo); + File parent = path.getParentFile(); + parent.mkdirs(); + if (!parent.exists()) { + Log.w(TAG, "Unable to create path: '" + parent.getAbsolutePath() + "'"); + return; + } + nSaveBuffer(path.getAbsolutePath(), buffer.mInfo.mPackageName, + buffer.mInfo.mVersionCode, buffer.mInfo.mStartTime, buffer.mInfo.mEndTime, + buffer.mData); + } + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); + } + + private void deleteRecursiveLocked(File file) { + if (file.isDirectory()) { + for (File child : file.listFiles()) { + deleteRecursiveLocked(child); + } + } + if (!file.delete()) { + Log.w(TAG, "Failed to delete '" + file.getAbsolutePath() + "'!"); + } + } + + private void deleteOldBuffers() { + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "deleting old graphicsstats buffers"); + synchronized (mFileAccessLock) { + File[] files = mGraphicsStatsDir.listFiles(); + if (files == null || files.length <= 3) { + return; + } + long[] sortedDates = new long[files.length]; + for (int i = 0; i < files.length; i++) { + try { + sortedDates[i] = Long.parseLong(files[i].getName()); + } catch (NumberFormatException ex) { + // Skip unrecognized folders + } + } + if (sortedDates.length <= 3) { + return; + } + Arrays.sort(sortedDates); + for (int i = 0; i < sortedDates.length - 3; i++) { + deleteRecursiveLocked(new File(mGraphicsStatsDir, Long.toString(sortedDates[i]))); + } + } + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); + } + + private void addToSaveQueue(ActiveBuffer buffer) { + try { + HistoricalBuffer data = new HistoricalBuffer(buffer); + Message.obtain(mWriteOutHandler, SAVE_BUFFER, data).sendToTarget(); + } catch (IOException e) { + Log.w(TAG, "Failed to copy graphicsstats from " + buffer.mInfo.mPackageName, e); + } + buffer.closeAllBuffers(); + } + + private void processDied(ActiveBuffer buffer) { + synchronized (mLock) { + mActive.remove(buffer); + } + addToSaveQueue(buffer); + } + + private ActiveBuffer fetchActiveBuffersLocked(IGraphicsStatsCallback token, int uid, int pid, + String packageName, long versionCode) throws RemoteException { + int size = mActive.size(); + long today = normalizeDate(System.currentTimeMillis()).getTimeInMillis(); + for (int i = 0; i < size; i++) { + ActiveBuffer buffer = mActive.get(i); + if (buffer.mPid == pid + && buffer.mUid == uid) { + // If the buffer is too old we remove it and return a new one + if (buffer.mInfo.mStartTime < today) { + buffer.binderDied(); + break; + } else { + return buffer; + } + } + } + // Didn't find one, need to create it + try { + ActiveBuffer buffers = new ActiveBuffer(token, uid, pid, packageName, versionCode); + mActive.add(buffers); + return buffers; + } catch (IOException ex) { + throw new RemoteException("Failed to allocate space"); + } + } + + private HashSet<File> dumpActiveLocked(long dump, ArrayList<HistoricalBuffer> buffers) { + HashSet<File> skipFiles = new HashSet<>(buffers.size()); + for (int i = 0; i < buffers.size(); i++) { + HistoricalBuffer buffer = buffers.get(i); + File path = pathForApp(buffer.mInfo); + skipFiles.add(path); + nAddToDump(dump, path.getAbsolutePath(), buffer.mInfo.mPackageName, + buffer.mInfo.mVersionCode, buffer.mInfo.mStartTime, buffer.mInfo.mEndTime, + buffer.mData); + } + return skipFiles; + } + + private void dumpHistoricalLocked(long dump, HashSet<File> skipFiles) { + for (File date : mGraphicsStatsDir.listFiles()) { + for (File pkg : date.listFiles()) { + for (File version : pkg.listFiles()) { + File data = new File(version, "total"); + if (skipFiles.contains(data)) { + continue; + } + nAddToDump(dump, data.getAbsolutePath()); + } + } + } + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { + if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, fout)) return; + boolean dumpProto = false; + for (String str : args) { + if ("--proto".equals(str)) { + dumpProto = true; + break; + } + } + ArrayList<HistoricalBuffer> buffers; + synchronized (mLock) { + buffers = new ArrayList<>(mActive.size()); + for (int i = 0; i < mActive.size(); i++) { + try { + buffers.add(new HistoricalBuffer(mActive.get(i))); + } catch (IOException ex) { + // Ignore + } + } + } + long dump = nCreateDump(fd.getInt$(), dumpProto); + try { + synchronized (mFileAccessLock) { + HashSet<File> skipList = dumpActiveLocked(dump, buffers); + buffers.clear(); + dumpHistoricalLocked(dump, skipList); + } + } finally { + nFinishDump(dump); + } + } + + @Override + protected void finalize() throws Throwable { + nativeDestructor(); + } + + private native void nativeInit(); + private static native void nativeDestructor(); + + private static native int nGetAshmemSize(); + private static native long nCreateDump(int outFd, boolean isProto); + private static native void nAddToDump(long dump, String path, String packageName, + long versionCode, long startTime, long endTime, byte[] data); + private static native void nAddToDump(long dump, String path); + private static native void nFinishDump(long dump); + private static native void nFinishDumpInMemory(long dump, long pulledData, boolean lastFullDay); + private static native void nSaveBuffer(String path, String packageName, long versionCode, + long startTime, long endTime, byte[] data); + + private final class BufferInfo { + final String mPackageName; + final long mVersionCode; + long mStartTime; + long mEndTime; + + BufferInfo(String packageName, long versionCode, long startTime) { + this.mPackageName = packageName; + this.mVersionCode = versionCode; + this.mStartTime = startTime; + } + } + + private final class ActiveBuffer implements DeathRecipient { + final BufferInfo mInfo; + final int mUid; + final int mPid; + final IGraphicsStatsCallback mCallback; + final IBinder mToken; + SharedMemory mProcessBuffer; + ByteBuffer mMapping; + + ActiveBuffer(IGraphicsStatsCallback token, int uid, int pid, String packageName, + long versionCode) + throws RemoteException, IOException { + mInfo = new BufferInfo(packageName, versionCode, System.currentTimeMillis()); + mUid = uid; + mPid = pid; + mCallback = token; + mToken = mCallback.asBinder(); + mToken.linkToDeath(this, 0); + try { + mProcessBuffer = SharedMemory.create("GFXStats-" + pid, mAshmemSize); + mMapping = mProcessBuffer.mapReadWrite(); + } catch (ErrnoException ex) { + ex.rethrowAsIOException(); + } + mMapping.position(0); + mMapping.put(mZeroData, 0, mAshmemSize); + } + + @Override + public void binderDied() { + mToken.unlinkToDeath(this, 0); + processDied(this); + } + + void closeAllBuffers() { + if (mMapping != null) { + SharedMemory.unmap(mMapping); + mMapping = null; + } + if (mProcessBuffer != null) { + mProcessBuffer.close(); + mProcessBuffer = null; + } + } + + ParcelFileDescriptor getPfd() { + try { + return mProcessBuffer.getFdDup(); + } catch (IOException ex) { + throw new IllegalStateException("Failed to get PFD from memory file", ex); + } + } + + void readBytes(byte[] buffer, int count) throws IOException { + if (mMapping == null) { + throw new IOException("SharedMemory has been deactivated"); + } + mMapping.position(0); + mMapping.get(buffer, 0, count); + } + } + + private final class HistoricalBuffer { + final BufferInfo mInfo; + final byte[] mData = new byte[mAshmemSize]; + HistoricalBuffer(ActiveBuffer active) throws IOException { + mInfo = active.mInfo; + mInfo.mEndTime = System.currentTimeMillis(); + active.readBytes(mData, mAshmemSize); + } + } +} diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java index 3b864139cf71..d08bfcf45a5c 100644 --- a/graphics/java/android/graphics/HardwareRenderer.java +++ b/graphics/java/android/graphics/HardwareRenderer.java @@ -157,7 +157,7 @@ public class HardwareRenderer { public HardwareRenderer() { mRootNode = RenderNode.adopt(nCreateRootRenderNode()); mRootNode.setClipToBounds(false); - mNativeProxy = nCreateProxy(!mOpaque, mRootNode.mNativeRenderNode); + mNativeProxy = nCreateProxy(!mOpaque, mIsWideGamut, mRootNode.mNativeRenderNode); if (mNativeProxy == 0) { throw new OutOfMemoryError("Unable to create hardware renderer"); } @@ -1085,7 +1085,8 @@ public class HardwareRenderer { private static native long nCreateRootRenderNode(); - private static native long nCreateProxy(boolean translucent, long rootRenderNode); + private static native long nCreateProxy(boolean translucent, boolean isWideGamut, + long rootRenderNode); private static native void nDeleteProxy(long nativeProxy); diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java index ba96a06cc852..4899fbe431cc 100644 --- a/graphics/java/android/graphics/fonts/Font.java +++ b/graphics/java/android/graphics/fonts/Font.java @@ -19,6 +19,7 @@ package android.graphics.fonts; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; import android.content.res.Resources; import android.os.LocaleList; @@ -35,7 +36,9 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.channels.FileChannel; import java.util.Arrays; import java.util.Objects; @@ -54,10 +57,6 @@ public final class Font { * A builder class for creating new Font. */ public static final class Builder { - private static final NativeAllocationRegistry sAssetByteBufferRegistry = - NativeAllocationRegistry.createMalloced(ByteBuffer.class.getClassLoader(), - nGetReleaseNativeAssetFunc()); - private static final NativeAllocationRegistry sFontRegistry = NativeAllocationRegistry.createMalloced(Font.class.getClassLoader(), nGetReleaseNativeFont()); @@ -151,7 +150,11 @@ public final class Font { * @param path the file name of the font data in the asset directory */ public Builder(@NonNull AssetManager am, @NonNull String path) { - this(am, path, true /* is asset */, 0 /* cookie */); + try { + mBuffer = createBuffer(am, path, true /* is asset */, 0 /* cookie */); + } catch (IOException e) { + mException = e; + } } /** @@ -165,18 +168,11 @@ public final class Font { */ public Builder(@NonNull AssetManager am, @NonNull String path, boolean isAsset, int cookie) { - final long nativeAsset = nGetNativeAsset(am, path, isAsset, cookie); - if (nativeAsset == 0) { - mException = new FileNotFoundException("Unable to open " + path); - return; - } - final ByteBuffer b = nGetAssetBuffer(nativeAsset); - sAssetByteBufferRegistry.registerNativeAllocation(b, nativeAsset); - if (b == null) { - mException = new FileNotFoundException(path + " not found"); - return; + try { + mBuffer = createBuffer(am, path, isAsset, cookie); + } catch (IOException e) { + mException = e; } - mBuffer = b; } /** @@ -199,19 +195,64 @@ public final class Font { mException = new FileNotFoundException(resId + " must be font file."); return; } - final long nativeAsset = nGetNativeAsset(res.getAssets(), str, false /* is asset */, - value.assetCookie); - if (nativeAsset == 0) { - mException = new FileNotFoundException("Unable to open " + str); - return; + + try { + mBuffer = createBuffer(res.getAssets(), str, false, value.assetCookie); + } catch (IOException e) { + mException = e; } - final ByteBuffer b = nGetAssetBuffer(nativeAsset); - sAssetByteBufferRegistry.registerNativeAllocation(b, nativeAsset); - if (b == null) { - mException = new FileNotFoundException(str + " not found"); - return; + } + + /** + * Creates a buffer containing font data using the assetManager and other + * provided inputs. + * + * @param am the application's asset manager + * @param path the file name of the font data in the asset directory + * @param isAsset true if the undelying data is in asset + * @param cookie set asset cookie + * @return buffer containing the contents of the file + * + * @hide + */ + public static ByteBuffer createBuffer(@NonNull AssetManager am, @NonNull String path, + boolean isAsset, int cookie) throws IOException { + Preconditions.checkNotNull(am, "assetManager can not be null"); + Preconditions.checkNotNull(path, "path can not be null"); + + if (!isAsset) { + // Attempt to open as FD, which should work unless the asset is compressed + AssetFileDescriptor assetFD; + try { + if (cookie > 0) { + assetFD = am.openNonAssetFd(cookie, path); + } else { + assetFD = am.openNonAssetFd(path); + } + + try (FileInputStream fis = assetFD.createInputStream()) { + final FileChannel fc = fis.getChannel(); + return fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); + } + } catch (IOException e) { + // failed to open as FD so now we will attempt to open as an input stream + } + } + + try (InputStream assetStream = isAsset ? am.open(path, AssetManager.ACCESS_BUFFER) + : am.openNonAsset(cookie, path, AssetManager.ACCESS_BUFFER)) { + + int capacity = assetStream.available(); + ByteBuffer buffer = ByteBuffer.allocateDirect(capacity); + buffer.order(ByteOrder.nativeOrder()); + assetStream.read(buffer.array(), buffer.arrayOffset(), assetStream.available()); + + if (assetStream.read() != -1) { + throw new IOException("Unable to access full contents of " + path); + } + + return buffer; } - mBuffer = b; } /** @@ -396,15 +437,6 @@ public final class Font { } /** - * Native methods for accessing underlying buffer in Asset - */ - private static native long nGetNativeAsset( - @NonNull AssetManager am, @NonNull String path, boolean isAsset, int cookie); - private static native ByteBuffer nGetAssetBuffer(long nativeAsset); - @CriticalNative - private static native long nGetReleaseNativeAssetFunc(); - - /** * Native methods for creating Font */ private static native long nInitBuilder(); |