Home | History | Annotate | Download | only in pdfrendererbasic
      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