package paris.tourolle.darkroom; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.webkit.WebResourceRequest; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; /** * The manual that ships in the APK, shown in a WebView. * *

Why an activity of our own rather than the browser

* *

The desktop hands the manual to the system browser. Android leaves no * way to do the same: the page is an asset inside the APK, which is not a * file; an unpacked copy in app-private storage is a file no browser may * read; a {@code file:} URI handed to another app is refused since API 24; * and a {@code content:} URI serves the page but leaves the browser to fetch * every picture by a relative URL against the provider, which browsers do not * reliably do. A WebView reads {@code file:///android_asset/} straight from * the APK, pictures and section anchor included, and nothing is unpacked. * *

What it is not

* *

A browser. JavaScript stays off (the page has none), and a link that * leaves the manual — the design documents are on the forge — goes to the * user's browser rather than opening inside this view, so the only thing ever * shown here is the page the APK carries. * *

Started by {@code dr_ui::manual} with {@code Intent.setClassName}, so the * name here and there must agree; a test in lib.rs checks the manifest * declares it. */ public final class ManualActivity extends Activity { /** The section to open at, a heading's anchor. Absent opens the top. */ public static final String EXTRA_ANCHOR = "anchor"; private static final String PAGE = "file:///android_asset/manual/index.html"; private WebView web; @Override protected void onCreate(Bundle saved) { super.onCreate(saved); setTitle("DarkRoom manual"); web = new WebView(this); WebSettings settings = web.getSettings(); settings.setJavaScriptEnabled(false); // Pinch to zoom into a screenshot, which is 1600 pixels wide and drawn // at the width of a phone. settings.setBuiltInZoomControls(true); settings.setDisplayZoomControls(false); web.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { Uri uri = request.getUrl(); if ("file".equals(uri.getScheme())) { return false; } try { startActivity(new Intent(Intent.ACTION_VIEW, uri)); } catch (ActivityNotFoundException e) { // No browser on the device: the link does nothing, which // is all it could do. } return true; } }); setContentView(web); if (saved != null) { web.restoreState(saved); } else { String anchor = getIntent().getStringExtra(EXTRA_ANCHOR); web.loadUrl(anchor == null || anchor.isEmpty() ? PAGE : PAGE + "#" + anchor); } } @Override protected void onSaveInstanceState(Bundle out) { super.onSaveInstanceState(out); web.saveState(out); } /** Back walks back through the sections visited, then leaves. */ @Override public void onBackPressed() { if (web.canGoBack()) { web.goBack(); } else { super.onBackPressed(); } } @Override protected void onDestroy() { web.destroy(); super.onDestroy(); } }