Compare commits

...

51 Commits

Author SHA1 Message Date
Daniel Micay fee1b1516f move away from deprecated package attribute 2022-09-15 19:53:32 -04:00
dependabot[bot] bc77ecbcc6 Bump gradle from 7.2.2 to 7.3.0
Bumps gradle from 7.2.2 to 7.3.0.

---
updated-dependencies:
- dependency-name: com.android.tools.build:gradle
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-15 19:44:05 -04:00
Daniel Micay 955bed3614 increment version to 15 2022-09-12 22:29:25 -04:00
Daniel Micay ee3605428e minor cleanup 2022-09-12 22:27:03 -04:00
Daniel Micay e967af2397 update pdf.js to v2.16.105 2022-09-12 22:23:42 -04:00
Daniel Micay 9ea5cb1802 remove duplicate icon resource 2022-09-12 22:17:44 -04:00
Daniel Micay a307f2892c add missing newlines 2022-09-12 22:16:21 -04:00
Pratyush 8c795d4552 add android 13 monochrome icon 2022-09-12 22:15:32 -04:00
Daniel Micay d7ca71770f remove unused string resource 2022-09-12 22:06:02 -04:00
Daniel Micay a29ec202a7 update appcompat to 1.5.1 2022-09-12 21:21:26 -04:00
Pratyush ea9a57fabe Improve password UI/UX 2022-09-12 02:15:53 -04:00
Pratyush 68fc48d3a9 add on (doc) loaded callback 2022-09-12 02:08:42 -04:00
quh4gko8 3ebdfd0a11 bump targetSdk to 33 2022-09-10 22:44:02 -04:00
quh4gko8 ff11df007f Build against android 13, resolve new deprecated APIs 2022-09-10 22:44:02 -04:00
Daniel Micay fa2db54804 update pdf.js to v2.15.349 2022-08-25 23:08:53 -04:00
Patryk Mis 7634c00f59 Bump dependencies 2022-08-11 14:39:32 -04:00
Daniel Micay f67021e975 update Gradle to 7.5.1 2022-08-06 10:57:35 -04:00
dependabot[bot] adb87cbaef Bump gradle from 7.2.1 to 7.2.2
Bumps gradle from 7.2.1 to 7.2.2.

---
updated-dependencies:
- dependency-name: com.android.tools.build:gradle
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-03 22:15:55 -04:00
Daniel Micay 09ccfb069d update Android build tools to 33.0.0 2022-07-16 18:29:19 -04:00
Daniel Micay 2a91d95eef update Gradle to 7.5 2022-07-16 17:48:38 -04:00
dependabot[bot] a48213c450 Bump kotlin-gradle-plugin from 1.7.0 to 1.7.10
Bumps [kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) from 1.7.0 to 1.7.10.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/commits)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-07 17:39:35 -04:00
dependabot[bot] 2f4c1ebfed Bump kotlin-gradle-plugin from 1.6.21 to 1.7.0
Bumps [kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) from 1.6.21 to 1.7.0.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/commits)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-08 17:12:29 -04:00
Daniel Micay 22b400c68f work around dependabot gradle bug 2022-06-06 06:42:49 -04:00
Daniel Micay 6a6a263b0b update AndroidX appcompat library to 1.4.2 2022-06-04 11:02:25 -04:00
Daniel Micay 8cd685c8c7 remove obsolete backup lint suppression 2022-05-26 20:45:37 -04:00
Daniel Micay be3f43e7a4 ignore vector path length lint for Material icons 2022-05-26 20:44:26 -04:00
Daniel Micay 5eb98841d6 removed unused zoom icons 2022-05-26 20:42:13 -04:00
Daniel Micay e297fdf7e4 ignore false positive lint warning 2022-05-26 20:41:47 -04:00
Daniel Micay 9355f54956 update Material library 2022-05-26 20:39:57 -04:00
dependabot[bot] 5dfdaa2c17 Bump gradle from 7.2.0 to 7.2.1
Bumps gradle from 7.2.0 to 7.2.1.

---
updated-dependencies:
- dependency-name: com.android.tools.build:gradle
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-25 16:39:38 -04:00
June 3411fb0426 Update pdf.js to v2.14.305
Signed-off-by: June <june@eridan.me>
2022-05-14 13:27:14 -04:00
dependabot[bot] af567a5c0c Bump gradle from 7.1.3 to 7.2.0
Bumps gradle from 7.1.3 to 7.2.0.

