├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── jarRepositories.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── personal │ │ └── android │ │ └── androidpdfreader │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── personal │ │ │ └── android │ │ │ └── androidpdfreader │ │ │ ├── home │ │ │ ├── PdfActivity.kt │ │ │ ├── PdfReaderAdapter.kt │ │ │ ├── PdfReaderViewHolder.kt │ │ │ └── PdfViewModel.kt │ │ │ └── util │ │ │ ├── Constants.kt │ │ │ ├── Extensions.kt │ │ │ ├── FilePathUtil.kt │ │ │ ├── PdfHelper.kt │ │ │ ├── PdfRendererExtensions.kt │ │ │ ├── PermissionCommonUtil.kt │ │ │ └── PinchToZoomRecyclerView.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_pdf.xml │ │ └── cell_pdf_page.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── personal │ └── android │ └── androidpdfreader │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── pdf_reader_demo.gif └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 20 | 22 | 23 | 24 | 26 | 27 | 28 |
29 | 30 | 31 | 32 | xmlns:android 33 | 34 | ^$ 35 | 36 | 37 | 38 |
39 |
40 | 41 | 42 | 43 | xmlns:.* 44 | 45 | ^$ 46 | 47 | 48 | BY_NAME 49 | 50 |
51 |
52 | 53 | 54 | 55 | .*:id 56 | 57 | http://schemas.android.com/apk/res/android 58 | 59 | 60 | 61 |
62 |
63 | 64 | 65 | 66 | .*:name 67 | 68 | http://schemas.android.com/apk/res/android 69 | 70 | 71 | 72 |
73 |
74 | 75 | 76 | 77 | name 78 | 79 | ^$ 80 | 81 | 82 | 83 |
84 |
85 | 86 | 87 | 88 | style 89 | 90 | ^$ 91 | 92 | 93 | 94 |
95 |
96 | 97 | 98 | 99 | .* 100 | 101 | ^$ 102 | 103 | 104 | BY_NAME 105 | 106 |
107 |
108 | 109 | 110 | 111 | .* 112 | 113 | http://schemas.android.com/apk/res/android 114 | 115 | 116 | ANDROID_ATTRIBUTE_ORDER 117 | 118 |
119 |
120 | 121 | 122 | 123 | .* 124 | 125 | .* 126 | 127 | 128 | BY_NAME 129 | 130 |
131 |
132 |
133 |
134 | 135 | 137 |
138 |
-------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidPdfReader 2 | 3 | Android Pdf Reader to read PDFs without increasing app size.
4 | 5 | ![Pdf View](https://github.com/aditya09tyagi/AndroidPdfReader/blob/master/pdf_reader_demo.gif) 6 |
7 |
8 | To add in your .xml file : 9 | 10 | ``` 11 | 16 | ``` 17 | 18 | In your PDFViewModel (kotlin/java) file add
19 | 20 | ``` 21 | var pdfRenderer: PdfRenderer? = null 22 | 23 | fun getPdfRenderer(filePath: String) { 24 | try { 25 | pdfRenderer = PdfRenderer( 26 | ParcelFileDescriptor.open( 27 | File(filePath), 28 | ParcelFileDescriptor.MODE_READ_ONLY 29 | ) 30 | ) 31 | } catch (exception: Exception) { 32 | throwExceptionForPdfRenderer(exception) 33 | } 34 | } 35 | 36 | private fun throwExceptionForPdfRenderer(exception: Exception) { 37 | val message: String = when (exception) { 38 | is IOException -> exception.localizedMessage 39 | is SecurityException -> exception.localizedMessage 40 | is FileNotFoundException -> exception.localizedMessage 41 | else -> exception.localizedMessage 42 | } 43 | Log.d("PDF Renderer exception", message) 44 | } 45 | 46 | ``` 47 |

48 | The PdfRenderer isn't meant to be a full blown PDF solution.If you need to cover more advanced cases, it probably won't be enough for you. 49 | As an example, it doesn't support annotations and it has issues dealing with password protected and corrupted files.That's why the above mentioned 50 | exceptions are thrown and in the case if any exception is thrown we use the alternative of showing the pdf in webView with a Google Drive pdf URL which 51 | enable us to rectify our issue upto some extent. 52 |

53 | 54 | 55 | ``` 56 | private fun setAdapter() { 57 | viewModel.getPdfRenderer(pdfPath) //Path of the pdf you want to render 58 | viewModel.pdfRenderer?.let { 59 | val displayMetrics = DisplayMetrics() 60 | windowManager.defaultDisplay.getMetrics(displayMetrics) 61 | val width = displayMetrics.widthPixels 62 | rvPdfView.layoutManager = LinearLayoutManager(this) 63 | pdfReaderAdapter = PdfReaderAdapter(pdfRenderer, width) 64 | rvPdfView.adapter = pdfReaderAdapter //set your adapter here 65 | } ?: run { 66 | takeActionForPdfRendererNull() //if exception is thrown and renderer is null show it in web view(refer PdfActivity) 67 | } 68 | } 69 | } 70 | ``` 71 | 72 | Now coming to point where you set your view of the PDF there you can use [PhotoView library by chris banes](https://github.com/chrisbanes/PhotoView) for 73 | zoom in purpose but the issue that arises here is you'll be able to zoom only a single page instead of whole recycler view so instead of that you can use 74 | [PinchToZoomRecyclerView](https://github.com/aditya09tyagi/AndroidPdfReader/blob/master/app/src/main/java/com/personal/android/androidpdfreader/util/PinchToZoomRecyclerView.kt) 75 | which enables you to perform zoom in ,zoom out ,double tap to zoom action on the whole recycler view just like you can do in any other PDF library. 76 | 77 | 78 | Through pdf renderer you can either opt. to render a single page or else you can use the following code where you create a bitmap for each and every page you render and then use that bitmap as a page of your pdf 79 | ``` 80 | fun PdfRenderer.Page.renderAndClose(width: Int) = use { 81 | val bitmap = createBitmap(width) 82 | render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY) 83 | bitmap 84 | } 85 | 86 | private fun PdfRenderer.Page.createBitmap(bitmapWidth: Int): Bitmap { 87 | val bitmap = Bitmap.createBitmap( 88 | bitmapWidth, (bitmapWidth.toFloat() / width * height).toInt(), Bitmap.Config.ARGB_8888 89 | ) 90 | 91 | val canvas = Canvas(bitmap) 92 | canvas.drawColor(Color.WHITE) 93 | canvas.drawBitmap(bitmap, 0f, 0f, null) 94 | 95 | return bitmap 96 | } 97 | ``` 98 | 99 | So for further information you can go through the project and you can get a better idea, 100 | basically this project is more like a pdf reader app you can use it to open pdf in you phone through this application . 101 | 102 | Thanks and hope it helps everyone..!! 103 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { url 'https://plugins.gradle.org/m2/' } 4 | maven { url 'https://maven.google.com' } 5 | } 6 | } 7 | apply plugin: 'com.android.application' 8 | 9 | apply plugin: 'kotlin-android' 10 | 11 | apply plugin: 'kotlin-android-extensions' 12 | 13 | apply plugin: 'kotlin-kapt' 14 | 15 | 16 | android { 17 | compileSdkVersion 29 18 | buildToolsVersion "29.0.3" 19 | 20 | defaultConfig { 21 | applicationId "com.personal.android.androidpdfreader" 22 | minSdkVersion 21 23 | targetSdkVersion 29 24 | versionCode 1 25 | versionName "1.0" 26 | 27 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 28 | } 29 | 30 | buildTypes { 31 | release { 32 | minifyEnabled false 33 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 34 | } 35 | } 36 | 37 | aaptOptions { 38 | noCompress "pdf" 39 | } 40 | 41 | compileOptions { 42 | sourceCompatibility JavaVersion.VERSION_1_8 43 | targetCompatibility JavaVersion.VERSION_1_8 44 | } 45 | 46 | } 47 | 48 | dependencies { 49 | 50 | def coroutines_version = '1.3.7' 51 | def lifecycle_version = '2.2.0' 52 | def runtime_permissions_version = '4.6.0' 53 | 54 | implementation fileTree(dir: "libs", include: ["*.jar"]) 55 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 56 | implementation 'androidx.core:core-ktx:1.3.1' 57 | implementation 'androidx.appcompat:appcompat:1.2.0' 58 | implementation 'androidx.constraintlayout:constraintlayout:2.0.1' 59 | 60 | //Testing 61 | testImplementation 'junit:junit:4.13' 62 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 63 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 64 | 65 | 66 | //Material Design 67 | implementation 'com.google.android.material:material:1.3.0-alpha02' 68 | 69 | 70 | //Kotlin Coroutines 71 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" 72 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" 73 | 74 | 75 | // Lifecycle components 76 | implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" 77 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" 78 | kapt "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" 79 | 80 | 81 | //RunTime Permissions 82 | implementation "org.permissionsdispatcher:permissionsdispatcher:$runtime_permissions_version" 83 | kapt "org.permissionsdispatcher:permissionsdispatcher-processor:$runtime_permissions_version" 84 | 85 | 86 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/personal/android/androidpdfreader/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.personal.android.androidpdfreader 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.personal.android.androidpdfreader", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/personal/android/androidpdfreader/home/PdfActivity.kt: -------------------------------------------------------------------------------- 1 | package com.personal.android.androidpdfreader.home 2 | 3 | import android.Manifest 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.graphics.pdf.PdfRenderer 7 | import android.net.Uri 8 | import android.os.Bundle 9 | import android.util.DisplayMetrics 10 | import android.webkit.WebResourceError 11 | import android.webkit.WebResourceRequest 12 | import android.webkit.WebView 13 | import android.webkit.WebViewClient 14 | import android.widget.Toast 15 | import androidx.appcompat.app.AppCompatActivity 16 | import androidx.lifecycle.ViewModelProvider 17 | import androidx.recyclerview.widget.LinearLayoutManager 18 | import com.personal.android.androidpdfreader.R 19 | import com.personal.android.androidpdfreader.util.* 20 | import kotlinx.android.synthetic.main.activity_pdf.* 21 | 22 | 23 | class PdfActivity : AppCompatActivity(), PermissionCommonUtil.OnPermissionRequestListener { 24 | 25 | private lateinit var pdfReaderAdapter: PdfReaderAdapter 26 | private lateinit var viewModel: PdfViewModel 27 | private var pdfUri: Uri? = null 28 | private lateinit var title: String 29 | private lateinit var pdfPath: String 30 | private lateinit var permissionCommonUtil: PermissionCommonUtil 31 | private val readWritePermissionRequestCode=565 32 | private val permissions = arrayOf( 33 | Manifest.permission.READ_EXTERNAL_STORAGE, 34 | Manifest.permission.WRITE_EXTERNAL_STORAGE 35 | ) 36 | 37 | companion object { 38 | fun newIntent(context: Context) = Intent(context, PdfActivity::class.java) 39 | } 40 | 41 | override fun onCreate(savedInstanceState: Bundle?) { 42 | super.onCreate(savedInstanceState) 43 | setContentView(R.layout.activity_pdf) 44 | initPermission() 45 | checkReadWritePermission() 46 | getArguments() 47 | initViewModel() 48 | setAdapter() 49 | } 50 | 51 | private fun initViewModel() { 52 | viewModel = ViewModelProvider(this).get(PdfViewModel::class.java) 53 | } 54 | 55 | private fun initPermission(){ 56 | permissionCommonUtil = PermissionCommonUtil(this,permissions,readWritePermissionRequestCode,this) 57 | } 58 | 59 | private fun getArguments(){ 60 | intent?.let { 61 | if (it.action == Intent.ACTION_VIEW) { 62 | if (it.data != null) { 63 | try { 64 | pdfUri = it.data 65 | FilePathUtil.getPath(this, it.data!!)?.let { 66 | pdfPath = it 67 | } 68 | title = pdfPath.substringAfterLast("/") 69 | } catch (e: Exception) { 70 | pdfUri?.let { 71 | title = it.path!! 72 | } 73 | } 74 | 75 | } 76 | } 77 | } 78 | } 79 | 80 | private fun setAdapter() { 81 | if (::viewModel.isInitialized && ::pdfPath.isInitialized && ::title.isInitialized) { 82 | toolbar.title = title 83 | viewModel.getPdfRenderer(pdfPath) 84 | viewModel.pdfRenderer?.let { 85 | takeActionForPdfRendererNotNull(it) 86 | } ?: run { 87 | takeActionForPdfRendererNull() 88 | } 89 | } 90 | } 91 | 92 | private fun takeActionForPdfRendererNotNull(pdfRenderer: PdfRenderer) { 93 | val displayMetrics = DisplayMetrics() 94 | windowManager.defaultDisplay.getMetrics(displayMetrics) 95 | val width = displayMetrics.widthPixels 96 | rvPdfView.visible() 97 | pdfWebView.gone() 98 | rvPdfView.layoutManager = LinearLayoutManager(this) 99 | pdfReaderAdapter = PdfReaderAdapter(pdfRenderer, width) 100 | rvPdfView.adapter = pdfReaderAdapter 101 | } 102 | 103 | private fun takeActionForPdfRendererNull() { 104 | rvPdfView.gone() 105 | pdfWebView.visible() 106 | showPdfInWebView() 107 | } 108 | 109 | private fun showPdfInWebView() { 110 | if (::pdfPath.isInitialized && pdfPath.isNotEmpty()) { 111 | pdfWebView.loadUrl(Constants.DEFAULT_GOOGLE_PDF_URL + pdfPath) 112 | setParametersToPdfWebView() 113 | attachWebViewClientToPdfWebView() 114 | } 115 | } 116 | 117 | private fun setParametersToPdfWebView() { 118 | pdfWebView.settings.setSupportZoom(true) 119 | pdfWebView.settings.allowFileAccess = true 120 | pdfWebView.settings.allowUniversalAccessFromFileURLs = true 121 | pdfWebView.settings.displayZoomControls = true 122 | pdfWebView.isHapticFeedbackEnabled = false 123 | pdfWebView.settings.javaScriptEnabled = true 124 | } 125 | 126 | private fun attachWebViewClientToPdfWebView() { 127 | pdfWebView.webViewClient = WebViewClient() 128 | pdfWebView.webViewClient = object : WebViewClient() { 129 | override fun onPageFinished(view: WebView?, url: String?) { 130 | super.onPageFinished(view, url) 131 | if (view?.title == Constants.EMPTY_STRING) 132 | view.reload() 133 | } 134 | 135 | override fun onReceivedError( 136 | view: WebView?, 137 | request: WebResourceRequest?, 138 | error: WebResourceError? 139 | ) { 140 | super.onReceivedError(view, request, error) 141 | } 142 | } 143 | } 144 | 145 | 146 | override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { 147 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 148 | permissionCommonUtil.onPermissionResult(requestCode, permissions, grantResults) 149 | } 150 | 151 | override fun onDestroy() { 152 | super.onDestroy() 153 | if (::viewModel.isInitialized) 154 | viewModel.pdfRenderer?.close() 155 | } 156 | 157 | private fun checkReadWritePermission() { 158 | if (permissionCommonUtil.isReadWritePermissionAvailable()) { 159 | permissionGranted() 160 | } else { 161 | permissionCommonUtil.requestReadWritePermission() 162 | } 163 | } 164 | 165 | override fun permissionGranted() { 166 | Toast.makeText(this,"Permission Granted",Toast.LENGTH_SHORT).show() 167 | } 168 | 169 | override fun permissionDenied() { 170 | Toast.makeText(this,"Please Grant Permission",Toast.LENGTH_SHORT).show() 171 | } 172 | 173 | override fun shouldShowRationalMessage() { 174 | Toast.makeText(this,"Please Allow Permission",Toast.LENGTH_SHORT).show() 175 | } 176 | 177 | } -------------------------------------------------------------------------------- /app/src/main/java/com/personal/android/androidpdfreader/home/PdfReaderAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.personal.android.androidpdfreader.home 2 | 3 | import android.graphics.pdf.PdfRenderer 4 | import android.view.LayoutInflater 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.personal.android.androidpdfreader.R 8 | 9 | class PdfReaderAdapter( 10 | private val renderer: PdfRenderer, 11 | private val pageWidth: Int 12 | ) : RecyclerView.Adapter() { 13 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PdfReaderViewHolder { 14 | val inflater = LayoutInflater.from(parent.context) 15 | val view = inflater.inflate(R.layout.cell_pdf_page, parent, false) 16 | return PdfReaderViewHolder(view, renderer, pageWidth) 17 | } 18 | 19 | override fun getItemCount(): Int { 20 | return renderer.pageCount 21 | } 22 | 23 | override fun onBindViewHolder(holder: PdfReaderViewHolder, position: Int) { 24 | holder.setPdfPage() 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/personal/android/androidpdfreader/home/PdfReaderViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.personal.android.androidpdfreader.home 2 | 3 | import android.graphics.pdf.PdfRenderer 4 | import android.view.View 5 | import android.widget.ImageView 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.personal.android.androidpdfreader.util.renderAndClose 8 | 9 | class PdfReaderViewHolder(itemView: View, private val pdfRenderer: PdfRenderer, private val pageWidth: Int) : 10 | RecyclerView.ViewHolder(itemView) { 11 | 12 | fun setPdfPage() { 13 | (itemView as ImageView).setImageBitmap(pdfRenderer.openPage(adapterPosition).renderAndClose(pageWidth)) 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/personal/android/androidpdfreader/home/PdfViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.personal.android.androidpdfreader.home 2 | 3 | import android.graphics.pdf.PdfRenderer 4 | import android.os.ParcelFileDescriptor 5 | import android.util.Log 6 | import androidx.lifecycle.ViewModel 7 | import java.io.* 8 | 9 | class PdfViewModel : ViewModel() { 10 | 11 | var pdfRenderer: PdfRenderer? = null 12 | 13 | fun getPdfRenderer(filePath: String) { 14 | try { 15 | pdfRenderer = PdfRenderer( 16 | ParcelFileDescriptor.open( 17 | File(filePath), 18 | ParcelFileDescriptor.MODE_READ_ONLY 19 | ) 20 | ) 21 | } catch (exception: Exception) { 22 | throwExceptionForPdfRenderer(exception) 23 | } 24 | } 25 | 26 | private fun throwExceptionForPdfRenderer(exception: Exception) { 27 | val message: String = when (exception) { 28 | is IOException -> exception.localizedMessage 29 | is SecurityException -> exception.localizedMessage 30 | is FileNotFoundException -> exception.localizedMessage 31 | else -> exception.localizedMessage 32 | } 33 | Log.d("PDF Renderer exception", message) 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/personal/android/androidpdfreader/util/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.personal.android.androidpdfreader.util 2 | 3 | interface Constants { 4 | 5 | companion object{ 6 | const val DEFAULT_GOOGLE_PDF_URL = "https://drive.google.com/viewerng/viewer?embedded=true&url=" 7 | const val EMPTY_STRING = "" 8 | 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/personal/android/androidpdfreader/util/Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.personal.android.androidpdfreader.util 2 | 3 | import android.view.View 4 | 5 | fun View.gone(){ 6 | this.visibility=View.GONE 7 | } 8 | 9 | fun View.visible(){ 10 | this.visibility=View.VISIBLE 11 | } 12 | 13 | fun View.inVisible(){ 14 | this.visibility=View.INVISIBLE 15 | } 16 | 17 | inline fun T.use(block: (T) -> R): R { 18 | var closed = false 19 | try { 20 | return block(this) 21 | } catch (e: Exception) { 22 | closed = true 23 | try { 24 | close() 25 | } catch (closeException: Exception) { 26 | e.addSuppressed(closeException) 27 | } 28 | throw e 29 | } finally { 30 | if (!closed) { 31 | close() 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/personal/android/androidpdfreader/util/FilePathUtil.kt: -------------------------------------------------------------------------------- 1 | package com.personal.android.androidpdfreader.util 2 | 3 | import android.content.ContentUris 4 | import android.content.Context 5 | import android.database.Cursor 6 | import android.net.Uri 7 | import android.os.Build 8 | import android.os.Environment 9 | import android.provider.DocumentsContract 10 | import android.provider.MediaStore 11 | 12 | object FilePathUtil { 13 | /** 14 | * Method for return file path of Gallery image 15 | * 16 | * @param context 17 | * @param uri 18 | * @return path of the selected image file from gallery 19 | */ 20 | fun getPath(context: Context, uri: Uri): String? { 21 | 22 | //check here to KITKAT or new version 23 | val isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT 24 | 25 | // DocumentProvider 26 | if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { 27 | 28 | // ExternalStorageProvider 29 | if (isExternalStorageDocument(uri)) { 30 | val docId = DocumentsContract.getDocumentId(uri) 31 | val split = docId.split(":".toRegex()).toTypedArray() 32 | val type = split[0] 33 | if ("primary".equals(type, ignoreCase = true)) { 34 | return Environment.getExternalStorageDirectory().toString() + "/" + split[1] 35 | } 36 | } else if (isDownloadsDocument(uri)) { 37 | val id = DocumentsContract.getDocumentId(uri) 38 | val contentUri = ContentUris.withAppendedId( 39 | Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id) 40 | ) 41 | return getDataColumn(context, contentUri, null, null) 42 | } else if (isMediaDocument(uri)) { 43 | val docId = DocumentsContract.getDocumentId(uri) 44 | val split = docId.split(":".toRegex()).toTypedArray() 45 | val type = split[0] 46 | var contentUri: Uri? = null 47 | if ("image" == type) { 48 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI 49 | } else if ("video" == type) { 50 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI 51 | } else if ("audio" == type) { 52 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI 53 | } 54 | val selection = "_id=?" 55 | val selectionArgs = arrayOf( 56 | split[1] 57 | ) 58 | return getDataColumn(context, contentUri, selection, selectionArgs) 59 | } 60 | } else if ("content".equals(uri.scheme, ignoreCase = true)) { 61 | 62 | // Return the remote address 63 | return if (isGooglePhotosUri(uri)) uri.lastPathSegment else getDataColumn( 64 | context, 65 | uri, 66 | null, 67 | null 68 | ) 69 | } else if ("file".equals(uri.scheme, ignoreCase = true)) { 70 | return uri.path 71 | } 72 | return null 73 | } 74 | 75 | /** 76 | * Get the value of the data column for this Uri. This is useful for 77 | * MediaStore Uris, and other file-based ContentProviders. 78 | * 79 | * @param context The context. 80 | * @param uri The Uri to query. 81 | * @param selection (Optional) Filter used in the query. 82 | * @param selectionArgs (Optional) Selection arguments used in the query. 83 | * @return The value of the _data column, which is typically a file path. 84 | */ 85 | fun getDataColumn( 86 | context: Context, uri: Uri?, selection: String?, 87 | selectionArgs: Array? 88 | ): String? { 89 | var cursor: Cursor? = null 90 | val column = "_data" 91 | val projection = arrayOf( 92 | column 93 | ) 94 | try { 95 | cursor = context.contentResolver.query( 96 | uri!!, projection, selection, selectionArgs, 97 | null 98 | ) 99 | if (cursor != null && cursor.moveToFirst()) { 100 | val index = cursor.getColumnIndexOrThrow(column) 101 | return cursor.getString(index) 102 | } 103 | } finally { 104 | cursor?.close() 105 | } 106 | return null 107 | } 108 | 109 | /** 110 | * @param uri The Uri to check. 111 | * @return Whether the Uri authority is ExternalStorageProvider. 112 | */ 113 | fun isExternalStorageDocument(uri: Uri): Boolean { 114 | return "com.android.externalstorage.documents" == uri.authority 115 | } 116 | 117 | /** 118 | * @param uri The Uri to check. 119 | * @return Whether the Uri authority is DownloadsProvider. 120 | */ 121 | fun isDownloadsDocument(uri: Uri): Boolean { 122 | return "com.android.providers.downloads.documents" == uri.authority 123 | } 124 | 125 | /** 126 | * @param uri The Uri to check. 127 | * @return Whether the Uri authority is MediaProvider. 128 | */ 129 | fun isMediaDocument(uri: Uri): Boolean { 130 | return "com.android.providers.media.documents" == uri.authority 131 | } 132 | 133 | /** 134 | * @param uri The Uri to check. 135 | * @return Whether the Uri authority is Google Photos. 136 | */ 137 | fun isGooglePhotosUri(uri: Uri): Boolean { 138 | return "com.google.android.apps.photos.content" == uri.authority 139 | } 140 | } -------------------------------------------------------------------------------- /app/src/main/java/com/personal/android/androidpdfreader/util/PdfHelper.kt: -------------------------------------------------------------------------------- 1 | package com.personal.android.androidpdfreader.util 2 | 3 | import android.graphics.pdf.PdfRenderer 4 | import android.os.ParcelFileDescriptor 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.withContext 7 | import java.io.File 8 | 9 | suspend fun renderSinglePage(filePath: String, width: Int) = withContext(Dispatchers.IO) { 10 | PdfRenderer(ParcelFileDescriptor.open(File(filePath), ParcelFileDescriptor.MODE_READ_ONLY)).use { renderer -> 11 | renderer.openPage(0).renderAndClose(width) 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/personal/android/androidpdfreader/util/PdfRendererExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.personal.android.androidpdfreader.util 2 | 3 | import android.graphics.Bitmap 4 | import android.graphics.Canvas 5 | import android.graphics.Color 6 | import android.graphics.pdf.PdfRenderer 7 | 8 | fun PdfRenderer.Page.renderAndClose(width: Int) = use { 9 | val bitmap = createBitmap(width) 10 | render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY) 11 | bitmap 12 | } 13 | 14 | private fun PdfRenderer.Page.createBitmap(bitmapWidth: Int): Bitmap { 15 | val bitmap = Bitmap.createBitmap( 16 | bitmapWidth, (bitmapWidth.toFloat() / width * height).toInt(), Bitmap.Config.ARGB_8888 17 | ) 18 | 19 | val canvas = Canvas(bitmap) 20 | canvas.drawColor(Color.WHITE) 21 | canvas.drawBitmap(bitmap, 0f, 0f, null) 22 | 23 | return bitmap 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/personal/android/androidpdfreader/util/PermissionCommonUtil.kt: -------------------------------------------------------------------------------- 1 | package com.personal.android.androidpdfreader.util 2 | 3 | import android.app.Activity 4 | import android.content.pm.PackageManager 5 | import androidx.core.app.ActivityCompat 6 | import androidx.core.content.ContextCompat 7 | 8 | class PermissionCommonUtil(private val activity: Activity, 9 | private val permissions: Array, 10 | private val readWritePermissionRequestCode: Int, 11 | private val listener: OnPermissionRequestListener) { 12 | 13 | fun isReadWritePermissionAvailable(): Boolean { 14 | var isGranted = true 15 | permissions.forEach { 16 | if (ContextCompat.checkSelfPermission(activity, it) != PackageManager.PERMISSION_GRANTED) { 17 | isGranted = false 18 | return@forEach 19 | } 20 | } 21 | return isGranted 22 | } 23 | 24 | fun requestReadWritePermission() { 25 | if (permissions.all { ActivityCompat.shouldShowRequestPermissionRationale(activity, it) }) { 26 | listener.shouldShowRationalMessage() 27 | ActivityCompat.requestPermissions(activity, permissions, readWritePermissionRequestCode) 28 | } else { 29 | // No explanation needed, we can request the permission. 30 | ActivityCompat.requestPermissions(activity, permissions, readWritePermissionRequestCode) 31 | } 32 | } 33 | 34 | fun onPermissionResult(requestCode: Int, permissions: Array, grantResults: IntArray) { 35 | when (requestCode) { 36 | readWritePermissionRequestCode -> { 37 | // If request is cancelled, the result arrays are empty. 38 | if ((grantResults.isNotEmpty() && grantResults.all { it == PackageManager.PERMISSION_GRANTED })) { 39 | listener.permissionGranted() 40 | } else { 41 | listener.permissionDenied() 42 | } 43 | } 44 | else -> { 45 | // Ignore all other requests. 46 | } 47 | } 48 | } 49 | 50 | interface OnPermissionRequestListener { 51 | fun permissionGranted() 52 | fun permissionDenied() 53 | fun shouldShowRationalMessage() 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/java/com/personal/android/androidpdfreader/util/PinchToZoomRecyclerView.kt: -------------------------------------------------------------------------------- 1 | package com.personal.android.androidpdfreader.util 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.util.AttributeSet 6 | import android.view.GestureDetector 7 | import android.view.MotionEvent 8 | import android.view.ScaleGestureDetector 9 | import android.view.ScaleGestureDetector.SimpleOnScaleGestureListener 10 | import androidx.recyclerview.widget.RecyclerView 11 | 12 | class PinchToZoomRecyclerView : RecyclerView { 13 | private var mActivePointerId = INVALID_POINTER_ID 14 | private var mScaleDetector: ScaleGestureDetector? = null 15 | private var mScaleFactor = 1f 16 | private var maxWidth = 0.0f 17 | private var maxHeight = 0.0f 18 | private var mLastTouchX = 0f 19 | private var mLastTouchY = 0f 20 | private var mPosX = 0f 21 | private var mPosY = 0f 22 | private var width = 0f 23 | private var height = 0f 24 | private var minScale = 1f 25 | private var maxScale = 1.5f 26 | private var gestureDetector: GestureDetector? = null 27 | 28 | constructor(context: Context?) : super(context!!) { 29 | if (!isInEditMode) { 30 | mScaleDetector = ScaleGestureDetector(getContext(), ScaleListener()) 31 | gestureDetector = GestureDetector(getContext(), GestureListener()) 32 | } 33 | } 34 | 35 | constructor(context: Context?, attrs: AttributeSet?) : super(context!!, attrs) { 36 | if (!isInEditMode) { 37 | mScaleDetector = ScaleGestureDetector(getContext(), ScaleListener()) 38 | gestureDetector = GestureDetector(getContext(), GestureListener()) 39 | } 40 | } 41 | 42 | constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context!!, attrs, defStyleAttr) { 43 | if (!isInEditMode) { 44 | mScaleDetector = ScaleGestureDetector(getContext(), ScaleListener()) 45 | gestureDetector = GestureDetector(getContext(), GestureListener()) 46 | } 47 | } 48 | 49 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 50 | width = MeasureSpec.getSize(widthMeasureSpec).toFloat() 51 | height = MeasureSpec.getSize(heightMeasureSpec).toFloat() 52 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 53 | } 54 | 55 | override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { 56 | try { 57 | return super.onInterceptTouchEvent(ev) 58 | } catch (ex: IllegalArgumentException) { 59 | ex.printStackTrace() 60 | } 61 | return false 62 | } 63 | 64 | override fun onTouchEvent(ev: MotionEvent): Boolean { 65 | super.onTouchEvent(ev) 66 | val action = ev.action 67 | mScaleDetector!!.onTouchEvent(ev) 68 | gestureDetector!!.onTouchEvent(ev) 69 | when (action and MotionEvent.ACTION_MASK) { 70 | MotionEvent.ACTION_DOWN -> { 71 | val x = ev.x 72 | val y = ev.y 73 | mLastTouchX = x 74 | mLastTouchY = y 75 | mActivePointerId = ev.getPointerId(0) 76 | } 77 | MotionEvent.ACTION_MOVE -> { 78 | 79 | /* this line is replaced because here came below isssue 80 | java.lang.IllegalArgumentException: pointerIndex out of range 81 | ref http://stackoverflow.com/questions/6919292/pointerindex-out-of-range-android-multitouch 82 | */ 83 | //final int pointerIndex = ev.findPointerIndex(mActivePointerId); 84 | val pointerIndex = (action and MotionEvent.ACTION_POINTER_INDEX_MASK 85 | shr MotionEvent.ACTION_POINTER_INDEX_SHIFT) 86 | val x = ev.getX(pointerIndex) 87 | val y = ev.getY(pointerIndex) 88 | val dx = x - mLastTouchX 89 | val dy = y - mLastTouchY 90 | mPosX += dx 91 | mPosY += dy 92 | if (mPosX > 0.0f) mPosX = 0.0f else if (mPosX < maxWidth) mPosX = maxWidth 93 | if (mPosY > 0.0f) mPosY = 0.0f else if (mPosY < maxHeight) mPosY = maxHeight 94 | mLastTouchX = x 95 | mLastTouchY = y 96 | invalidate() 97 | } 98 | MotionEvent.ACTION_UP -> { 99 | mActivePointerId = INVALID_POINTER_ID 100 | } 101 | MotionEvent.ACTION_CANCEL -> { 102 | mActivePointerId = INVALID_POINTER_ID 103 | } 104 | MotionEvent.ACTION_POINTER_UP -> { 105 | val pointerIndex = action and MotionEvent.ACTION_POINTER_INDEX_MASK shr MotionEvent.ACTION_POINTER_INDEX_SHIFT 106 | val pointerId = ev.getPointerId(pointerIndex) 107 | if (pointerId == mActivePointerId) { 108 | val newPointerIndex = if (pointerIndex == 0) 1 else 0 109 | mLastTouchX = ev.getX(newPointerIndex) 110 | mLastTouchY = ev.getY(newPointerIndex) 111 | mActivePointerId = ev.getPointerId(newPointerIndex) 112 | } 113 | } 114 | } 115 | return true 116 | } 117 | 118 | override fun onDraw(canvas: Canvas) { 119 | super.onDraw(canvas) 120 | canvas.save() 121 | canvas.translate(mPosX, mPosY) 122 | canvas.scale(mScaleFactor, mScaleFactor) 123 | canvas.restore() 124 | } 125 | 126 | override fun dispatchDraw(canvas: Canvas) { 127 | canvas.save() 128 | if (mScaleFactor == 1.0f) { 129 | mPosX = 0.0f 130 | mPosY = 0.0f 131 | } 132 | canvas.translate(mPosX, mPosY) 133 | canvas.scale(mScaleFactor, mScaleFactor) 134 | super.dispatchDraw(canvas) 135 | canvas.restore() 136 | invalidate() 137 | } 138 | 139 | private inner class ScaleListener : SimpleOnScaleGestureListener() { 140 | override fun onScale(detector: ScaleGestureDetector): Boolean { 141 | mScaleFactor *= detector.scaleFactor 142 | mScaleFactor = Math.max(1.0f, Math.min(mScaleFactor, 3.0f)) 143 | maxWidth = width - width * mScaleFactor 144 | maxHeight = height - height * mScaleFactor 145 | invalidate() 146 | return true 147 | } 148 | } 149 | 150 | private inner class GestureListener : GestureDetector.SimpleOnGestureListener() { 151 | override fun onDoubleTap(e: MotionEvent?): Boolean { 152 | mScaleFactor = if (mScaleFactor == maxScale) { 153 | minScale 154 | } else { 155 | maxScale 156 | } 157 | invalidate() 158 | return false 159 | } 160 | } 161 | 162 | companion object { 163 | private const val INVALID_POINTER_ID = -1 164 | } 165 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_pdf.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 23 | 24 | 25 | 26 | 33 | 34 | 39 | 40 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/res/layout/cell_pdf_page.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya09tyagi/AndroidPdfReader/7b4d2fa28b88319d2139da3f7d86a5f69203dc75/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya09tyagi/AndroidPdfReader/7b4d2fa28b88319d2139da3f7d86a5f69203dc75/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya09tyagi/AndroidPdfReader/7b4d2fa28b88319d2139da3f7d86a5f69203dc75/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya09tyagi/AndroidPdfReader/7b4d2fa28b88319d2139da3f7d86a5f69203dc75/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya09tyagi/AndroidPdfReader/7b4d2fa28b88319d2139da3f7d86a5f69203dc75/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya09tyagi/AndroidPdfReader/7b4d2fa28b88319d2139da3f7d86a5f69203dc75/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya09tyagi/AndroidPdfReader/7b4d2fa28b88319d2139da3f7d86a5f69203dc75/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya09tyagi/AndroidPdfReader/7b4d2fa28b88319d2139da3f7d86a5f69203dc75/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya09tyagi/AndroidPdfReader/7b4d2fa28b88319d2139da3f7d86a5f69203dc75/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya09tyagi/AndroidPdfReader/7b4d2fa28b88319d2139da3f7d86a5f69203dc75/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #6200EE 4 | #3700B3 5 | #03DAC5 6 | 7 | #FFFFFF 8 | #000000 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AndroidPdfReader 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/test/java/com/personal/android/androidpdfreader/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.personal.android.androidpdfreader 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | ext.kotlin_version = "1.4.0" 4 | ext.services_version = '4.2.0' 5 | repositories { 6 | google() 7 | jcenter() 8 | maven { url 'https://jitpack.io' } 9 | } 10 | dependencies { 11 | classpath "com.android.tools.build:gradle:4.0.1" 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 | 14 | // NOTE: Do not place your application dependencies here; they belong 15 | // in the individual module build.gradle files 16 | } 17 | } 18 | 19 | allprojects { 20 | repositories { 21 | google() 22 | jcenter() 23 | maven { url 'https://jitpack.io' } 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya09tyagi/AndroidPdfReader/7b4d2fa28b88319d2139da3f7d86a5f69203dc75/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Sep 04 16:51:30 IST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /pdf_reader_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aditya09tyagi/AndroidPdfReader/7b4d2fa28b88319d2139da3f7d86a5f69203dc75/pdf_reader_demo.gif -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | rootProject.name = "AndroidPdfReader" --------------------------------------------------------------------------------