Java tutorial
/*** / This file is part of Golly, a Game of Life Simulator. Copyright (C) 2013 Andrew Trevorrow and Tomas Rokicki. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Web site: http://sourceforge.net/projects/golly Authors: rokicki@gmail.com andrew@trevorrow.com / ***/ package net.sf.golly; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.support.v4.app.NavUtils; import android.util.DisplayMetrics; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.Button; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.Toast; public class HelpActivity extends BaseActivity { // see jnicalls.cpp for these native routines: private static native void nativeClassInit(); // this MUST be static private native void nativeCreate(); // the rest must NOT be static private native void nativeDestroy(); private native void nativeGetURL(String url, String pageurl); private native void nativeUnzipFile(String zippath); private native boolean nativeDownloadedFile(String url); // local fields: private static boolean firstcall = true; private static Bundle webbundle = null; // for saving/restoring scroll position and page history private boolean restarted = false; // onRestart was called before OnResume? private boolean clearhistory = false; // tell onPageFinished to clear page history? private WebView gwebview; // for displaying html pages private Button backbutton; // go to previous page private Button nextbutton; // go to next page private static String pageurl; // URL for last displayed page private ProgressBar progbar; // progress bar for downloads private LinearLayout proglayout; // view containing progress bar private boolean cancelled; // download was cancelled? public final static String SHOWHELP_MESSAGE = "net.sf.golly.SHOWHELP"; // ----------------------------------------------------------------------------- static { nativeClassInit(); // caches Java method IDs } // ----------------------------------------------------------------------------- // this class lets us intercept link taps private class MyWebViewClient extends WebViewClient { @Override public boolean shouldOverrideUrlLoading(WebView webview, String url) { // look for special prefixes used by Golly and return true if found if (url.startsWith("open:")) { openFile(url.substring(5)); return true; } if (url.startsWith("edit:")) { editFile(url.substring(5)); return true; } if (url.startsWith("rule:")) { changeRule(url.substring(5)); return true; } if (url.startsWith("lexpatt:")) { // user tapped on pattern in Life Lexicon loadLexiconPattern(url.substring(8)); return true; } if (url.startsWith("get:")) { // download file specifed in link (possibly relative to a previous full url) nativeGetURL(url.substring(4), pageurl); return true; } if (url.startsWith("unzip:")) { nativeUnzipFile(url.substring(6)); return true; } // no special prefix, so look for file with .zip/rle/lif/mc extension and download it if (nativeDownloadedFile(url)) { return true; } return false; } @Override public void onPageFinished(WebView webview, String url) { super.onPageFinished(webview, url); if (clearhistory) { // this only works after page is loaded gwebview.clearHistory(); clearhistory = false; } backbutton.setEnabled(gwebview.canGoBack()); nextbutton.setEnabled(gwebview.canGoForward()); // need URL of this page for relative "get:" links pageurl = gwebview.getUrl(); // Log.i("URL", pageurl); } } // ----------------------------------------------------------------------------- private void openFile(String filepath) { // switch to main screen and open given file Intent intent = new Intent(this, MainActivity.class); intent.putExtra(MainActivity.OPENFILE_MESSAGE, filepath); startActivity(intent); } // ----------------------------------------------------------------------------- private void editFile(String filepath) { // let user read/edit given file if (filepath.startsWith("Supplied/")) { // read contents of supplied .rule file into a string String prefix = baseapp.supplieddir.getAbsolutePath(); prefix = prefix.substring(0, prefix.length() - 8); // strip off "Supplied" String fullpath = prefix + filepath; // display filecontents Intent intent = new Intent(this, InfoActivity.class); intent.putExtra(InfoActivity.INFO_MESSAGE, fullpath); startActivity(intent); } else { // let user read or edit user's .rule file String fullpath = baseapp.userdir.getAbsolutePath() + "/" + filepath; Intent intent = new Intent(this, EditActivity.class); intent.putExtra(EditActivity.EDITFILE_MESSAGE, fullpath); startActivity(intent); } } // ----------------------------------------------------------------------------- private void changeRule(String rule) { // switch to main screen and change rule Intent intent = new Intent(this, MainActivity.class); intent.putExtra(MainActivity.RULE_MESSAGE, rule); startActivity(intent); } // ----------------------------------------------------------------------------- private void loadLexiconPattern(String pattern) { // switch to main screen and load given lexicon pattern Intent intent = new Intent(this, MainActivity.class); intent.putExtra(MainActivity.LEXICON_MESSAGE, pattern); startActivity(intent); } // ----------------------------------------------------------------------------- @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.help_layout); backbutton = (Button) findViewById(R.id.back); nextbutton = (Button) findViewById(R.id.forwards); progbar = (ProgressBar) findViewById(R.id.progress_bar); proglayout = (LinearLayout) findViewById(R.id.progress_layout); proglayout.setVisibility(LinearLayout.INVISIBLE); // show the Up button in the action bar getActionBar().setDisplayHomeAsUpEnabled(true); nativeCreate(); // cache this instance gwebview = (WebView) findViewById(R.id.webview); gwebview.setWebViewClient(new MyWebViewClient()); // JavaScript is used to detect device type gwebview.getSettings().setJavaScriptEnabled(true); DisplayMetrics metrics = getResources().getDisplayMetrics(); // my Nexus 7 has a density of 320 if (metrics.densityDpi > 300) { // use bigger font size for high density screens (default size is 16) gwebview.getSettings().setDefaultFontSize(24); } if (firstcall) { firstcall = false; backbutton.setEnabled(false); nextbutton.setEnabled(false); showContentsPage(); // pageurl has now been initialized } } // ----------------------------------------------------------------------------- @Override protected void onNewIntent(Intent intent) { setIntent(intent); restarted = true; } // ----------------------------------------------------------------------------- @Override protected void onPause() { super.onPause(); gwebview.onPause(); // save scroll position and back/forward history if (webbundle == null) webbundle = new Bundle(); gwebview.saveState(webbundle); } // ----------------------------------------------------------------------------- @Override protected void onResume() { super.onResume(); gwebview.onResume(); // restore scroll position and back/forward history if (webbundle != null && !restarted) { gwebview.restoreState(webbundle); } restarted = false; // check for messages sent by other activities Intent intent = getIntent(); String filepath = intent.getStringExtra(SHOWHELP_MESSAGE); if (filepath != null) { gwebview.loadUrl("file://" + filepath); } else { gwebview.reload(); } } // ----------------------------------------------------------------------------- @Override protected void onRestart() { super.onRestart(); // set flag to prevent onResume calling gwebview.restoreState // causing app to crash for some unknown reason restarted = true; } // ----------------------------------------------------------------------------- @Override protected void onDestroy() { nativeDestroy(); super.onDestroy(); } // ----------------------------------------------------------------------------- @Override public boolean onCreateOptionsMenu(Menu menu) { // add main.xml items to the action bar getMenuInflater().inflate(R.menu.main, menu); // disable the item for this activity MenuItem item = menu.findItem(R.id.help); item.setEnabled(false); return true; } // ----------------------------------------------------------------------------- @Override public boolean onOptionsItemSelected(MenuItem item) { // action bar item has been tapped Intent intent; switch (item.getItemId()) { case android.R.id.home: // the Home or Up button will go back to MainActivity NavUtils.navigateUpFromSameTask(this); return true; case R.id.open: finish(); intent = new Intent(this, OpenActivity.class); startActivity(intent); return true; case R.id.settings: finish(); intent = new Intent(this, SettingsActivity.class); startActivity(intent); return true; case R.id.help: // do nothing break; } return super.onOptionsItemSelected(item); } // ----------------------------------------------------------------------------- private void showContentsPage() { // display html data in supplieddir/Help/index.html String fullpath = baseapp.supplieddir.getAbsolutePath() + "/Help/index.html"; File htmlfile = new File(fullpath); if (htmlfile.exists()) { gwebview.loadUrl("file://" + fullpath); // fullpath starts with "/" } else { // should never happen String htmldata = "<html><center><font size=+1 color=\"red\">Failed to find index.html!</font></center></html>"; gwebview.loadDataWithBaseURL(null, htmldata, "text/html", "utf-8", null); } } // ----------------------------------------------------------------------------- // called when the Contents button is tapped public void doContents(View view) { clearhistory = true; // tell onPageFinished to clear page history showContentsPage(); if (webbundle != null) { webbundle.clear(); webbundle = null; } } // ----------------------------------------------------------------------------- // called when backbutton is tapped public void doBack(View view) { gwebview.goBack(); } // ----------------------------------------------------------------------------- // called when nextbutton is tapped public void doForwards(View view) { gwebview.goForward(); } // ----------------------------------------------------------------------------- // called when Cancel is tapped public void doCancel(View view) { cancelled = true; } // ----------------------------------------------------------------------------- private String downloadURL(String urlstring, String filepath) { // download given url and save data in given file try { File outfile = new File(filepath); final int BUFFSIZE = 8192; FileOutputStream outstream = null; try { outstream = new FileOutputStream(outfile); } catch (FileNotFoundException e) { return "File not found: " + filepath; } long starttime = System.nanoTime(); // Log.i("downloadURL: ", urlstring); URL url = new URL(urlstring); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setAllowUserInteraction(false); connection.setInstanceFollowRedirects(true); connection.setRequestMethod("GET"); connection.connect(); if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { outstream.close(); return "No HTTP_OK response."; } // init info for progress bar int filesize = connection.getContentLength(); int downloaded = 0; int percent; int lastpercent = 0; // stream the data to given file InputStream instream = connection.getInputStream(); byte[] buffer = new byte[BUFFSIZE]; int bufflen = 0; while ((bufflen = instream.read(buffer, 0, BUFFSIZE)) > 0) { outstream.write(buffer, 0, bufflen); downloaded += bufflen; percent = (int) ((downloaded / (float) filesize) * 100); if (percent > lastpercent) { progbar.setProgress(percent); lastpercent = percent; } // show proglayout only if download takes more than 1 second if (System.nanoTime() - starttime > 1000000000L) { runOnUiThread(new Runnable() { public void run() { proglayout.setVisibility(LinearLayout.VISIBLE); } }); starttime = Long.MAX_VALUE; // only show it once } if (cancelled) break; } outstream.close(); connection.disconnect(); if (cancelled) return "Cancelled."; } catch (MalformedURLException e) { return "Bad URL string: " + urlstring; } catch (IOException e) { return "Could not connect to URL: " + urlstring; } return ""; // success } // ----------------------------------------------------------------------------- private String dresult; static class LooperInterrupter extends Handler { public void handleMessage(Message msg) { throw new RuntimeException(); } } // this method is called from C++ code (see jnicalls.cpp) private String DownloadFile(String urlstring, String filepath) { // we cannot do network connections on main thread, so we do the // download on a new thread, but we have to wait for it to finish final Handler handler = new LooperInterrupter(); cancelled = false; progbar.setProgress(0); // don't show proglayout immediately // proglayout.setVisibility(LinearLayout.VISIBLE); dresult = ""; final String durl = urlstring; final String dfile = filepath; Thread download_thread = new Thread(new Runnable() { public void run() { dresult = downloadURL(durl, dfile); handler.sendMessage(handler.obtainMessage()); } }); download_thread.setPriority(Thread.MAX_PRIORITY); download_thread.start(); // wait for thread to finish try { Looper.loop(); } catch (RuntimeException re) { } proglayout.setVisibility(LinearLayout.INVISIBLE); if (dresult.length() > 0 && !cancelled) { Toast.makeText(this, "Download failed! " + dresult, Toast.LENGTH_SHORT).show(); } return dresult; } } // HelpActivity class