libpdfviewer: Genesis

This commit is contained in:
Matéo Duparc 2022-02-18 15:03:53 +01:00
parent fdf2cf8c27
commit 4e4b7c5da4
Signed by: hardcoresushi
GPG Key ID: 007F84120107191E
27 changed files with 110 additions and 375 deletions

2
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "pdfjs-dist"] [submodule "pdfjs-dist"]
path = third_party/pdfjs-dist path = app/pdfjs-dist
url = https://github.com/mozilla/pdfjs-dist.git url = https://github.com/mozilla/pdfjs-dist.git

View File

@ -9,7 +9,7 @@ if (useKeystoreProperties) {
} }
plugins { plugins {
id("com.android.application") id("com.android.library")
id("kotlin-android") id("kotlin-android")
} }
@ -29,21 +29,13 @@ android {
buildToolsVersion = "32.0.0" buildToolsVersion = "32.0.0"
defaultConfig { defaultConfig {
applicationId = "org.grapheneos.pdfviewer" minSdk = 21
minSdk = 26
targetSdk = 31 targetSdk = 31
versionCode = 10
versionName = versionCode.toString()
resourceConfigurations.add("en") resourceConfigurations.add("en")
} }
buildTypes { buildTypes {
getByName("debug") {
applicationIdSuffix = ".debug"
}
getByName("release") { getByName("release") {
isShrinkResources = true
isMinifyEnabled = true isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
if (useKeystoreProperties) { if (useKeystoreProperties) {

View File

@ -2,25 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.grapheneos.pdfviewer" package="org.grapheneos.pdfviewer"
android:targetSandboxVersion="2"> android:targetSandboxVersion="2">
<application android:icon="@mipmap/ic_launcher" <application>
android:roundIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:allowBackup="true">
<activity android:name=".PdfViewer"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:mimeType="application/pdf" />
</intent-filter>
</activity>
<meta-data android:name="android.webkit.WebView.MetricsOptOut" <meta-data android:name="android.webkit.WebView.MetricsOptOut"
android:value="true" /> android:value="true" />
<meta-data android:name="android.webkit.WebView.EnableSafeBrowsing" <meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"

View File

@ -1 +1 @@
../../../../third_party/pdfjs-dist/build/pdf.min.js ../../../pdfjs-dist/build/pdf.min.js

View File

@ -1 +1 @@
../../../../third_party/pdfjs-dist/build/pdf.worker.min.js ../../../pdfjs-dist/build/pdf.worker.min.js

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@ -2,8 +2,6 @@ package org.grapheneos.pdfviewer;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector; import android.view.ScaleGestureDetector;
import android.view.View; import android.view.View;
@ -13,7 +11,6 @@ import android.view.View;
class GestureHelper { class GestureHelper {
public interface GestureListener { public interface GestureListener {
boolean onTapUp();
// Can be replaced with ratio when supported // Can be replaced with ratio when supported
void onZoomIn(float value); void onZoomIn(float value);
void onZoomOut(float value); void onZoomOut(float value);
@ -22,15 +19,6 @@ class GestureHelper {
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
static void attach(Context context, View gestureView, GestureListener listener) { 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, final ScaleGestureDetector scaleDetector = new ScaleGestureDetector(context,
new ScaleGestureDetector.SimpleOnScaleGestureListener() { new ScaleGestureDetector.SimpleOnScaleGestureListener() {
final float SPAN_RATIO = 600; final float SPAN_RATIO = 600;
@ -67,7 +55,6 @@ class GestureHelper {
}); });
gestureView.setOnTouchListener((view, motionEvent) -> { gestureView.setOnTouchListener((view, motionEvent) -> {
detector.onTouchEvent(motionEvent);
scaleDetector.onTouchEvent(motionEvent); scaleDetector.onTouchEvent(motionEvent);
return false; return false;
}); });

View File

@ -1,18 +1,16 @@
package org.grapheneos.pdfviewer; package org.grapheneos.pdfviewer;
import android.annotation.SuppressLint; import android.content.Context;
import android.app.Activity;
import android.content.Intent;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
import android.graphics.Color; import android.graphics.Color;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.Gravity; import android.view.Gravity;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View;
import android.webkit.CookieManager; import android.webkit.CookieManager;
import android.webkit.JavascriptInterface; import android.webkit.JavascriptInterface;
import android.webkit.WebResourceRequest; import android.webkit.WebResourceRequest;
@ -24,13 +22,11 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.loader.app.LoaderManager; import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader; import androidx.loader.content.Loader;
import com.google.android.material.snackbar.Snackbar;
import org.grapheneos.pdfviewer.databinding.WebviewBinding;
import org.grapheneos.pdfviewer.fragment.DocumentPropertiesFragment; import org.grapheneos.pdfviewer.fragment.DocumentPropertiesFragment;
import org.grapheneos.pdfviewer.fragment.JumpToPageFragment; import org.grapheneos.pdfviewer.fragment.JumpToPageFragment;
import org.grapheneos.pdfviewer.loader.DocumentPropertiesLoader; import org.grapheneos.pdfviewer.loader.DocumentPropertiesLoader;
@ -40,13 +36,9 @@ import java.io.InputStream;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
public class PdfViewer extends AppCompatActivity implements LoaderManager.LoaderCallbacks<List<CharSequence>> { public class PdfViewer extends WebView implements LoaderManager.LoaderCallbacks<List<CharSequence>> {
public static final String TAG = "PdfViewer"; public static final String TAG = "PdfViewer";
private static final String STATE_URI = "uri";
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 KEY_PROPERTIES = "properties"; private static final String KEY_PROPERTIES = "properties";
private static final String CONTENT_SECURITY_POLICY = private static final String CONTENT_SECURITY_POLICY =
@ -92,12 +84,10 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
private static final float MAX_ZOOM_RATIO = 1.5f; private static final float MAX_ZOOM_RATIO = 1.5f;
private static final int ALPHA_LOW = 130; private static final int ALPHA_LOW = 130;
private static final int ALPHA_HIGH = 255; 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_LOADED = 1;
private static final int STATE_END = 2; private static final int STATE_END = 2;
private static final int PADDING = 10; private static final int PADDING = 10;
private Uri mUri;
public int mPage; public int mPage;
public int mNumPages; public int mNumPages;
private float mZoomRatio = 1f; private float mZoomRatio = 1f;
@ -107,70 +97,27 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
private List<CharSequence> mDocumentProperties; private List<CharSequence> mDocumentProperties;
private InputStream mInputStream; private InputStream mInputStream;
private WebviewBinding binding;
private TextView mTextView; private TextView mTextView;
private Toast mToast; private Toast mToast;
private Snackbar snackbar;
private class Channel { public AppCompatActivity activity;
@JavascriptInterface String fileName;
public int getWindowInsetTop() { Long fileSize;
return windowInsetTop;
}
@JavascriptInterface void init(Context context) {
public int getPage() { setBackgroundColor(Color.TRANSPARENT);
return mPage;
}
@JavascriptInterface
public float getZoomRatio() {
return mZoomRatio;
}
@JavascriptInterface
public int getDocumentOrientationDegrees() {
return mDocumentOrientationDegrees;
}
@JavascriptInterface
public void setNumPages(int numPages) {
mNumPages = numPages;
runOnUiThread(PdfViewer.this::invalidateOptionsMenu);
}
@JavascriptInterface
public void setDocumentProperties(final String properties) {
if (mDocumentProperties != null) {
throw new SecurityException("mDocumentProperties not null");
}
final Bundle args = new Bundle();
args.putString(KEY_PROPERTIES, properties);
runOnUiThread(() -> LoaderManager.getInstance(PdfViewer.this).restartLoader(DocumentPropertiesLoader.ID, args, PdfViewer.this));
}
}
@Override
@SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"})
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = WebviewBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.webview.setBackgroundColor(Color.TRANSPARENT);
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
WebView.setWebContentsDebuggingEnabled(true); WebView.setWebContentsDebuggingEnabled(true);
} }
binding.webview.setOnApplyWindowInsetsListener((view, insets) -> { setOnApplyWindowInsetsListener((view, insets) -> {
windowInsetTop = insets.getSystemWindowInsetTop(); windowInsetTop = insets.getSystemWindowInsetTop();
binding.webview.evaluateJavascript("updateInset()", null); evaluateJavascript("updateInset()", null);
return insets; return insets;
}); });
final WebSettings settings = binding.webview.getSettings(); final WebSettings settings = getSettings();
settings.setAllowContentAccess(false); settings.setAllowContentAccess(false);
settings.setAllowFileAccess(false); settings.setAllowFileAccess(false);
settings.setCacheMode(WebSettings.LOAD_NO_CACHE); settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
@ -178,12 +125,12 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
CookieManager.getInstance().setAcceptCookie(false); CookieManager.getInstance().setAcceptCookie(false);
binding.webview.addJavascriptInterface(new Channel(), "channel"); addJavascriptInterface(new Channel(), "channel");
binding.webview.setWebViewClient(new WebViewClient() { setWebViewClient(new WebViewClient() {
private WebResourceResponse fromAsset(final String mime, final String path) { private WebResourceResponse fromAsset(final String mime, final String path) {
try { try {
InputStream inputStream = getAssets().open(path.substring(1)); InputStream inputStream = context.getAssets().open(path.substring(1));
return new WebResourceResponse(mime, null, inputStream); return new WebResourceResponse(mime, null, inputStream);
} catch (IOException e) { } catch (IOException e) {
return null; return null;
@ -237,30 +184,12 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
@Override @Override
public void onPageFinished(WebView view, String url) { public void onPageFinished(WebView view, String url) {
mDocumentState = STATE_LOADED; mDocumentState = STATE_LOADED;
invalidateOptionsMenu(); activity.invalidateOptionsMenu();
} }
}); });
GestureHelper.attach(PdfViewer.this, binding.webview, GestureHelper.attach(context, this,
new GestureHelper.GestureListener() { new GestureHelper.GestureListener() {
@Override
public boolean onTapUp() {
if (mUri != null) {
binding.webview.evaluateJavascript("isTextSelected()", selection -> {
if (!Boolean.parseBoolean(selection)) {
if ((getWindow().getDecorView().getSystemUiVisibility() &
View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
hideSystemUi();
} else {
showSystemUi();
}
}
});
return true;
}
return false;
}
@Override @Override
public void onZoomIn(float value) { public void onZoomIn(float value) {
zoomIn(value, false); zoomIn(value, false);
@ -277,56 +206,82 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
} }
}); });
mTextView = new TextView(this); mTextView = new TextView(context);
mTextView.setBackgroundColor(Color.DKGRAY); mTextView.setBackgroundColor(Color.DKGRAY);
mTextView.setTextColor(ColorStateList.valueOf(Color.WHITE)); mTextView.setTextColor(ColorStateList.valueOf(Color.WHITE));
mTextView.setTextSize(18); mTextView.setTextSize(18);
mTextView.setPadding(PADDING, 0, PADDING, 0); mTextView.setPadding(PADDING, 0, PADDING, 0);
}
// If loaders are not being initialized in onCreate(), the result will not be delivered public PdfViewer(@NonNull Context context) {
// after orientation change (See FragmentHostCallback), thus initialize the super(context);
// loader manager impl so that the result will be delivered. init(context);
LoaderManager.getInstance(this); }
snackbar = Snackbar.make(binding.webview, "", Snackbar.LENGTH_LONG); public PdfViewer(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
final Intent intent = getIntent(); public PdfViewer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
if (Intent.ACTION_VIEW.equals(intent.getAction())) { super(context, attrs, defStyleAttr);
if (!"application/pdf".equals(intent.getType())) { init(context);
snackbar.setText(R.string.invalid_mime_type).show(); }
return;
} public void onCreateOptionMenu(Menu menu) {
mUri = intent.getData(); MenuInflater inflater = activity.getMenuInflater();
mPage = 1; inflater.inflate(R.menu.pdf_viewer, menu);
}
private class Channel {
@JavascriptInterface
public int getWindowInsetTop() {
return windowInsetTop;
} }
if (savedInstanceState != null) { @JavascriptInterface
mUri = savedInstanceState.getParcelable(STATE_URI); public int getPage() {
mPage = savedInstanceState.getInt(STATE_PAGE); return mPage;
mZoomRatio = savedInstanceState.getFloat(STATE_ZOOM_RATIO);
mDocumentOrientationDegrees = savedInstanceState.getInt(STATE_DOCUMENT_ORIENTATION_DEGREES);
} }
if (mUri != null) { @JavascriptInterface
if ("file".equals(mUri.getScheme())) { public float getZoomRatio() {
snackbar.setText(R.string.legacy_file_uri).show(); return mZoomRatio;
return; }
@JavascriptInterface
public int getDocumentOrientationDegrees() {
return mDocumentOrientationDegrees;
}
@JavascriptInterface
public void setNumPages(int numPages) {
mNumPages = numPages;
activity.runOnUiThread(activity::invalidateOptionsMenu);
}
@JavascriptInterface
public void setDocumentProperties(final String properties) {
if (mDocumentProperties != null) {
throw new SecurityException("mDocumentProperties not null");
} }
loadPdf(); final Bundle args = new Bundle();
args.putString(KEY_PROPERTIES, properties);
activity.runOnUiThread(() -> LoaderManager.getInstance(PdfViewer.this.activity).restartLoader(DocumentPropertiesLoader.ID, args, PdfViewer.this));
} }
} }
@NonNull @NonNull
@Override @Override
public Loader<List<CharSequence>> onCreateLoader(int id, Bundle args) { public Loader<List<CharSequence>> onCreateLoader(int id, Bundle args) {
return new DocumentPropertiesLoader(this, args.getString(KEY_PROPERTIES), mNumPages, mUri); return new DocumentPropertiesLoader(activity, args.getString(KEY_PROPERTIES), mNumPages, fileName, fileSize);
} }
@Override @Override
public void onLoadFinished(@NonNull Loader<List<CharSequence>> loader, List<CharSequence> data) { public void onLoadFinished(@NonNull Loader<List<CharSequence>> loader, List<CharSequence> data) {
mDocumentProperties = data; mDocumentProperties = data;
LoaderManager.getInstance(this).destroyLoader(DocumentPropertiesLoader.ID); LoaderManager.getInstance(activity).destroyLoader(DocumentPropertiesLoader.ID);
} }
@Override @Override
@ -334,23 +289,18 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
mDocumentProperties = null; mDocumentProperties = null;
} }
private void loadPdf() { public void loadPdf(InputStream inputStream, String fileName, Long fileSize) {
try { mPage = 1;
if (mInputStream != null) { mDocumentProperties = null;
mInputStream.close(); mInputStream = inputStream;
} this.fileName = fileName;
mInputStream = getContentResolver().openInputStream(mUri); this.fileSize = fileSize;
} catch (IOException e) { loadUrl("https://localhost/viewer.html");
snackbar.setText(R.string.io_error).show(); activity.invalidateOptionsMenu();
return;
}
showSystemUi();
binding.webview.loadUrl("https://localhost/viewer.html");
} }
private void renderPage(final int zoom) { private void renderPage(final int zoom) {
binding.webview.evaluateJavascript("onRenderPage(" + zoom + ")", null); evaluateJavascript("onRenderPage(" + zoom + ")", null);
} }
private void documentOrientationChanged(final int orientationDegreesOffset) { private void documentOrientationChanged(final int orientationDegreesOffset) {
@ -361,18 +311,11 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
renderPage(0); renderPage(0);
} }
private void openDocument() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/pdf");
startActivityForResult(intent, ACTION_OPEN_DOCUMENT_REQUEST_CODE);
}
private void zoomIn(float value, boolean end) { private void zoomIn(float value, boolean end) {
if (mZoomRatio < MAX_ZOOM_RATIO) { if (mZoomRatio < MAX_ZOOM_RATIO) {
mZoomRatio = Math.min(mZoomRatio + value, MAX_ZOOM_RATIO); mZoomRatio = Math.min(mZoomRatio + value, MAX_ZOOM_RATIO);
renderPage(end ? 1 : 2); renderPage(end ? 1 : 2);
invalidateOptionsMenu(); activity.invalidateOptionsMenu();
} }
} }
@ -380,7 +323,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
if (mZoomRatio > MIN_ZOOM_RATIO) { if (mZoomRatio > MIN_ZOOM_RATIO) {
mZoomRatio = Math.max(mZoomRatio - value, MIN_ZOOM_RATIO); mZoomRatio = Math.max(mZoomRatio - value, MIN_ZOOM_RATIO);
renderPage(end ? 1 : 2); renderPage(end ? 1 : 2);
invalidateOptionsMenu(); activity.invalidateOptionsMenu();
} }
} }
@ -403,48 +346,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
mPage = selected_page; mPage = selected_page;
renderPage(0); renderPage(0);
showPageNumber(); showPageNumber();
invalidateOptionsMenu(); activity.invalidateOptionsMenu();
}
}
private void showSystemUi() {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
private void hideSystemUi() {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_FULLSCREEN |
View.SYSTEM_UI_FLAG_IMMERSIVE);
}
@Override
public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putParcelable(STATE_URI, mUri);
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();
}
} }
} }
@ -453,22 +355,13 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
mToast.cancel(); mToast.cancel();
} }
mTextView.setText(String.format("%s/%s", mPage, mNumPages)); mTextView.setText(String.format("%s/%s", mPage, mNumPages));
mToast = new Toast(getApplicationContext()); mToast = new Toast(activity);
mToast.setGravity(Gravity.BOTTOM | Gravity.END, PADDING, PADDING); mToast.setGravity(Gravity.BOTTOM | Gravity.END, PADDING, PADDING);
mToast.setDuration(Toast.LENGTH_SHORT); mToast.setDuration(Toast.LENGTH_SHORT);
mToast.setView(mTextView); mToast.setView(mTextView);
mToast.show(); mToast.show();
} }
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.pdf_viewer, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) { public boolean onPrepareOptionsMenu(Menu menu) {
final int[] ids = { R.id.action_zoom_in, R.id.action_zoom_out, R.id.action_jump_to_page, 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_next, R.id.action_previous, R.id.action_first, R.id.action_last,
@ -499,7 +392,6 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
return true; return true;
} }
@Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
final int itemId = item.getItemId(); final int itemId = item.getItemId();
if (itemId == R.id.action_previous) { if (itemId == R.id.action_previous) {
@ -514,9 +406,6 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
} else if (itemId == R.id.action_last) { } else if (itemId == R.id.action_last) {
onJumpToPageInDocument(mNumPages); onJumpToPageInDocument(mNumPages);
return true; return true;
} else if (itemId == R.id.action_open) {
openDocument();
return true;
} else if (itemId == R.id.action_zoom_out) { } else if (itemId == R.id.action_zoom_out) {
zoomOut(0.25f, true); zoomOut(0.25f, true);
return true; return true;
@ -532,14 +421,13 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
} else if (itemId == R.id.action_view_document_properties) { } else if (itemId == R.id.action_view_document_properties) {
DocumentPropertiesFragment DocumentPropertiesFragment
.newInstance(mDocumentProperties) .newInstance(mDocumentProperties)
.show(getSupportFragmentManager(), DocumentPropertiesFragment.TAG); .show(activity.getSupportFragmentManager(), DocumentPropertiesFragment.TAG);
return true; return true;
} else if (itemId == R.id.action_jump_to_page) { } else if (itemId == R.id.action_jump_to_page) {
new JumpToPageFragment() JumpToPageFragment.newInstance(this)
.show(getSupportFragmentManager(), JumpToPageFragment.TAG); .show(activity.getSupportFragmentManager(), JumpToPageFragment.TAG);
return true; return true;
} }
return false;
return super.onOptionsItemSelected(item);
} }
} }