---
updated-dependencies:
- dependency-name: com.android.tools.build:gradle
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-09 18:04:52 -04:00
Daniel Micay 6a7982ce80 move snackbar to root view 2022-05-06 10:12:30 -04:00
Daniel Micay 1a3e816cfc simplify error string for error while opening PDF 2022-05-06 10:10:18 -04:00
Daniel Micay c6113df31d extend Permissions Policy
These aren't supported by Chromium yet but they're standardized and it
makes sense to explicitly forbid them.
2022-05-06 10:07:59 -04:00
Daniel Micay db98371c9d increment version to 14 2022-05-06 10:07:59 -04:00
Pratyush 87c71ddac2 added support for encrypted PDF
based on https://github.com/GrapheneOS/PdfViewer/pull/17

Signed-off-by: Pratyush <codelab@pratyush.dev>
Co-authored-by: Tommy-Geenexus <tomgapplicationsdevelopment@gmail.com>
Co-authored-by: empratyush <codelab@pratyush.dev>
2022-05-06 10:07:59 -04:00
Daniel Micay 7dab1cdc91 reorder action strings 2022-05-06 10:07:59 -04:00
Daniel Micay 61d204d188 move share action lower in the menu 2022-05-06 10:07:59 -04:00
Daniel Micay 96651d02af remove obsolete zoom in / zoom out menu actions 2022-05-06 10:07:59 -04:00
Daniel Micay 21d7b9d76e fix warning about non-positional format parameters 2022-05-06 10:07:59 -04:00
Pratyush eb6eb8046c added feature to save document 2022-05-06 10:07:50 -04:00
dependabot[bot] c1a1263bd5 Bump kotlin-gradle-plugin from 1.6.20 to 1.6.21
Bumps [kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) from 1.6.20 to 1.6.21.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/commits)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-06 04:36:50 -04:00
smdyv d71f804b5a Replace feature graphic 2022-04-22 15:14:36 -04:00
Pratyush ba9a6c8206 replace deprecated startActivityForResult 2022-04-22 04:24:25 -04:00
Daniel Micay c0eea02bb1 update CI JDK to 18 2022-04-20 06:27:40 -04:00
Patryk Mis eb6c14740f Update Gradle Wrapper 2022-04-14 07:12:13 -04:00
dependabot[bot] 6847883605 Bump actions/setup-java from 2 to 3
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 2 to 3.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-08 22:07:49 -04:00
dependabot[bot] 975dfb966c Bump gradle from 7.1.2 to 7.1.3
Bumps gradle from 7.1.2 to 7.1.3.

---
updated-dependencies:
- dependency-name: com.android.tools.build:gradle
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-07 18:20:58 -04:00
dependabot[bot] ec965ddaaa Bump kotlin-gradle-plugin from 1.6.10 to 1.6.20
Bumps [kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) from 1.6.10 to 1.6.20.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/commits)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-01 17:10:35 -04:00
June 087a01cf72 Update gradle wrapper to 7.4.2
Signed-off-by: June <june@eridan.me>
2022-03-31 18:32:13 -04:00
25 changed files with 462 additions and 122 deletions

View File

@ -10,11 +10,11 @@ jobs:
- uses: actions/checkout@v3
with:
submodules: true
- name: Set up JDK 17
uses: actions/setup-java@v2
- name: Set up JDK 18
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: 17
java-version: 18
cache: gradle
- name: Build with Gradle
run: ./gradlew build --no-daemon

View File

@ -32,14 +32,16 @@ android {
}
}
compileSdk = 32
buildToolsVersion = "32.0.0"
compileSdk = 33
buildToolsVersion = "33.0.0"
namespace = "app.grapheneos.pdfviewer"
defaultConfig {
applicationId = "app.grapheneos.pdfviewer"
minSdk = 26
targetSdk = 32
versionCode = 13
targetSdk = 33
versionCode = 15
versionName = versionCode.toString()
resourceConfigurations.add("en")
}
@ -82,6 +84,6 @@ android {
}
dependencies {
implementation("androidx.appcompat:appcompat:1.4.1")
implementation("com.google.android.material:material:1.5.0")
implementation("androidx.appcompat:appcompat:1.5.1")
implementation("com.google.android.material:material:1.6.1")
}

