1 /*
<lambda>null2  * 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 androidx.lifecycle.AndroidViewModel
24 import androidx.lifecycle.LiveData
25 import androidx.lifecycle.MutableLiveData
26 import kotlinx.coroutines.CoroutineScope
27 import kotlinx.coroutines.Job
28 import kotlinx.coroutines.asCoroutineDispatcher
29 import kotlinx.coroutines.launch
30 import java.io.File
31 import java.util.concurrent.Executor
32 import java.util.concurrent.Executors
33 
34 class PdfRendererBasicViewModel @JvmOverloads constructor(
35         application: Application,
36         useInstantExecutor: Boolean = false
37 ) : AndroidViewModel(application) {
38 
39     companion object {
40         const val FILENAME = "sample.pdf"
41     }
42 
43     private val job = Job()
44     private val executor = if (useInstantExecutor) {
45         Executor { it.run() }
46     } else {
47         Executors.newSingleThreadExecutor()
48     }
49     private val scope = CoroutineScope(executor.asCoroutineDispatcher() + job)
50 
51     private var fileDescriptor: ParcelFileDescriptor? = null
52     private var pdfRenderer: PdfRenderer? = null
53     private var currentPage: PdfRenderer.Page? = null
54     private var cleared = false
55 
56     private val _pageBitmap = MutableLiveData<Bitmap>()
57     val pageBitmap: LiveData<Bitmap>
58         get() = _pageBitmap
59 
60     private val _previousEnabled = MutableLiveData<Boolean>()
61     val previousEnabled: LiveData<Boolean>
62         get() = _previousEnabled
63 
64     private val _nextEnabled = MutableLiveData<Boolean>()
65     val nextEnabled: LiveData<Boolean>
66         get() = _nextEnabled
67 
68     private val _pageInfo = MutableLiveData<Pair<Int, Int>>()
69     val pageInfo: LiveData<Pair<Int, Int>>
70         get() = _pageInfo
71 
72     init {
73         scope.launch {
74             openPdfRenderer()
75             showPage(0)
76             if (cleared) {
77                 closePdfRenderer()
78             }
79         }
80     }
81 
82     override fun onCleared() {
83         super.onCleared()
84         scope.launch {
85             closePdfRenderer()
86             cleared = true
87             job.cancel()
88         }
89     }
90 
91     fun showPrevious() {
92         scope.launch {
93             currentPage?.let { page ->
94                 if (page.index > 0) {
95                     showPage(page.index - 1)
96                 }
97             }
98         }
99     }
100 
101     fun showNext() {
102         scope.launch {
103             pdfRenderer?.let { renderer ->
104                 currentPage?.let { page ->
105                     if (page.index + 1 < renderer.pageCount) {
106                         showPage(page.index + 1)
107                     }
108                 }
109             }
110         }
111     }
112 
113     private fun openPdfRenderer() {
114         val application = getApplication<Application>()
115         val file = File(application.cacheDir, FILENAME)
116         if (!file.exists()) {
117             application.assets.open(FILENAME).use { asset ->
118                 file.writeBytes(asset.readBytes())
119             }
120         }
121         fileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY).also {
122             pdfRenderer = PdfRenderer(it)
123         }
124     }
125 
126     private fun showPage(index: Int) {
127         // Make sure to close the current page before opening another one.
128         currentPage?.let { page ->
129             currentPage = null
130             page.close()
131         }
132         pdfRenderer?.let { renderer ->
133             // Use `openPage` to open a specific page in PDF.
134             val page = renderer.openPage(index).also {
135                 currentPage = it
136             }
137             // Important: the destination bitmap must be ARGB (not RGB).
138             val bitmap = Bitmap.createBitmap(page.width, page.height, Bitmap.Config.ARGB_8888)
139             // Here, we render the page onto the Bitmap.
140             // To render a portion of the page, use the second and third parameter. Pass nulls to get
141             // the default result.
142             // Pass either RENDER_MODE_FOR_DISPLAY or RENDER_MODE_FOR_PRINT for the last parameter.
143             page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
144             _pageBitmap.postValue(bitmap)
145             val count = renderer.pageCount
146             _pageInfo.postValue(index to count)
147             _previousEnabled.postValue(index > 0)
148             _nextEnabled.postValue(index + 1 < count)
149         }
150     }
151 
152     private fun closePdfRenderer() {
153         currentPage?.close()
154         pdfRenderer?.close()
155         fileDescriptor?.close()
156     }
157 
158 }
159