diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d109a0d..350d32a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 diff --git a/app/src/main/assets/viewer.js b/app/src/main/assets/viewer.js index c5e4ef2..0e04afa 100644 --- a/app/src/main/assets/viewer.js +++ b/app/src/main/assets/viewer.js @@ -198,15 +198,27 @@ 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) { + 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); -}); +} diff --git a/app/src/main/java/app/grapheneos/pdfviewer/GestureHelper.java b/app/src/main/java/app/grapheneos/pdfviewer/GestureHelper.java index 28e643f..2a291cd 100644 --- a/app/src/main/java/app/grapheneos/pdfviewer/GestureHelper.java +++ b/app/src/main/java/app/grapheneos/pdfviewer/GestureHelper.java @@ -2,6 +2,8 @@ package app.grapheneos.pdfviewer; import android.annotation.SuppressLint; import android.content.Context; +import android.view.GestureDetector; +import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.View; @@ -11,6 +13,7 @@ import android.view.View; class GestureHelper { public interface GestureListener { + boolean onTapUp(); // Can be replaced with ratio when supported void onZoomIn(float value); void onZoomOut(float value); @@ -19,6 +22,15 @@ class GestureHelper { @SuppressLint("ClickableViewAccessibility") static void attach(Context context, View gestureView, GestureListener listener) { + + final GestureDetector detector = new GestureDetector(context, + new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onSingleTapUp(MotionEvent motionEvent) { + return listener.onTapUp(); + } + }); + final ScaleGestureDetector scaleDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() { final float SPAN_RATIO = 600; @@ -55,6 +67,7 @@ class GestureHelper { }); gestureView.setOnTouchListener((view, motionEvent) -> { + detector.onTouchEvent(motionEvent); scaleDetector.onTouchEvent(motionEvent); return false; }); diff --git a/app/src/main/java/app/grapheneos/pdfviewer/PdfViewer.java b/app/src/main/java/app/grapheneos/pdfviewer/PdfViewer.java index 06e2e7a..71ec7a9 100644 --- a/app/src/main/java/app/grapheneos/pdfviewer/PdfViewer.java +++ b/app/src/main/java/app/grapheneos/pdfviewer/PdfViewer.java @@ -8,11 +8,11 @@ import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.Gravity; -import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; +import android.view.ViewGroup; import android.webkit.CookieManager; import android.webkit.JavascriptInterface; import android.webkit.WebResourceRequest; @@ -26,14 +26,23 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; 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.loader.app.LoaderManager; import androidx.loader.content.Loader; +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 java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; @@ -67,6 +76,7 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks mDocumentProperties; - private InputStream mInputStream; + private ByteArrayInputStream mInputStream; private PdfviewerBinding binding; private TextView mTextView; @@ -107,6 +119,8 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks LoaderManager.getInstance(PdfViewer.this.activity).restartLoader(DocumentPropertiesLoader.ID, args, PdfViewer.this)); } + + @JavascriptInterface + public void showPasswordPrompt() { + if (getPasswordPromptFragment().isAdded()) { + getPasswordPromptFragment().dismiss(); + } + getPasswordPromptFragment().show(activity.getSupportFragmentManager(), PasswordPromptFragment.class.getName()); + } + + @JavascriptInterface + public void invalidPassword() { + activity.runOnUiThread(PdfViewer.this::notifyInvalidPassword); + showPasswordPrompt(); + } + + @JavascriptInterface + public String getPassword() { + return mEncryptedDocumentPassword != null ? mEncryptedDocumentPassword : ""; + } + } + + private void notifyInvalidPassword() { + snackbar.setText(R.string.password_prompt_invalid_password).show(); } public PdfViewer(@NonNull AppCompatActivity activity) { this.activity = activity; - LayoutInflater inflater = activity.getLayoutInflater(); - binding = PdfviewerBinding.inflate(inflater); + binding = PdfviewerBinding.inflate(activity.getLayoutInflater()); activity.setContentView(binding.getRoot()); + activity.setSupportActionBar(binding.toolbar); + + WindowCompat.setDecorFitsSystemWindows(activity.getWindow(), false); + + // Margins for the toolbar are needed, so that content of the toolbar + // is not covered by a system button navigation bar when in landscape. + ViewCompat.setOnApplyWindowInsetsListener(binding.toolbar, (v, windowInsets) -> { + Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); + ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) v.getLayoutParams(); + mlp.leftMargin = insets.left; + mlp.rightMargin = insets.right; + v.setLayoutParams(mlp); + return windowInsets; + }); binding.webview.setBackgroundColor(Color.TRANSPARENT); @@ -189,6 +239,7 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks { + if (!Boolean.parseBoolean(selection)) { + if ((activity.getWindow().getDecorView().getSystemUiVisibility() & + View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { + hideSystemUi(); + } else { + showSystemUi(); + } + } + }); + return true; + } + @Override public void onZoomIn(float value) { zoomIn(value, false); @@ -248,17 +315,44 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks= Build.VERSION_CODES.O) { - // The user could have left the activity to update the WebView - activity.invalidateOptionsMenu(); if (getWebViewRelease() >= MIN_WEBVIEW_RELEASE) { binding.webviewOutOfDateLayout.setVisibility(View.GONE); binding.webview.setVisibility(View.VISIBLE); @@ -294,14 +388,20 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks 1); @@ -411,12 +532,6 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks + if (actionId != IME_ACTION_DONE) return@setOnEditorActionListener false + sendPassword() + true + } + passwordPrompt.setPositiveButton(R.string.open) { _, _ -> sendPassword() } + passwordPrompt.setNegativeButton(R.string.cancel, null) + val dialog = passwordPrompt.create() + dialog.setCanceledOnTouchOutside(false) + dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE) + return dialog + } + + private fun updatePositiveButton() { + 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)) { + pdfViewer.loadPdfWithPassword(password) + dialog?.dismiss() + } + } + + override fun onStart() { + super.onStart() + updatePositiveButton() + passwordEditText.requestFocus() + } +} diff --git a/app/src/main/res/drawable/ic_insert_drive_file_24dp.xml b/app/src/main/res/drawable/ic_insert_drive_file_24dp.xml deleted file mode 100644 index d35c045..0000000 --- a/app/src/main/res/drawable/ic_insert_drive_file_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_zoom_in_24dp.xml b/app/src/main/res/drawable/ic_zoom_in_24dp.xml deleted file mode 100644 index ab4ab2f..0000000 --- a/app/src/main/res/drawable/ic_zoom_in_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_zoom_out_24dp.xml b/app/src/main/res/drawable/ic_zoom_out_24dp.xml deleted file mode 100644 index aa7acf4..0000000 --- a/app/src/main/res/drawable/ic_zoom_out_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/layout/password_dialog_fragment.xml b/app/src/main/res/layout/password_dialog_fragment.xml new file mode 100644 index 0000000..a2448fd --- /dev/null +++ b/app/src/main/res/layout/password_dialog_fragment.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/pdfviewer.xml b/app/src/main/res/layout/pdfviewer.xml index cdc4fe6..8c01e23 100644 --- a/app/src/main/res/layout/pdfviewer.xml +++ b/app/src/main/res/layout/pdfviewer.xml @@ -4,6 +4,21 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + + + + + - + \ No newline at end of file diff --git a/app/src/main/res/menu/pdf_viewer.xml b/app/src/main/res/menu/pdf_viewer.xml index 7a8cc25..488ecf2 100644 --- a/app/src/main/res/menu/pdf_viewer.xml +++ b/app/src/main/res/menu/pdf_viewer.xml @@ -19,18 +19,6 @@ android:title="@string/action_next" app:showAsAction="ifRoom" /> - - - - + + + + + + +