View File

@ -20,15 +20,12 @@ public class JumpToPageFragment extends DialogFragment {
private final static String STATE_PICKER_MAX = "picker_max"; private final static String STATE_PICKER_MAX = "picker_max";
private NumberPicker mPicker; private NumberPicker mPicker;
PdfViewer pdfViewer;
@Override public static JumpToPageFragment newInstance(PdfViewer pdfViewer) {
public void onActivityCreated(Bundle savedInstanceState) { JumpToPageFragment f = new JumpToPageFragment();
super.onActivityCreated(savedInstanceState); f.pdfViewer = pdfViewer;
if (savedInstanceState != null) { return f;
mPicker.setMinValue(savedInstanceState.getInt(STATE_PICKER_MIN));
mPicker.setMaxValue(savedInstanceState.getInt(STATE_PICKER_MAX));
mPicker.setValue(savedInstanceState.getInt(STATE_PICKER_CUR));
}
} }
@NonNull @NonNull
@ -36,8 +33,8 @@ public class JumpToPageFragment extends DialogFragment {
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
mPicker = new NumberPicker(getActivity()); mPicker = new NumberPicker(getActivity());
mPicker.setMinValue(1); mPicker.setMinValue(1);
mPicker.setMaxValue(((PdfViewer)requireActivity()).mNumPages); mPicker.setMaxValue(pdfViewer.mNumPages);
mPicker.setValue(((PdfViewer)requireActivity()).mPage); mPicker.setValue(pdfViewer.mPage);
final FrameLayout layout = new FrameLayout(getActivity()); final FrameLayout layout = new FrameLayout(getActivity());
layout.addView(mPicker, new FrameLayout.LayoutParams( layout.addView(mPicker, new FrameLayout.LayoutParams(
@ -49,7 +46,7 @@ public class JumpToPageFragment extends DialogFragment {
.setView(layout) .setView(layout)
.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> { .setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {
mPicker.clearFocus(); mPicker.clearFocus();
((PdfViewer)requireActivity()).onJumpToPageInDocument(mPicker.getValue()); pdfViewer.onJumpToPageInDocument(mPicker.getValue());
}) })
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.create(); .create();

View File

@ -1,10 +1,7 @@
package org.grapheneos.pdfviewer.loader; package org.grapheneos.pdfviewer.loader;
import android.content.Context; import android.content.Context;
import android.database.Cursor;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.net.Uri;
import android.provider.OpenableColumns;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.Spanned; import android.text.Spanned;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
@ -12,6 +9,8 @@ import android.util.Log;
import androidx.loader.content.AsyncTaskLoader; import androidx.loader.content.AsyncTaskLoader;
import org.grapheneos.pdfviewer.R;
import org.grapheneos.pdfviewer.Utils;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -19,9 +18,6 @@ import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.grapheneos.pdfviewer.R;
import org.grapheneos.pdfviewer.Utils;
public class DocumentPropertiesLoader extends AsyncTaskLoader<List<CharSequence>> { public class DocumentPropertiesLoader extends AsyncTaskLoader<List<CharSequence>> {
public static final String TAG = "DocumentPropertiesLoader"; public static final String TAG = "DocumentPropertiesLoader";
@ -29,16 +25,17 @@ public class DocumentPropertiesLoader extends AsyncTaskLoader<List<CharSequence>
private final String mProperties; private final String mProperties;
private final int mNumPages; private final int mNumPages;
private final Uri mUri;
private Cursor mCursor; String fileName;
Long fileSize;
public DocumentPropertiesLoader(Context context, String properties, int numPages, Uri uri) { public DocumentPropertiesLoader(Context context, String properties, int numPages, String fileName, Long fileSize) {
super(context); super(context);
mProperties = properties; mProperties = properties;
mNumPages = numPages; mNumPages = numPages;
mUri = uri; this.fileName = fileName;
this.fileSize = fileSize;
} }
@Override @Override
@ -48,23 +45,8 @@ public class DocumentPropertiesLoader extends AsyncTaskLoader<List<CharSequence>
final String[] names = context.getResources().getStringArray(R.array.property_names); final String[] names = context.getResources().getStringArray(R.array.property_names);
final List<CharSequence> properties = new ArrayList<>(names.length); final List<CharSequence> properties = new ArrayList<>(names.length);
mCursor = context.getContentResolver().query(mUri, null, null, null, null); properties.add(getProperty(null, names[0], fileName));
if (mCursor != null) { properties.add(getProperty(null, names[1], Utils.parseFileSize(fileSize)));
mCursor.moveToFirst();
final int indexName = mCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
if (indexName >= 0) {
properties.add(getProperty(null, names[0], mCursor.getString(indexName)));
}
final int indexSize = mCursor.getColumnIndex(OpenableColumns.SIZE);
if (indexSize >= 0) {
final long fileSize = Long.parseLong(mCursor.getString(indexSize));
properties.add(getProperty(null, names[1], Utils.parseFileSize(fileSize)));
}
mCursor.close();
}
try { try {
final JSONObject json = new JSONObject(mProperties); final JSONObject json = new JSONObject(mProperties);
@ -89,9 +71,7 @@ public class DocumentPropertiesLoader extends AsyncTaskLoader<List<CharSequence>
@Override @Override
public void deliverResult(List<CharSequence> properties) { public void deliverResult(List<CharSequence> properties) {
if (isReset()) { if (!isReset() && isStarted()) {
onReleaseResources();
} else if (isStarted()) {
super.deliverResult(properties); super.deliverResult(properties);
} }
} }
@ -106,26 +86,11 @@ public class DocumentPropertiesLoader extends AsyncTaskLoader<List<CharSequence>
cancelLoad(); cancelLoad();
} }
@Override
public void onCanceled(List<CharSequence> properties) {
super.onCanceled(properties);
onReleaseResources();
}
@Override @Override
protected void onReset() { protected void onReset() {
super.onReset(); super.onReset();
onStopLoading(); onStopLoading();
onReleaseResources();
}
private void onReleaseResources() {
if (mCursor != null) {
mCursor.close();
mCursor = null;
}
} }
private CharSequence getProperty(final JSONObject json, String name, String specName) { private CharSequence getProperty(final JSONObject json, String name, String specName) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 372 B

View File

@ -1,30 +0,0 @@
<!--
Copyright (C) 2021 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group
android:scaleX="2.43375"
android:scaleY="2.43375"
android:translateX="24.795"
android:translateY="24.795">
<path
android:fillColor="#000000"
android:pathData="M18,4L6,4C4.9,4 4,4.9 4,6v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,6C20,4.9 19.1,4 18,4ZM9.5,11.5C9.5,12.33 8.83,13 8,13L7,13v1.25C7,14.66 6.66,15 6.25,15 5.84,15 5.5,14.66 5.5,14.25L5.5,10c0,-0.55 0.45,-1 1,-1L8,9c0.83,0 1.5,0.67 1.5,1.5zM14.5,13.5c0,0.83 -0.67,1.5 -1.5,1.5h-2c-0.28,0 -0.5,-0.22 -0.5,-0.5v-5C10.5,9.22 10.72,9 11,9h2c0.83,0 1.5,0.67 1.5,1.5zM18.5,9.75c0,0.41 -0.34,0.75 -0.75,0.75L17,10.5v1h0.75c0.41,0 0.75,0.34 0.75,0.75 0,0.41 -0.34,0.75 -0.75,0.75L17,13v1.25C17,14.66 16.66,15 16.25,15 15.84,15 15.5,14.66 15.5,14.25L15.5,10c0,-0.55 0.45,-1 1,-1h1.25c0.41,0 0.75,0.34 0.75,0.75zM7,11.5h1v-1L7,10.5ZM12,13.5h1v-3h-1z" />
</group>
</vector>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/webview"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />

View File

@ -19,12 +19,6 @@
android:title="@string/action_next" android:title="@string/action_next"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item
android:id="@+id/action_open"
android:icon="@drawable/ic_insert_drive_file_white_24dp"
android:title="@string/action_open"
app:showAsAction="ifRoom" />
<item <item
android:id="@+id/action_zoom_out" android:id="@+id/action_zoom_out"
android:icon="@drawable/ic_zoom_out_white_24dp" android:icon="@drawable/ic_zoom_out_white_24dp"

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 +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,3 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:shrinkMode="strict" />

View File

@ -1,9 +0,0 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Customize your theme here. -->
<item name="android:windowBackground">#000000</item>
</style>
</resources>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View File

@ -4,7 +4,6 @@
<!--<string name="action_settings">Settings</string>--> <!--<string name="action_settings">Settings</string>-->
<string name="action_previous">Previous page</string> <string name="action_previous">Previous page</string>
<string name="action_next">Next page</string> <string name="action_next">Next page</string>
<string name="action_open">Open document</string>
<string name="action_first">First page</string> <string name="action_first">First page</string>
<string name="action_last">Last page</string> <string name="action_last">Last page</string>
<string name="action_zoom_out">Zoom out</string> <string name="action_zoom_out">Zoom out</string>

View File

@ -1,8 +0,0 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Customize your theme here. -->
</style>
</resources>