1 /* 2 * Copyright (C) 2019 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 com.example.android.pdfrendererbasic; 18 19 import android.app.Application; 20 import android.graphics.Bitmap; 21 import android.graphics.pdf.PdfRenderer; 22 import android.os.ParcelFileDescriptor; 23 import android.util.Log; 24 25 import java.io.File; 26 import java.io.FileOutputStream; 27 import java.io.IOException; 28 import java.io.InputStream; 29 import java.util.concurrent.Executor; 30 import java.util.concurrent.Executors; 31 32 import androidx.annotation.WorkerThread; 33 import androidx.lifecycle.AndroidViewModel; 34 import androidx.lifecycle.LiveData; 35 import androidx.lifecycle.MutableLiveData; 36 37 /** 38 * This holds all the data we need for this sample app. 39 * <p> 40 * We use a {@link android.graphics.pdf.PdfRenderer} to render PDF pages as 41 * {@link android.graphics.Bitmap}s. 42 */ 43 public class PdfRendererBasicViewModel extends AndroidViewModel { 44 45 private static final String TAG = "PdfRendererBasic"; 46 47 /** 48 * The filename of the PDF. 49 */ 50 private static final String FILENAME = "sample.pdf"; 51 52 private final MutableLiveData<PageInfo> mPageInfo = new MutableLiveData<>(); 53 private final MutableLiveData<Bitmap> mPageBitmap = new MutableLiveData<>(); 54 private final MutableLiveData<Boolean> mPreviousEnabled = new MutableLiveData<>(); 55 private final MutableLiveData<Boolean> mNextEnabled = new MutableLiveData<>(); 56 57 private final Executor mExecutor; 58 private ParcelFileDescriptor mFileDescriptor; 59 private PdfRenderer mPdfRenderer; 60 private PdfRenderer.Page mCurrentPage; 61 private boolean mCleared; 62 63 @SuppressWarnings("unused") 64 public PdfRendererBasicViewModel(Application application) { 65 this(application, false); 66 } 67 68 PdfRendererBasicViewModel(Application application, boolean useInstantExecutor) { 69 super(application); 70 if (useInstantExecutor) { 71 mExecutor = Runnable::run; 72 } else { 73 mExecutor = Executors.newSingleThreadExecutor(); 74 } 75 mExecutor.execute(() -> { 76 try { 77 openPdfRenderer(); 78 showPage(0); 79 if (mCleared) { 80 closePdfRenderer(); 81 } 82 } catch (IOException e) { 83 Log.e(TAG, "Failed to open PdfRenderer", e); 84 } 85 }); 86 } 87 88 @Override 89 protected void onCleared() { 90 super.onCleared(); 91 mExecutor.execute(() -> { 92 try { 93 closePdfRenderer(); 94 mCleared = true; 95 } catch (IOException e) { 96 Log.i(TAG, "Failed to close PdfRenderer", e); 97 } 98 }); 99 } 100 101 LiveData<PageInfo> getPageInfo() { 102 return mPageInfo; 103 } 104 105 LiveData<Bitmap> getPageBitmap() { 106 return mPageBitmap; 107 } 108 109 LiveData<Boolean> getPreviousEnabled() { 110 return mPreviousEnabled; 111 } 112 113 LiveData<Boolean> getNextEnabled() { 114 return mNextEnabled; 115 } 116 117 void showPrevious() { 118 if (mPdfRenderer == null || mCurrentPage == null) { 119 return; 120 } 121 final int index = mCurrentPage.getIndex(); 122 if (index > 0) { 123 mExecutor.execute(() -> showPage(index - 1)); 124 } 125 } 126 127 void showNext() { 128 if (mPdfRenderer == null || mCurrentPage == null) { 129 return; 130 } 131 final int index = mCurrentPage.getIndex(); 132 if (index + 1 < mPdfRenderer.getPageCount()) { 133 mExecutor.execute(() -> showPage(index + 1)); 134 } 135 } 136 137 @WorkerThread 138 private void openPdfRenderer() throws IOException { 139 final File file = new File(getApplication().getCacheDir(), FILENAME); 140 if (!file.exists()) { 141 // Since PdfRenderer cannot handle the compressed asset file directly, we copy it into 142 // the cache directory. 143 final InputStream asset = getApplication().getAssets().open(FILENAME); 144 final FileOutputStream output = new FileOutputStream(file); 145 final byte[] buffer = new byte[1024]; 146 int size; 147 while ((size = asset.read(buffer)) != -1) { 148 output.write(buffer, 0, size); 149 } 150 asset.close(); 151 output.close(); 152 } 153 mFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); 154 if (mFileDescriptor != null) { 155 mPdfRenderer = new PdfRenderer(mFileDescriptor); 156 } 157 } 158 159 @WorkerThread 160 private void closePdfRenderer() throws IOException { 161 if (mCurrentPage != null) { 162 mCurrentPage.close(); 163 } 164 if (mPdfRenderer != null) { 165 mPdfRenderer.close(); 166 } 167 if (mFileDescriptor != null) { 168 mFileDescriptor.close(); 169 } 170 } 171 172 @WorkerThread 173 private void showPage(int index) { 174 // Make sure to close the current page before opening another one. 175 if (null != mCurrentPage) { 176 mCurrentPage.close(); 177 } 178 // Use `openPage` to open a specific page in PDF. 179 mCurrentPage = mPdfRenderer.openPage(index); 180 // Important: the destination bitmap must be ARGB (not RGB). 181 final Bitmap bitmap = Bitmap.createBitmap(mCurrentPage.getWidth(), mCurrentPage.getHeight(), 182 Bitmap.Config.ARGB_8888); 183 // Here, we render the page onto the Bitmap. 184 // To render a portion of the page, use the second and third parameter. Pass nulls to get 185 // the default result. 186 // Pass either RENDER_MODE_FOR_DISPLAY or RENDER_MODE_FOR_PRINT for the last parameter. 187 mCurrentPage.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY); 188 mPageBitmap.postValue(bitmap); 189 final int count = mPdfRenderer.getPageCount(); 190 mPageInfo.postValue(new PageInfo(index, count)); 191 mPreviousEnabled.postValue(index > 0); 192 mNextEnabled.postValue(index + 1 < count); 193 } 194 195 static class PageInfo { 196 final int index; 197 final int count; 198 199 PageInfo(int index, int count) { 200 this.index = index; 201 this.count = count; 202 } 203 } 204 205 } 206