View File

@ -1,7 +1,8 @@
<lint>
<!-- full backups are desired -->
<issue id="AllowBackup">
<ignore path="src/main/AndroidManifest.xml"/>
<!-- overly aggressive check -->
<issue id="VectorPath">
<ignore path="src/main/res/drawable/ic_rotate_left_24dp.xml"/>
<ignore path="src/main/res/drawable/ic_rotate_right_24dp.xml"/>
</issue>
<!-- Google app indexing doesn't make any sense for this app -->

View File

@ -1,11 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="app.grapheneos.pdfviewer"
android:targetSandboxVersion="2">
<original-package android:name="org.grapheneos.pdfviewer" />
<application android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:roundIcon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:allowBackup="true">

View File

@ -198,15 +198,28 @@ function isTextSelected() {
return window.getSelection().toString() !== "";
}
pdfjsLib.getDocument("https://localhost/placeholder.pdf").promise.then(function(newDoc) {
pdfDoc = newDoc;
channel.setNumPages(pdfDoc.numPages);
pdfDoc.getMetadata().then(function(data) {
channel.setDocumentProperties(JSON.stringify(data.info));
}).catch(function(error) {
console.log("getMetadata error: " + error);
function loadDocument() {
const pdfPassword = channel.getPassword();
const loadingTask = pdfjsLib.getDocument({ url: "https://localhost/placeholder.pdf", password: pdfPassword });
loadingTask.onPassword = (_, error) => {
if (error === pdfjsLib.PasswordResponses.NEED_PASSWORD) {
channel.showPasswordPrompt();
} else if (error === pdfjsLib.PasswordResponses.INCORRECT_PASSWORD) {
channel.invalidPassword();
}
}
loadingTask.promise.then(function (newDoc) {
channel.onLoaded();
pdfDoc = newDoc;
channel.setNumPages(pdfDoc.numPages);
pdfDoc.getMetadata().then(function (data) {
channel.setDocumentProperties(JSON.stringify(data.info));
}).catch(function (error) {
console.log("getMetadata error: " + error);
});
renderPage(channel.getPage(), false, false);
}, function (reason) {
console.error(reason.name + ": " + reason.message);
});
renderPage(channel.getPage(), false, false);
}).catch(function(error) {
console.log("getDocument error: " + error);
});
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -0,0 +1,25 @@
package app.grapheneos.pdfviewer
import android.content.Context
import android.net.Uri
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
@Throws(FileNotFoundException::class, IOException::class)
fun saveAs(context: Context, existingUri: Uri, saveAs: Uri) {
context.asInputStream(existingUri)?.use { inputStream ->
context.asOutputStream(saveAs)?.use { outputStream ->
outputStream.write(inputStream.readBytes())
}
}
}
@Throws(FileNotFoundException::class)
private fun Context.asInputStream(uri: Uri): InputStream? = contentResolver.openInputStream(uri)
@Throws(FileNotFoundException::class)
private fun Context.asOutputStream(uri: Uri): OutputStream? = contentResolver.openOutputStream(uri)

View File

@ -1,12 +1,12 @@
package app.grapheneos.pdfviewer;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
@ -25,12 +25,16 @@ import android.webkit.WebViewClient;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
@ -38,11 +42,14 @@ import com.google.android.material.snackbar.Snackbar;
import app.grapheneos.pdfviewer.databinding.PdfviewerBinding;
import app.grapheneos.pdfviewer.fragment.DocumentPropertiesFragment;
import app.grapheneos.pdfviewer.fragment.PasswordPromptFragment;
import app.grapheneos.pdfviewer.fragment.JumpToPageFragment;
import app.grapheneos.pdfviewer.loader.DocumentPropertiesLoader;
import app.grapheneos.pdfviewer.viewModel.PasswordStatus;
import java.io.IOException;
import java.io.InputStream;
import java.io.FileNotFoundException;
import java.util.HashMap;
import java.util.List;
@ -53,6 +60,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
private static final String STATE_PAGE = "page";
private static final String STATE_ZOOM_RATIO = "zoomRatio";
private static final String STATE_DOCUMENT_ORIENTATION_DEGREES = "documentOrientationDegrees";
private static final String STATE_ENCRYPTED_DOCUMENT_PASSWORD = "encrypted_document_password";
private static final String KEY_PROPERTIES = "properties";
private static final int MIN_WEBVIEW_RELEASE = 89;
@ -78,6 +86,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
"document-domain=(), " +
"encrypted-media=(), " +
"fullscreen=(), " +
"gamepad=(), " +
"geolocation=(), " +
"gyroscope=(), " +
"hid=(), " +
@ -91,6 +100,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
"publickey-credentials-get=(), " +
"screen-wake-lock=(), " +
"serial=(), " +
"speaker-selection=(), " +
"sync-xhr=(), " +
"usb=(), " +
"xr-spatial-tracking=()";
@ -99,7 +109,6 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
private static final float MAX_ZOOM_RATIO = 1.5f;
private static final int ALPHA_LOW = 130;
private static final int ALPHA_HIGH = 255;
private static final int ACTION_OPEN_DOCUMENT_REQUEST_CODE = 1;
private static final int STATE_LOADED = 1;
private static final int STATE_END = 2;
private static final int PADDING = 10;
@ -110,6 +119,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
private float mZoomRatio = 1f;
private int mDocumentOrientationDegrees;
private int mDocumentState;
private String mEncryptedDocumentPassword;
private List<CharSequence> mDocumentProperties;
private InputStream mInputStream;
@ -117,6 +127,36 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
private TextView mTextView;
private Toast mToast;
private Snackbar snackbar;
private PasswordPromptFragment mPasswordPromptFragment;
public PasswordStatus passwordValidationViewModel;
private final ActivityResultLauncher<Intent> openDocumentLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(), result -> {
if (result == null) return;
if (result.getResultCode() != RESULT_OK) return;
Intent resultData = result.getData();
if (resultData != null) {
mUri = result.getData().getData();
mPage = 1;
mDocumentProperties = null;
mEncryptedDocumentPassword = "";
loadPdf();
invalidateOptionsMenu();
}
});
private final ActivityResultLauncher<Intent> saveAsLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(), result -> {
if (result == null) return;
if (result.getResultCode() != RESULT_OK) return;
Intent resultData = result.getData();
if (resultData != null) {
Uri path = resultData.getData();
if (path != null) {
saveDocumentAs(path);
}
}
});
private class Channel {
@JavascriptInterface
@ -150,6 +190,32 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
args.putString(KEY_PROPERTIES, properties);
runOnUiThread(() -> LoaderManager.getInstance(PdfViewer.this).restartLoader(DocumentPropertiesLoader.ID, args, PdfViewer.this));
}
@JavascriptInterface
public void showPasswordPrompt() {
if (!getPasswordPromptFragment().isAdded()){
getPasswordPromptFragment().show(getSupportFragmentManager(), PasswordPromptFragment.class.getName());
}
passwordValidationViewModel.passwordMissing();
}
@JavascriptInterface
public void invalidPassword() {
runOnUiThread(() -> passwordValidationViewModel.invalid());
}
@JavascriptInterface
public void onLoaded() {
passwordValidationViewModel.validated();
if (getPasswordPromptFragment().isAdded()) {
getPasswordPromptFragment().dismiss();
}
}
@JavascriptInterface
public String getPassword() {
return mEncryptedDocumentPassword != null ? mEncryptedDocumentPassword : "";
}
}
@Override
@ -159,6 +225,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
binding = PdfviewerBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
passwordValidationViewModel = new ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication())).get(PasswordStatus.class);
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
@ -214,6 +281,12 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
Log.d(TAG, "path " + path);
if ("/placeholder.pdf".equals(path)) {
maybeCloseInputStream();
try {
mInputStream = getContentResolver().openInputStream(mUri);
} catch (FileNotFoundException ignored) {
snackbar.setText(R.string.error_while_opening).show();
}
return new WebResourceResponse("application/pdf", null, mInputStream);
}
@ -247,6 +320,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
public void onPageFinished(WebView view, String url) {
mDocumentState = STATE_LOADED;
invalidateOptionsMenu();
loadPdfWithPassword(mEncryptedDocumentPassword);
}
});
@ -297,7 +371,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
// loader manager impl so that the result will be delivered.
LoaderManager.getInstance(this);
snackbar = Snackbar.make(binding.webview, "", Snackbar.LENGTH_LONG);
snackbar = Snackbar.make(binding.getRoot(), "", Snackbar.LENGTH_LONG);
final Intent intent = getIntent();
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
@ -310,10 +384,15 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
}
if (savedInstanceState != null) {
mUri = savedInstanceState.getParcelable(STATE_URI);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
mUri = savedInstanceState.getParcelable(STATE_URI);
} else {
mUri = savedInstanceState.getParcelable(STATE_URI, Uri.class);
}
mPage = savedInstanceState.getInt(STATE_PAGE);
mZoomRatio = savedInstanceState.getFloat(STATE_ZOOM_RATIO);
mDocumentOrientationDegrees = savedInstanceState.getInt(STATE_DOCUMENT_ORIENTATION_DEGREES);
mEncryptedDocumentPassword = savedInstanceState.getString(STATE_ENCRYPTED_DOCUMENT_PASSWORD);
}
if (mUri != null) {
@ -326,6 +405,38 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
}
}
@Override
protected void onDestroy() {
super.onDestroy();
binding.webview.removeJavascriptInterface("channel");
binding.getRoot().removeView(binding.webview);
binding.webview.destroy();
maybeCloseInputStream();
}
void maybeCloseInputStream() {
InputStream stream = mInputStream;
if (stream == null) {
return;
}
mInputStream = null;
try {
stream.close();
} catch (IOException ignored) {}
}
private PasswordPromptFragment getPasswordPromptFragment() {
if (mPasswordPromptFragment == null) {
final Fragment fragment = getSupportFragmentManager().findFragmentByTag(PasswordPromptFragment.class.getName());
if (fragment != null) {
mPasswordPromptFragment = (PasswordPromptFragment) fragment;
} else {
mPasswordPromptFragment = new PasswordPromptFragment();
}
}
return mPasswordPromptFragment;
}
@Override
protected void onResume() {
super.onResume();
@ -372,14 +483,21 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
}
mInputStream = getContentResolver().openInputStream(mUri);
} catch (IOException e) {
snackbar.setText(R.string.io_error).show();
snackbar.setText(R.string.error_while_opening).show();
return;
}
mDocumentState = 0;
showSystemUi();
invalidateOptionsMenu();
binding.webview.loadUrl("https://localhost/viewer.html");
}
public void loadPdfWithPassword(final String password) {
mEncryptedDocumentPassword = password;
binding.webview.evaluateJavascript("loadDocument()", null);
}
private void renderPage(final int zoom) {
binding.webview.evaluateJavascript("onRenderPage(" + zoom + ")", null);
}
@ -396,7 +514,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/pdf");
startActivityForResult(intent, ACTION_OPEN_DOCUMENT_REQUEST_CODE);
openDocumentLauncher.launch(intent);
}
private void shareDocument() {
@ -476,21 +594,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
savedInstanceState.putInt(STATE_PAGE, mPage);
savedInstanceState.putFloat(STATE_ZOOM_RATIO, mZoomRatio);
savedInstanceState.putInt(STATE_DOCUMENT_ORIENTATION_DEGREES, mDocumentOrientationDegrees);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent resultData) {
super.onActivityResult(requestCode, resultCode, resultData);
if (requestCode == ACTION_OPEN_DOCUMENT_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
if (resultData != null) {
mUri = resultData.getData();
mPage = 1;
mDocumentProperties = null;
loadPdf();
invalidateOptionsMenu();
}
}
savedInstanceState.putString(STATE_ENCRYPTED_DOCUMENT_PASSWORD, mEncryptedDocumentPassword);
}
private void showPageNumber() {
@ -515,10 +619,10 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
final int[] ids = { R.id.action_zoom_in, R.id.action_zoom_out, R.id.action_jump_to_page,
R.id.action_next, R.id.action_previous, R.id.action_first, R.id.action_last,
R.id.action_rotate_clockwise, R.id.action_rotate_counterclockwise,
R.id.action_view_document_properties, R.id.action_share };
final int[] ids = {R.id.action_jump_to_page, R.id.action_next, R.id.action_previous,
R.id.action_first, R.id.action_last, R.id.action_rotate_clockwise,
R.id.action_rotate_counterclockwise, R.id.action_view_document_properties,
R.id.action_share, R.id.action_save_as};
if (mDocumentState < STATE_LOADED) {
for (final int id : ids) {
final MenuItem item = menu.findItem(id);
@ -537,11 +641,10 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
}
enableDisableMenuItem(menu.findItem(R.id.action_open), getWebViewRelease() >= MIN_WEBVIEW_RELEASE);
enableDisableMenuItem(menu.findItem(R.id.action_zoom_in), mZoomRatio != MAX_ZOOM_RATIO);
enableDisableMenuItem(menu.findItem(R.id.action_zoom_out), mZoomRatio != MIN_ZOOM_RATIO);
enableDisableMenuItem(menu.findItem(R.id.action_share), mUri != null);
enableDisableMenuItem(menu.findItem(R.id.action_next), mPage < mNumPages);
enableDisableMenuItem(menu.findItem(R.id.action_previous), mPage > 1);
enableDisableMenuItem(menu.findItem(R.id.action_save_as), mUri != null);
return true;
}
@ -564,12 +667,6 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
} else if (itemId == R.id.action_open) {
openDocument();
return true;
} else if (itemId == R.id.action_zoom_out) {
zoomOut(0.25f, true);
return true;
} else if (itemId == R.id.action_zoom_in) {
zoomIn(0.25f, true);
return true;
} else if (itemId == R.id.action_rotate_clockwise) {
documentOrientationChanged(90);
return true;
@ -588,8 +685,42 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
} else if (itemId == R.id.action_share) {
shareDocument();
return true;
} else if (itemId == R.id.action_save_as) {
saveDocument();
}
return super.onOptionsItemSelected(item);
}
private void saveDocument() {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/pdf");
intent.putExtra(Intent.EXTRA_TITLE, getCurrentDocumentName());
saveAsLauncher.launch(intent);
}
private String getCurrentDocumentName() {
if (mDocumentProperties == null || mDocumentProperties.isEmpty()) return "";
String fileName = "";
String title = "";
for (CharSequence property : mDocumentProperties) {
if (property.toString().startsWith("File name:")) {
fileName = property.toString().replace("File name:", "");
}
if (property.toString().startsWith("Title:-")) {
title = property.toString().replace("Title:-", "");
}
}
return fileName.length() > 2 ? fileName : title;
}
private void saveDocumentAs(Uri uri) {
try {
KtUtilsKt.saveAs(this, mUri, uri);
} catch (IOException e) {
e.printStackTrace();
snackbar.setText(R.string.error_while_saving).show();
}
}
}

