2022-02-25 22:26:22 +01:00
|
|
|
package app.grapheneos.pdfviewer;
|
2019-06-28 05:22:08 +02:00
|
|
|
|
|
|
|
import android.annotation.SuppressLint;
|
|
|
|
import android.content.Intent;
|
2022-02-25 13:52:20 +01:00
|
|
|
import android.content.pm.PackageInfo;
|
2019-06-28 05:22:08 +02:00
|
|
|
import android.content.res.ColorStateList;
|
|
|
|
import android.graphics.Color;
|
|
|
|
import android.net.Uri;
|
|
|
|
import android.os.Bundle;
|
|
|
|
import android.util.Log;
|
|
|
|
import android.view.Gravity;
|
|
|
|
import android.view.Menu;
|
|
|
|
import android.view.MenuInflater;
|
|
|
|
import android.view.MenuItem;
|
2019-08-17 01:42:51 +02:00
|
|
|
import android.view.View;
|
2022-02-20 17:29:02 +01:00
|
|
|
import android.view.ViewGroup;
|
2019-06-28 05:22:08 +02:00
|
|
|
import android.webkit.CookieManager;
|
|
|
|
import android.webkit.JavascriptInterface;
|
|
|
|
import android.webkit.WebResourceRequest;
|
|
|
|
import android.webkit.WebResourceResponse;
|
|
|
|
import android.webkit.WebSettings;
|
|
|
|
import android.webkit.WebView;
|
|
|
|
import android.webkit.WebViewClient;
|
|
|
|
import android.widget.TextView;
|
|
|
|
import android.widget.Toast;
|
|
|
|
|
2022-04-22 06:22:19 +02:00
|
|
|
import androidx.activity.result.ActivityResultLauncher;
|
|
|
|
import androidx.activity.result.contract.ActivityResultContracts;
|
2022-02-06 04:25:11 +01:00
|
|
|
import androidx.annotation.NonNull;
|
2019-06-28 05:22:08 +02:00
|
|
|
import androidx.appcompat.app.AppCompatActivity;
|
2022-02-20 17:29:02 +01:00
|
|
|
import androidx.core.graphics.Insets;
|
|
|
|
import androidx.core.view.ViewCompat;
|
2022-02-20 21:21:25 +01:00
|
|
|
import androidx.core.view.WindowCompat;
|
2022-02-20 17:29:02 +01:00
|
|
|
import androidx.core.view.WindowInsetsCompat;
|
2019-06-28 05:22:08 +02:00
|
|
|
import androidx.loader.app.LoaderManager;
|
|
|
|
import androidx.loader.content.Loader;
|
|
|
|
|
2020-04-11 19:21:36 +02:00
|
|
|
import com.google.android.material.snackbar.Snackbar;
|
|
|
|
|
2022-02-25 22:26:22 +01:00
|
|
|
import app.grapheneos.pdfviewer.databinding.PdfviewerBinding;
|
|
|
|
import app.grapheneos.pdfviewer.fragment.DocumentPropertiesFragment;
|
|
|
|
import app.grapheneos.pdfviewer.fragment.JumpToPageFragment;
|
|
|
|
import app.grapheneos.pdfviewer.loader.DocumentPropertiesLoader;
|
2020-08-25 06:58:59 +02:00
|
|
|
|
2019-06-28 05:22:08 +02:00
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStream;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
public class PdfViewer extends AppCompatActivity implements LoaderManager.LoaderCallbacks<List<CharSequence>> {
|
|
|
|
public static final String TAG = "PdfViewer";
|
|
|
|
|
|
|
|
private static final String STATE_URI = "uri";
|
|
|
|
private static final String STATE_PAGE = "page";
|
2019-11-18 11:49:58 +01:00
|
|
|
private static final String STATE_ZOOM_RATIO = "zoomRatio";
|
2019-07-01 07:46:05 +02:00
|
|
|
private static final String STATE_DOCUMENT_ORIENTATION_DEGREES = "documentOrientationDegrees";
|
2019-06-28 05:22:08 +02:00
|
|
|
private static final String KEY_PROPERTIES = "properties";
|
2022-02-25 13:52:20 +01:00
|
|
|
private static final int MIN_WEBVIEW_RELEASE = 89;
|
2019-06-28 05:22:08 +02:00
|
|
|
|
|
|
|
private static final String CONTENT_SECURITY_POLICY =
|
|
|
|
"default-src 'none'; " +
|
|
|
|
"form-action 'none'; " +
|
|
|
|
"connect-src https://localhost/placeholder.pdf; " +
|
|
|
|
"img-src blob: 'self'; " +
|
2021-11-21 21:41:15 +01:00
|
|
|
"script-src 'self'; " +
|
2019-06-28 05:22:08 +02:00
|
|
|
"style-src 'self'; " +
|
|
|
|
"frame-ancestors 'none'; " +
|
|
|
|
"base-uri 'none'";
|
|
|
|
|
2021-11-21 21:45:58 +01:00
|
|
|
private static final String PERMISSIONS_POLICY =
|
|
|
|
"accelerometer=(), " +
|
|
|
|
"ambient-light-sensor=(), " +
|
|
|
|
"autoplay=(), " +
|
|
|
|
"battery=(), " +
|
|
|
|
"camera=(), " +
|
|
|
|
"clipboard-read=(), " +
|
|
|
|
"clipboard-write=(), " +
|
|
|
|
"display-capture=(), " +
|
|
|
|
"document-domain=(), " +
|
|
|
|
"encrypted-media=(), " +
|
|
|
|
"fullscreen=(), " +
|
|
|
|
"geolocation=(), " +
|
|
|
|
"gyroscope=(), " +
|
|
|
|
"hid=(), " +
|
2021-11-24 08:19:18 +01:00
|
|
|
"idle-detection=(), " +
|
2021-11-21 21:45:58 +01:00
|
|
|
"interest-cohort=(), " +
|
|
|
|
"magnetometer=(), " +
|
|
|
|
"microphone=(), " +
|
|
|
|
"midi=(), " +
|
|
|
|
"payment=(), " +
|
|
|
|
"picture-in-picture=(), " +
|
|
|
|
"publickey-credentials-get=(), " +
|
|
|
|
"screen-wake-lock=(), " +
|
|
|
|
"serial=(), " +
|
|
|
|
"sync-xhr=(), " +
|
|
|
|
"usb=(), " +
|
|
|
|
"xr-spatial-tracking=()";
|
2019-06-28 05:22:08 +02:00
|
|
|
|
2019-11-18 11:49:58 +01:00
|
|
|
private static final float MIN_ZOOM_RATIO = 0.5f;
|
|
|
|
private static final float MAX_ZOOM_RATIO = 1.5f;
|
2019-06-28 05:22:08 +02:00
|
|
|
private static final int ALPHA_LOW = 130;
|
|
|
|
private static final int ALPHA_HIGH = 255;
|
|
|
|
private static final int STATE_LOADED = 1;
|
|
|
|
private static final int STATE_END = 2;
|
|
|
|
private static final int PADDING = 10;
|
|
|
|
|
|
|
|
private Uri mUri;
|
|
|
|
public int mPage;
|
|
|
|
public int mNumPages;
|
2019-11-18 11:49:58 +01:00
|
|
|
private float mZoomRatio = 1f;
|
2017-11-14 18:29:10 +01:00
|
|
|
private int mDocumentOrientationDegrees;
|
2019-06-28 05:22:08 +02:00
|
|
|
private int mDocumentState;
|
|
|
|
private List<CharSequence> mDocumentProperties;
|
|
|
|
private InputStream mInputStream;
|
|
|
|
|
2022-02-07 17:42:47 +01:00
|
|
|
private PdfviewerBinding binding;
|
2019-06-28 05:22:08 +02:00
|
|
|
private TextView mTextView;
|
|
|
|
private Toast mToast;
|
2020-04-11 19:21:36 +02:00
|
|
|
private Snackbar snackbar;
|
2019-06-28 05:22:08 +02:00
|
|
|
|
2022-04-22 06:22:19 +02:00
|
|
|
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;
|
|
|
|
loadPdf();
|
|
|
|
invalidateOptionsMenu();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2019-06-28 05:22:08 +02:00
|
|
|
private class Channel {
|
|
|
|
@JavascriptInterface
|
|
|
|
public int getPage() {
|
|
|
|
return mPage;
|
|
|
|
}
|
|
|
|
|
|
|
|
@JavascriptInterface
|
2019-11-18 11:49:58 +01:00
|
|
|
public float getZoomRatio() {
|
|
|
|
return mZoomRatio;
|
2019-06-28 05:22:08 +02:00
|
|
|
}
|
|
|
|
|
2017-11-14 18:29:10 +01:00
|
|
|
@JavascriptInterface
|
|
|
|
public int getDocumentOrientationDegrees() {
|
|
|
|
return mDocumentOrientationDegrees;
|
|
|
|
}
|
|
|
|
|
2019-06-28 05:22:08 +02:00
|
|
|
@JavascriptInterface
|
|
|
|
public void setNumPages(int numPages) {
|
|
|
|
mNumPages = numPages;
|
2019-10-06 00:16:35 +02:00
|
|
|
runOnUiThread(PdfViewer.this::invalidateOptionsMenu);
|
2019-06-28 05:22:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@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);
|
2022-02-06 04:21:10 +01:00
|
|
|
runOnUiThread(() -> LoaderManager.getInstance(PdfViewer.this).restartLoader(DocumentPropertiesLoader.ID, args, PdfViewer.this));
|
2019-06-28 05:22:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2019-08-17 01:42:51 +02:00
|
|
|
@SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"})
|
2019-06-28 05:22:08 +02:00
|
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
|
|
super.onCreate(savedInstanceState);
|
2022-02-07 17:42:47 +01:00
|
|
|
binding = PdfviewerBinding.inflate(getLayoutInflater());
|
2022-02-18 10:47:26 +01:00
|
|
|
setContentView(binding.getRoot());
|
2022-02-07 17:42:47 +01:00
|
|
|
setSupportActionBar(binding.toolbar);
|
2019-06-28 05:22:08 +02:00
|
|
|
|
2022-02-20 21:21:25 +01:00
|
|
|
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
|
|
|
|
|
2022-02-20 17:29:02 +01:00
|
|
|
// 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;
|
|
|
|
});
|
|
|
|
|
2022-02-18 10:47:26 +01:00
|
|
|
binding.webview.setBackgroundColor(Color.TRANSPARENT);
|
2019-08-20 18:23:38 +02:00
|
|
|
|
2021-11-21 21:49:26 +01:00
|
|
|
if (BuildConfig.DEBUG) {
|
2022-02-06 04:33:55 +01:00
|
|
|
WebView.setWebContentsDebuggingEnabled(true);
|
2021-11-21 21:49:26 +01:00
|
|
|
}
|
|
|
|
|
2022-02-18 10:47:26 +01:00
|
|
|
final WebSettings settings = binding.webview.getSettings();
|
2019-06-28 05:22:08 +02:00
|
|
|
settings.setAllowContentAccess(false);
|
|
|
|
settings.setAllowFileAccess(false);
|
|
|
|
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
|
|
|
|
settings.setJavaScriptEnabled(true);
|
|
|
|
|
|
|
|
CookieManager.getInstance().setAcceptCookie(false);
|
|
|
|
|
2022-02-18 10:47:26 +01:00
|
|
|
binding.webview.addJavascriptInterface(new Channel(), "channel");
|
2019-06-28 05:22:08 +02:00
|
|
|
|
2022-02-18 10:47:26 +01:00
|
|
|
binding.webview.setWebViewClient(new WebViewClient() {
|
2019-06-28 05:22:08 +02:00
|
|
|
private WebResourceResponse fromAsset(final String mime, final String path) {
|
|
|
|
try {
|
|
|
|
InputStream inputStream = getAssets().open(path.substring(1));
|
|
|
|
return new WebResourceResponse(mime, null, inputStream);
|
|
|
|
} catch (IOException e) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
|
|
|
|
if (!"GET".equals(request.getMethod())) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
final Uri url = request.getUrl();
|
|
|
|
if (!"localhost".equals(url.getHost())) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
final String path = url.getPath();
|
|
|
|
Log.d(TAG, "path " + path);
|
|
|
|
|
|
|
|
if ("/placeholder.pdf".equals(path)) {
|
|
|
|
return new WebResourceResponse("application/pdf", null, mInputStream);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ("/viewer.html".equals(path)) {
|
|
|
|
final WebResourceResponse response = fromAsset("text/html", path);
|
2022-02-06 05:15:19 +01:00
|
|
|
HashMap<String, String> headers = new HashMap<>();
|
2019-06-28 05:22:08 +02:00
|
|
|
headers.put("Content-Security-Policy", CONTENT_SECURITY_POLICY);
|
2021-11-21 21:45:58 +01:00
|
|
|
headers.put("Permissions-Policy", PERMISSIONS_POLICY);
|
2019-07-01 07:52:34 +02:00
|
|
|
headers.put("X-Content-Type-Options", "nosniff");
|
2019-06-28 05:22:08 +02:00
|
|
|
response.setResponseHeaders(headers);
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ("/viewer.css".equals(path)) {
|
|
|
|
return fromAsset("text/css", path);
|
|
|
|
}
|
|
|
|
|
2019-06-30 02:03:46 +02:00
|
|
|
if ("/viewer.js".equals(path) || "/pdf.js".equals(path) || "/pdf.worker.js".equals(path)) {
|
2019-06-28 05:22:08 +02:00
|
|
|
return fromAsset("application/javascript", path);
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onPageFinished(WebView view, String url) {
|
|
|
|
mDocumentState = STATE_LOADED;
|
|
|
|
invalidateOptionsMenu();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-02-18 10:47:26 +01:00
|
|
|
GestureHelper.attach(PdfViewer.this, binding.webview,
|
2019-10-05 16:00:25 +02:00
|
|
|
new GestureHelper.GestureListener() {
|
2019-08-17 01:42:51 +02:00
|
|
|
@Override
|
2019-10-05 16:00:25 +02:00
|
|
|
public boolean onTapUp() {
|
2019-08-17 01:42:51 +02:00
|
|
|
if (mUri != null) {
|
2022-02-18 10:47:26 +01:00
|
|
|
binding.webview.evaluateJavascript("isTextSelected()", selection -> {
|
2022-02-06 05:15:19 +01:00
|
|
|
if (!Boolean.parseBoolean(selection)) {
|
2019-08-17 01:42:51 +02:00
|
|
|
if ((getWindow().getDecorView().getSystemUiVisibility() &
|
|
|
|
View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
|
|
|
|
hideSystemUi();
|
|
|
|
} else {
|
|
|
|
showSystemUi();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2019-10-05 12:06:00 +02:00
|
|
|
|
|
|
|
@Override
|
2020-04-04 17:57:15 +02:00
|
|
|
public void onZoomIn(float value) {
|
2020-04-04 18:22:51 +02:00
|
|
|
zoomIn(value, false);
|
2019-10-05 12:06:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2020-04-04 17:57:15 +02:00
|
|
|
public void onZoomOut(float value) {
|
2020-04-04 18:22:51 +02:00
|
|
|
zoomOut(value, false);
|
2019-10-05 12:06:00 +02:00
|
|
|
}
|
2020-04-04 17:54:34 +02:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onZoomEnd() {
|
|
|
|
zoomEnd();
|
|
|
|
}
|
2019-10-05 12:06:00 +02:00
|
|
|
});
|
|
|
|
|
2019-06-28 05:22:08 +02:00
|
|
|
mTextView = new TextView(this);
|
|
|
|
mTextView.setBackgroundColor(Color.DKGRAY);
|
|
|
|
mTextView.setTextColor(ColorStateList.valueOf(Color.WHITE));
|
|
|
|
mTextView.setTextSize(18);
|
|
|
|
mTextView.setPadding(PADDING, 0, PADDING, 0);
|
|
|
|
|
|
|
|
// If loaders are not being initialized in onCreate(), the result will not be delivered
|
|
|
|
// after orientation change (See FragmentHostCallback), thus initialize the
|
|
|
|
// loader manager impl so that the result will be delivered.
|
|
|
|
LoaderManager.getInstance(this);
|
|
|
|
|
2022-02-18 10:47:26 +01:00
|
|
|
snackbar = Snackbar.make(binding.webview, "", Snackbar.LENGTH_LONG);
|
2020-04-11 19:21:36 +02:00
|
|
|
|
2019-06-28 05:22:08 +02:00
|
|
|
final Intent intent = getIntent();
|
|
|
|
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
|
|
|
|
if (!"application/pdf".equals(intent.getType())) {
|
2020-04-11 19:21:36 +02:00
|
|
|
snackbar.setText(R.string.invalid_mime_type).show();
|
2019-06-28 05:22:08 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
mUri = intent.getData();
|
|
|
|
mPage = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (savedInstanceState != null) {
|
|
|
|
mUri = savedInstanceState.getParcelable(STATE_URI);
|
|
|
|
mPage = savedInstanceState.getInt(STATE_PAGE);
|
2019-11-18 11:49:58 +01:00
|
|
|
mZoomRatio = savedInstanceState.getFloat(STATE_ZOOM_RATIO);
|
2019-07-01 07:46:05 +02:00
|
|
|
mDocumentOrientationDegrees = savedInstanceState.getInt(STATE_DOCUMENT_ORIENTATION_DEGREES);
|
2019-06-28 05:22:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (mUri != null) {
|
2020-05-28 20:02:44 +02:00
|
|
|
if ("file".equals(mUri.getScheme())) {
|
|
|
|
snackbar.setText(R.string.legacy_file_uri).show();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-06-28 05:22:08 +02:00
|
|
|
loadPdf();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-25 13:52:20 +01:00
|
|
|
@Override
|
|
|
|
protected void onResume() {
|
|
|
|
super.onResume();
|
|
|
|
|
|
|
|
// The user could have left the activity to update the WebView
|
|
|
|
invalidateOptionsMenu();
|
|
|
|
if (getWebViewRelease() >= MIN_WEBVIEW_RELEASE) {
|
|
|
|
binding.webviewOutOfDateLayout.setVisibility(View.GONE);
|
|
|
|
binding.webview.setVisibility(View.VISIBLE);
|
|
|
|
} else {
|
|
|
|
binding.webview.setVisibility(View.GONE);
|
|
|
|
binding.webviewOutOfDateMessage.setText(getString(R.string.webview_out_of_date_message, getWebViewRelease(), MIN_WEBVIEW_RELEASE));
|
|
|
|
binding.webviewOutOfDateLayout.setVisibility(View.VISIBLE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private int getWebViewRelease() {
|
|
|
|
PackageInfo webViewPackage = WebView.getCurrentWebViewPackage();
|
|
|
|
String webViewVersionName = webViewPackage.versionName;
|
|
|
|
return Integer.parseInt(webViewVersionName.substring(0, webViewVersionName.indexOf(".")));
|
|
|
|
}
|
|
|
|
|
2022-02-06 05:39:42 +01:00
|
|
|
@NonNull
|
2019-06-28 05:22:08 +02:00
|
|
|
@Override
|
|
|
|
public Loader<List<CharSequence>> onCreateLoader(int id, Bundle args) {
|
|
|
|
return new DocumentPropertiesLoader(this, args.getString(KEY_PROPERTIES), mNumPages, mUri);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-02-06 05:39:42 +01:00
|
|
|
public void onLoadFinished(@NonNull Loader<List<CharSequence>> loader, List<CharSequence> data) {
|
2019-06-28 05:22:08 +02:00
|
|
|
mDocumentProperties = data;
|
|
|
|
LoaderManager.getInstance(this).destroyLoader(DocumentPropertiesLoader.ID);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-02-06 05:39:42 +01:00
|
|
|
public void onLoaderReset(@NonNull Loader<List<CharSequence>> loader) {
|
2019-06-28 05:22:08 +02:00
|
|
|
mDocumentProperties = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void loadPdf() {
|
|
|
|
try {
|
|
|
|
if (mInputStream != null) {
|
|
|
|
mInputStream.close();
|
|
|
|
}
|
|
|
|
mInputStream = getContentResolver().openInputStream(mUri);
|
|
|
|
} catch (IOException e) {
|
2020-04-11 19:21:36 +02:00
|
|
|
snackbar.setText(R.string.io_error).show();
|
2019-06-28 05:22:08 +02:00
|
|
|
return;
|
|
|
|
}
|
2020-08-25 06:58:59 +02:00
|
|
|
|
|
|
|
showSystemUi();
|
2022-02-18 10:47:26 +01:00
|
|
|
binding.webview.loadUrl("https://localhost/viewer.html");
|
2019-06-28 05:22:08 +02:00
|
|
|
}
|
|
|
|
|
2020-04-04 18:14:38 +02:00
|
|
|
private void renderPage(final int zoom) {
|
2022-02-18 10:47:26 +01:00
|
|
|
binding.webview.evaluateJavascript("onRenderPage(" + zoom + ")", null);
|
2019-06-28 05:22:08 +02:00
|
|
|
}
|
|
|
|
|
2017-11-14 18:29:10 +01:00
|
|
|
private void documentOrientationChanged(final int orientationDegreesOffset) {
|
|
|
|
mDocumentOrientationDegrees = (mDocumentOrientationDegrees + orientationDegreesOffset) % 360;
|
|
|
|
if (mDocumentOrientationDegrees < 0) {
|
|
|
|
mDocumentOrientationDegrees += 360;
|
|
|
|
}
|
2020-04-04 18:14:38 +02:00
|
|
|
renderPage(0);
|
2017-11-14 18:29:10 +01:00
|
|
|
}
|
|
|
|
|
2019-06-28 05:22:08 +02:00
|
|
|
private void openDocument() {
|
|
|
|
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
|
|
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
|
|
|
intent.setType("application/pdf");
|
2022-04-22 06:22:19 +02:00
|
|
|
openDocumentLauncher.launch(intent);
|
2019-06-28 05:22:08 +02:00
|
|
|
}
|
|
|
|
|
2022-03-05 11:55:45 +01:00
|
|
|
private void shareDocument() {
|
|
|
|
if (mUri != null) {
|
|
|
|
Intent shareIntent = new Intent(Intent.ACTION_SEND);
|
|
|
|
shareIntent.setDataAndTypeAndNormalize(mUri, "application/pdf");
|
|
|
|
shareIntent.putExtra(Intent.EXTRA_STREAM, mUri);
|
|
|
|
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
|
|
startActivity(Intent.createChooser(shareIntent, getString(R.string.action_share)));
|
|
|
|
} else {
|
|
|
|
Log.w(TAG, "Cannot share unexpected null URI");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-04 18:22:51 +02:00
|
|
|
private void zoomIn(float value, boolean end) {
|
2019-11-18 11:49:58 +01:00
|
|
|
if (mZoomRatio < MAX_ZOOM_RATIO) {
|
2020-04-04 18:27:35 +02:00
|
|
|
mZoomRatio = Math.min(mZoomRatio + value, MAX_ZOOM_RATIO);
|
2020-04-04 18:22:51 +02:00
|
|
|
renderPage(end ? 1 : 2);
|
2019-10-05 12:06:00 +02:00
|
|
|
invalidateOptionsMenu();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-04 18:22:51 +02:00
|
|
|
private void zoomOut(float value, boolean end) {
|
2019-11-18 11:49:58 +01:00
|
|
|
if (mZoomRatio > MIN_ZOOM_RATIO) {
|
2020-04-04 18:27:35 +02:00
|
|
|
mZoomRatio = Math.max(mZoomRatio - value, MIN_ZOOM_RATIO);
|
2020-04-04 18:22:51 +02:00
|
|
|
renderPage(end ? 1 : 2);
|
2019-10-05 12:06:00 +02:00
|
|
|
invalidateOptionsMenu();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-04 17:54:34 +02:00
|
|
|
private void zoomEnd() {
|
2020-04-04 18:14:38 +02:00
|
|
|
renderPage(1);
|
2020-04-04 17:54:34 +02:00
|
|
|
}
|
|
|
|
|
2019-06-28 05:22:08 +02:00
|
|
|
private static void enableDisableMenuItem(MenuItem item, boolean enable) {
|
|
|
|
if (enable) {
|
2019-10-06 00:16:35 +02:00
|
|
|
item.setEnabled(true);
|
|
|
|
item.getIcon().setAlpha(ALPHA_HIGH);
|
|
|
|
} else {
|
2019-06-28 05:22:08 +02:00
|
|
|
item.setEnabled(false);
|
|
|
|
item.getIcon().setAlpha(ALPHA_LOW);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-22 16:46:10 +01:00
|
|
|
public void onJumpToPageInDocument(final int selected_page) {
|
|
|
|
if (selected_page >= 1 && selected_page <= mNumPages && mPage != selected_page) {
|
2019-06-28 05:22:08 +02:00
|
|
|
mPage = selected_page;
|
2020-04-04 18:14:38 +02:00
|
|
|
renderPage(0);
|
2019-06-28 05:22:08 +02:00
|
|
|
showPageNumber();
|
2019-10-06 00:16:35 +02:00
|
|
|
invalidateOptionsMenu();
|
2019-06-28 05:22:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-17 01:42:51 +02:00
|
|
|
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);
|
2022-02-07 17:42:47 +01:00
|
|
|
getSupportActionBar().show();
|
2019-08-17 01:42:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2022-02-07 17:42:47 +01:00
|
|
|
getSupportActionBar().hide();
|
2019-08-17 01:42:51 +02:00
|
|
|
}
|
|
|
|
|
2019-06-28 05:22:08 +02:00
|
|
|
@Override
|
2022-02-06 05:39:42 +01:00
|
|
|
public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
|
2019-06-28 05:22:08 +02:00
|
|
|
super.onSaveInstanceState(savedInstanceState);
|
|
|
|
savedInstanceState.putParcelable(STATE_URI, mUri);
|
|
|
|
savedInstanceState.putInt(STATE_PAGE, mPage);
|
2019-11-18 11:49:58 +01:00
|
|
|
savedInstanceState.putFloat(STATE_ZOOM_RATIO, mZoomRatio);
|
2019-07-01 07:46:05 +02:00
|
|
|
savedInstanceState.putInt(STATE_DOCUMENT_ORIENTATION_DEGREES, mDocumentOrientationDegrees);
|
2019-06-28 05:22:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private void showPageNumber() {
|
|
|
|
if (mToast != null) {
|
|
|
|
mToast.cancel();
|
|
|
|
}
|
|
|
|
mTextView.setText(String.format("%s/%s", mPage, mNumPages));
|
|
|
|
mToast = new Toast(getApplicationContext());
|
|
|
|
mToast.setGravity(Gravity.BOTTOM | Gravity.END, PADDING, PADDING);
|
|
|
|
mToast.setDuration(Toast.LENGTH_SHORT);
|
|
|
|
mToast.setView(mTextView);
|
|
|
|
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) {
|
2022-02-06 05:15:19 +01:00
|
|
|
final int[] ids = { R.id.action_zoom_in, R.id.action_zoom_out, R.id.action_jump_to_page,
|
2017-11-22 16:46:10 +01:00
|
|
|
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,
|
2022-03-05 11:55:45 +01:00
|
|
|
R.id.action_view_document_properties, R.id.action_share };
|
2019-06-28 05:22:08 +02:00
|
|
|
if (mDocumentState < STATE_LOADED) {
|
|
|
|
for (final int id : ids) {
|
|
|
|
final MenuItem item = menu.findItem(id);
|
|
|
|
if (item.isVisible()) {
|
|
|
|
item.setVisible(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (mDocumentState == STATE_LOADED) {
|
|
|
|
for (final int id : ids) {
|
|
|
|
final MenuItem item = menu.findItem(id);
|
|
|
|
if (!item.isVisible()) {
|
|
|
|
item.setVisible(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
mDocumentState = STATE_END;
|
|
|
|
}
|
|
|
|
|
2022-02-25 13:52:20 +01:00
|
|
|
enableDisableMenuItem(menu.findItem(R.id.action_open), getWebViewRelease() >= MIN_WEBVIEW_RELEASE);
|
2019-11-18 11:49:58 +01:00
|
|
|
enableDisableMenuItem(menu.findItem(R.id.action_zoom_in), mZoomRatio != MAX_ZOOM_RATIO);
|
|
|
|
enableDisableMenuItem(menu.findItem(R.id.action_zoom_out), mZoomRatio != MIN_ZOOM_RATIO);
|
2022-03-05 11:55:45 +01:00
|
|
|
enableDisableMenuItem(menu.findItem(R.id.action_share), mUri != null);
|
2019-10-06 00:16:35 +02:00
|
|
|
enableDisableMenuItem(menu.findItem(R.id.action_next), mPage < mNumPages);
|
|
|
|
enableDisableMenuItem(menu.findItem(R.id.action_previous), mPage > 1);
|
2019-11-18 11:49:58 +01:00
|
|
|
|
|
|
|
return true;
|
2019-06-28 05:22:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
2021-11-21 22:02:22 +01:00
|
|
|
final int itemId = item.getItemId();
|
|
|
|
if (itemId == R.id.action_previous) {
|
|
|
|
onJumpToPageInDocument(mPage - 1);
|
|
|
|
return true;
|
|
|
|
} else if (itemId == R.id.action_next) {
|
|
|
|
onJumpToPageInDocument(mPage + 1);
|
|
|
|
return true;
|
|
|
|
} else if (itemId == R.id.action_first) {
|
|
|
|
onJumpToPageInDocument(1);
|
|
|
|
return true;
|
|
|
|
} else if (itemId == R.id.action_last) {
|
|
|
|
onJumpToPageInDocument(mNumPages);
|
|
|
|
return true;
|
|
|
|
} 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;
|
|
|
|
} else if (itemId == R.id.action_rotate_counterclockwise) {
|
|
|
|
documentOrientationChanged(-90);
|
|
|
|
return true;
|
|
|
|
} else if (itemId == R.id.action_view_document_properties) {
|
|
|
|
DocumentPropertiesFragment
|
|
|
|
.newInstance(mDocumentProperties)
|
|
|
|
.show(getSupportFragmentManager(), DocumentPropertiesFragment.TAG);
|
|
|
|
return true;
|
|
|
|
} else if (itemId == R.id.action_jump_to_page) {
|
|
|
|
new JumpToPageFragment()
|
|
|
|
.show(getSupportFragmentManager(), JumpToPageFragment.TAG);
|
|
|
|
return true;
|
2022-03-05 11:55:45 +01:00
|
|
|
} else if (itemId == R.id.action_share) {
|
|
|
|
shareDocument();
|
|
|
|
return true;
|
2019-06-28 05:22:08 +02:00
|
|
|
}
|
2021-11-21 22:02:22 +01:00
|
|
|
|
|
|
|
return super.onOptionsItemSelected(item);
|
2019-06-28 05:22:08 +02:00
|
|
|
}
|
|
|
|
}
|