From d522fe640128a573d5721743ce71357d346bbeef Mon Sep 17 00:00:00 2001 From: Julian Mancini Date: Tue, 11 Jul 2017 12:51:35 -0700 Subject: [PATCH] Add provider level support for PDF Thumbnails Bug: 63586859 Test: N/A Change-Id: I14b0dec18493478b3bd3585eab62aa64da909838 --- .../internal/content/FileSystemProvider.java | 15 ++- .../android/internal/content/PdfUtils.java | 96 +++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 core/java/com/android/internal/content/PdfUtils.java diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java index ebea1a46aed84..04874bd456ae1 100644 --- a/core/java/com/android/internal/content/FileSystemProvider.java +++ b/core/java/com/android/internal/content/FileSystemProvider.java @@ -28,6 +28,7 @@ import android.database.MatrixCursor.RowBuilder; import android.graphics.Point; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.CancellationSignal; import android.os.FileObserver; import android.os.FileUtils; @@ -68,6 +69,8 @@ public abstract class FileSystemProvider extends DocumentsProvider { private Handler mHandler; + private static final String MIMETYPE_PDF = "application/pdf"; + protected abstract File getFileForDocId(String docId, boolean visible) throws FileNotFoundException; @@ -387,6 +390,13 @@ public abstract class FileSystemProvider extends DocumentsProvider { String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException { final File file = getFileForDocId(documentId); + if (getTypeForFile(file).equals(MIMETYPE_PDF)) { + try { + return PdfUtils.openPdfThumbnail(file, sizeHint); + } catch (Exception e) { + Log.v(TAG, "Could not load PDF's thumbnail", e); + } + } return DocumentsContract.openImageThumbnail(file); } @@ -416,7 +426,10 @@ public abstract class FileSystemProvider extends DocumentsProvider { final String mimeType = getTypeForFile(file); final String displayName = file.getName(); - if (mimeType.startsWith("image/")) { + // As of right now, we aren't sure on the performance affect of loading all PDF Thumbnails + // Until a solution is found, it will be behind a debuggable flag. + if (mimeType.startsWith("image/") + || (mimeType.equals(MIMETYPE_PDF) && Build.IS_DEBUGGABLE)) { flags |= Document.FLAG_SUPPORTS_THUMBNAIL; } diff --git a/core/java/com/android/internal/content/PdfUtils.java b/core/java/com/android/internal/content/PdfUtils.java new file mode 100644 index 0000000000000..1716d426b362e --- /dev/null +++ b/core/java/com/android/internal/content/PdfUtils.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2017 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 com.android.internal.content; + +import android.annotation.Nullable; +import android.content.res.AssetFileDescriptor; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.pdf.PdfRenderer; +import android.os.AsyncTask; +import android.os.ParcelFileDescriptor; + +import libcore.io.IoUtils; +import libcore.io.Streams; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * Utils class for extracting PDF Thumbnails + */ +public final class PdfUtils { + + private PdfUtils() { + } + + /** + * Returns the front page of the pdf as a thumbnail + * @param file Given PDF File + * @param size Cropping of the front page. + * @return AssetFileDescriptor containing the thumbnail as a bitmap. + * @throws IOException if the file isn't a pdf or if the file doesn't exist. + */ + public static @Nullable AssetFileDescriptor openPdfThumbnail(File file, Point size) + throws IOException { + // Create the bitmap of the PDF's first page + ParcelFileDescriptor pdfDescriptor = + ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); + PdfRenderer renderer = new PdfRenderer(pdfDescriptor); + PdfRenderer.Page frontPage = renderer.openPage(0); + Bitmap thumbnail = Bitmap.createBitmap(size.x, size.y, + Bitmap.Config.ARGB_8888); + frontPage.render(thumbnail, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY); + + // Create an AssetFileDescriptor that contains the Bitmap's information + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + // Quality is an integer that determines how much compression is used. + // However, this integer is ignored when using the PNG format + int quality = 100; + // The use of Bitmap.CompressFormat.JPEG leads to a black PDF background on the thumbnail + thumbnail.compress(Bitmap.CompressFormat.PNG, quality, out); + + final ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); + + final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createReliablePipe(); + new AsyncTask() { + @Override + protected Object doInBackground(Object... params) { + final FileOutputStream fos = new FileOutputStream(fds[1].getFileDescriptor()); + try { + Streams.copy(in, fos); + } catch (IOException e) { + throw new RuntimeException(e); + } + IoUtils.closeQuietly(fds[1]); + try { + pdfDescriptor.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + return null; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + pdfDescriptor.close(); + return new AssetFileDescriptor(fds[0], 0, AssetFileDescriptor.UNKNOWN_LENGTH); + } + +}