View File

@ -39,14 +39,13 @@ public class Utils {
final Calendar calendar = Calendar.getInstance();
final int currentYear = calendar.get(Calendar.YEAR);
int year;
// Year is required
String field = date.substring(position += 2, 6);
if (!TextUtils.isDigitsOnly(field)) {
throw new ParseException("Invalid year", position);
}
year = Integer.parseInt(field);
int year = Integer.parseInt(field);
if (year > currentYear) {
year = currentYear;
}

View File

@ -0,0 +1,102 @@
package app.grapheneos.pdfviewer.fragment
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.text.Editable
import android.text.TextUtils
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.WindowManager
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import app.grapheneos.pdfviewer.PdfViewer
import app.grapheneos.pdfviewer.R
import app.grapheneos.pdfviewer.databinding.PasswordDialogFragmentBinding
import app.grapheneos.pdfviewer.viewModel.PasswordStatus
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
class PasswordPromptFragment : DialogFragment() {
private lateinit var passwordLayout : TextInputLayout
private lateinit var passwordEditText : TextInputEditText
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val passwordPrompt = MaterialAlertDialogBuilder(requireContext())
val passwordDialogFragmentBinding =
PasswordDialogFragmentBinding.inflate(LayoutInflater.from(requireContext()))
passwordLayout = passwordDialogFragmentBinding.pdfPasswordTextInputLayout
passwordEditText = passwordDialogFragmentBinding.pdfPasswordEditText
passwordPrompt.setView(passwordDialogFragmentBinding.root)
passwordEditText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(input: CharSequence, i: Int, i1: Int, i2: Int) {
updatePositiveButton()
}
override fun afterTextChanged(editable: Editable) {}
})
passwordEditText.setOnEditorActionListener { _, actionId, _ ->
if (actionId != IME_ACTION_DONE) return@setOnEditorActionListener false
sendPassword()
true
}
passwordPrompt.setPositiveButton(R.string.open, null)
passwordPrompt.setNegativeButton(R.string.cancel, null)
val dialog = passwordPrompt.create()
passwordPrompt.setCancelable(false)
isCancelable = false
dialog.setCanceledOnTouchOutside(false)
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
(requireActivity() as PdfViewer).passwordValidationViewModel.status.observe(
this
) {
when (it) {
PasswordStatus.Status.MissingPassword -> {
passwordEditText.editableText.clear()
passwordDialogFragmentBinding.title.setText(R.string.password_prompt_description)
}
PasswordStatus.Status.InvalidPassword -> {
passwordEditText.editableText.clear()
passwordDialogFragmentBinding.pdfPasswordTextInputLayout.error =
"invalid password"
}
PasswordStatus.Status.Validated -> {
//Activity will dismiss the dialog
}
else -> {
throw NullPointerException("status shouldn't be null")
}
}
}
return dialog
}
private fun updatePositiveButton() {
passwordLayout.error = ""
val btn = (dialog as AlertDialog).getButton(DialogInterface.BUTTON_POSITIVE)
btn.isEnabled = passwordEditText.text?.isNotEmpty() ?: false
}
private fun sendPassword() {
val password = passwordEditText.text.toString()
if (!TextUtils.isEmpty(password)) {
(activity as PdfViewer).loadPdfWithPassword(password)
}
}
override fun onStart() {
super.onStart()
updatePositiveButton()
passwordEditText.requestFocus()
}
override fun onResume() {
super.onResume()
(dialog as AlertDialog).getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener {
sendPassword()
}
}
}

View File

@ -0,0 +1,27 @@
package app.grapheneos.pdfviewer.viewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class PasswordStatus : ViewModel() {
enum class Status {
MissingPassword,
InvalidPassword,
Validated
}
val status: MutableLiveData<Status> = MutableLiveData(Status.MissingPassword)
fun passwordMissing() {
status.postValue(Status.MissingPassword)
}
fun invalid() {
status.postValue(Status.InvalidPassword)
}
fun validated() {
status.postValue(Status.Validated)
}
}

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM19,19L5,19L5,5h11.17L19,7.83L19,19zM12,12c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3zM6,6h9v4L6,10z"/>
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M15.5,14h-0.79l-0.28,-0.27c1.2,-1.4 1.82,-3.31 1.48,-5.34 -0.47,-2.78 -2.79,-5 -5.59,-5.34 -4.23,-0.52 -7.78,3.04 -7.27,7.27 0.34,2.8 2.56,5.12 5.34,5.59 2.03,0.34 3.94,-0.28 5.34,-1.48l0.27,0.28v0.79l4.26,4.25c0.41,0.41 1.07,0.41 1.48,0l0.01,-0.01c0.41,-0.41 0.41,-1.07 0,-1.48L15.5,14zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14zM9.5,7c-0.28,0 -0.5,0.22 -0.5,0.5L9,9L7.5,9c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5L9,10v1.5c0,0.28 0.22,0.5 0.5,0.5s0.5,-0.22 0.5,-0.5L10,10h1.5c0.28,0 0.5,-0.22 0.5,-0.5s-0.22,-0.5 -0.5,-0.5L10,9L10,7.5c0,-0.28 -0.22,-0.5 -0.5,-0.5z" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M15.5,14h-0.79l-0.28,-0.27c1.2,-1.4 1.82,-3.31 1.48,-5.34 -0.47,-2.78 -2.79,-5 -5.59,-5.34 -4.23,-0.52 -7.79,3.04 -7.27,7.27 0.34,2.8 2.56,5.12 5.34,5.59 2.03,0.34 3.94,-0.28 5.34,-1.48l0.27,0.28v0.79l4.26,4.25c0.41,0.41 1.07,0.41 1.48,0l0.01,-0.01c0.41,-0.41 0.41,-1.07 0,-1.48L15.5,14zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14zM7.5,9h4c0.28,0 0.5,0.22 0.5,0.5s-0.22,0.5 -0.5,0.5h-4c-0.28,0 -0.5,-0.22 -0.5,-0.5s0.22,-0.5 0.5,-0.5z" />
</vector>

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="20dp"
android:text="@string/password_prompt_description"
android:textColor="?android:attr/colorAccent"
android:textSize="20sp" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/pdf_password_text_input_layout"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:hintEnabled="false"
app:passwordToggleEnabled="true"
app:passwordToggleTint="?android:attr/colorAccent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/pdf_password_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
android:hint="@string/password_prompt_hint"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>

View File

@ -25,24 +25,6 @@
android:title="@string/action_open"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_zoom_out"
android:icon="@drawable/ic_zoom_out_24dp"
android:title="@string/action_zoom_out"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_zoom_in"
android:icon="@drawable/ic_zoom_in_24dp"
android:title="@string/action_zoom_in"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_share"
android:icon="@drawable/ic_share_24dp"
android:title="@string/action_share"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_first"
android:icon="@drawable/ic_first_page_24dp"
@ -73,6 +55,18 @@
android:title="@string/action_rotate_counterclockwise"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_share"
android:icon="@drawable/ic_share_24dp"
android:title="@string/action_share"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_save_as"
android:icon="@drawable/ic_save_24dp"
android:title="@string/action_save_as"
app:showAsAction="never" />
<item
android:id="@+id/action_view_document_properties"
android:title="@string/action_view_document_properties"

View File

@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name">PDF Viewer</string>
<!--<string name="action_settings">Settings</string>-->
<string name="action_previous">Previous page</string>
@ -7,11 +7,11 @@
<string name="action_open">Open document</string>
<string name="action_first">First page</string>
<string name="action_last">Last page</string>
<string name="action_zoom_out">Zoom out</string>
<string name="action_zoom_in">Zoom in</string>
<string name="action_jump_to_page">Jump to page</string>
<string name="action_rotate_clockwise">Rotate clockwise</string>
<string name="action_rotate_counterclockwise">Rotate counterclockwise</string>
<string name="action_jump_to_page">Jump to page</string>
<string name="action_share">Share</string>
<string name="action_save_as">Save as</string>
<string name="action_view_document_properties">Properties</string>
<string name="document_properties_invalid_date">Invalid date</string>
@ -19,9 +19,14 @@
<string name="invalid_mime_type">Cannot open file with invalid MIME type</string>
<string name="legacy_file_uri">Cannot open legacy file paths from insecure apps</string>
<string name="io_error">Received I/O error trying to open content</string>
<string name="error_while_opening">Error encountered trying to open content</string>
<string name="error_while_saving">Error encountered while saving</string>
<string name="webview_out_of_date_title">WebView out-of-date</string>
<string name="webview_out_of_date_message">Your current WebView version is %d. The WebView should be at least version %d for the PDF Viewer to work.</string>
<string name="action_share">Share</string>
<string name="webview_out_of_date_message" tools:ignore="PluralsCandidate">Your current WebView version is %1$d. The WebView should be at least version %2$d for the PDF Viewer to work.</string>
<string name="password_prompt_hint">Password</string>
<string name="password_prompt_description">Enter the password to decrypt this PDF file</string>
<string name="open">Open</string>
<string name="cancel">Cancel</string>
</resources>

View File

@ -1,11 +1,17 @@
buildscript {
repositories {
google()
mavenCentral()
// dependabot cannot handle google()
maven {
url = uri("https://dl.google.com/dl/android/maven2")
}
// dependabot cannot handle mavenCentral()
maven {
url = uri("https://repo.maven.apache.org/maven2")
}
}
dependencies {
classpath("com.android.tools.build:gradle:7.1.2")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10")
classpath("com.android.tools.build:gradle:7.3.0")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10")
}
}

Binary file not shown.

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=e5444a57cda4a95f90b0c9446a9e1b47d3d7f69057765bfb54bd4f482542d548
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip
distributionSha256Sum=f6b8596b10cce501591e92f229816aa4046424f3b24d771751b06779d58c8ec4
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

6
gradlew vendored
View File

@ -205,6 +205,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.

14
gradlew.bat vendored
View File

@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@ -25,7 +25,7 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal

@ -1 +1 @@
Subproject commit 070a365be5a7579f4ddd5a9a2d4efcd281c2d64f
Subproject commit cc3d3bf299ae11f8f72ae8d64cbf19b340f9a996