Java tutorial
/* * ComicViewer - Android library for viewing comics with hover text. * * xkcdViewer - Android app to view xkcd comics with hover text * Copyright (C) 2009-2014 Tom Coxon, Tyler Breisacher, David McCullough, * Kristian Lundkvist, Ivan Vasiljevi. * * 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. */ package net.bytten.comicviewer; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import org.json.JSONException; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.preference.PreferenceManager; import android.text.InputType; import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnFocusChangeListener; import android.view.ViewGroup; import android.view.Window; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.webkit.WebChromeClient; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.Button; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; import android.widget.Toast; public abstract class ComicViewerActivity extends Activity { static class CouldntParseComicPage extends Exception { private static final long serialVersionUID = 1L; } public static final FrameLayout.LayoutParams ZOOM_PARAMS = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM); private HackedWebView webview; private TextView title; protected IComicInfo comicInfo; protected IComicDefinition comicDef; protected IComicProvider provider; private EditText comicIdSel; private View zoom = null; private ImageView bookmarkBtn = null; // Constants for showActivityForResult calls static final int PICK_ARCHIVE_ITEM = 0; // Prep the errors and the failedDialog so we can get a reference to it later. private String errors = ""; private AlertDialog failedDialog; // Constants defining dialogs static final int DIALOG_SHOW_HOVER_TEXT = 0; static final int DIALOG_SHOW_ABOUT = 1; static final int DIALOG_SEARCH_BY_TITLE = 2; static final int DIALOG_FAILED = 3; protected abstract IComicDefinition makeComicDef(); protected abstract Class<? extends ArchiveActivity> getArchiveActivityClass(); protected abstract String getStringAppName(); protected abstract String getStringAboutText(); protected void resetContent() { comicDef = makeComicDef(); provider = comicDef.getProvider(); comicInfo = provider.createEmptyComicInfo(); //Only hide the title bar if we're running an android less than Android 3.0 if (VersionHacks.getSdkInt() < 11) requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.main); webview = (HackedWebView) findViewById(R.id.viewer); title = (TextView) findViewById(R.id.title); comicIdSel = (EditText) findViewById(R.id.comicIdSel); webview.requestFocus(); zoom = webview.getZoomControls(); webview.setClickable(true); webview.setOnClickListener(new OnClickListener() { public void onClick(View v) { if (!"".equals(comicInfo.getAlt())) showDialog(DIALOG_SHOW_HOVER_TEXT); } }); title.setText(comicInfo.getTitle()); comicIdSel.setText(comicInfo.getId()); if (comicDef.idsAreNumbers()) comicIdSel.setInputType(InputType.TYPE_CLASS_NUMBER); comicIdSel.setOnEditorActionListener(new OnEditorActionListener() { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { String text = comicIdSel.getText().toString(); if (!text.equals("") && (actionId == EditorInfo.IME_ACTION_GO || (actionId == EditorInfo.IME_NULL && event.getKeyCode() == KeyEvent.KEYCODE_ENTER))) { loadComic(createComicUri(text)); comicIdSel.setText(""); return true; } return false; } }); comicIdSel.setOnFocusChangeListener(new OnFocusChangeListener() { public void onFocusChange(View v, boolean hasFocus) { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (hasFocus) { comicIdSel.setText(""); imm.showSoftInput(comicIdSel, InputMethodManager.SHOW_IMPLICIT); } else { imm.hideSoftInputFromWindow(comicIdSel.getWindowToken(), 0); } } }); ((Button) findViewById(R.id.firstBtn)).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { goToFirst(); } }); ((Button) findViewById(R.id.prevBtn)).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { goToPrev(); } }); ((Button) findViewById(R.id.nextBtn)).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { goToNext(); } }); ((Button) findViewById(R.id.finalBtn)).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { goToFinal(); } }); ((ImageView) findViewById(R.id.randomBtn)).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { goToRandom(); } }); bookmarkBtn = (ImageView) findViewById(R.id.bookmarkBtn); bookmarkBtn.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { toggleBookmark(); } }); refreshBookmarkBtn(); } public void refreshBookmarkBtn() { if (comicInfo != null && comicInfo.isBookmarked()) { bookmarkBtn.setBackgroundResource(android.R.drawable.btn_star_big_on); } else { bookmarkBtn.setBackgroundResource(android.R.drawable.btn_star_big_off); } } public void toggleBookmark() { if (comicInfo != null) { if (comicInfo.isBookmarked()) { BookmarksHelper.removeBookmark(this, comicInfo.getId()); } else { BookmarksHelper.addBookmark(this, comicInfo.getId(), comicInfo.getTitle()); } comicInfo.setBookmarked(!comicInfo.isBookmarked()); refreshBookmarkBtn(); } } public void resetZoomControlEnable() { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); final boolean allowPinchZoom = prefs.getBoolean("useZoomControls", !VersionHacks.isIncredible()), showZoomButtons = prefs.getBoolean("showZoomButtons", true); setZoomControlEnable(allowPinchZoom, showZoomButtons); } public boolean isReopenLastComic() { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); return prefs.getBoolean("reopenLastComic", false); } public String getLastReadComic() throws NumberFormatException { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); return prefs.getString("lastComic", null); } public void setLastReadComic(String id) { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); final SharedPreferences.Editor editor = prefs.edit(); editor.putString("lastComic", id); editor.commit(); } public void setZoomControlEnable(boolean allowPinchZoom, boolean showZoomButtons) { final ViewGroup zoomParent = (ViewGroup) webview.getParent().getParent(); if (zoom.getParent() == zoomParent) zoomParent.removeView(zoom); webview.getSettings().setBuiltInZoomControls(allowPinchZoom); webview.setAllowZoomButtons(false); webview.setAllowPinchZoom(allowPinchZoom); if (showZoomButtons) { if (allowPinchZoom) { webview.setAllowZoomButtons(true); } else { zoomParent.addView(zoom, ZOOM_PARAMS); zoom.setVisibility(View.GONE); } } } /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); resetContent(); final Intent i = this.getIntent(); // Decide if application is called from launcher or from other application // by using action. If you use category it can be null, that is the case // with gmail application and link from that application. if (Intent.ACTION_VIEW.equals(i.getAction())) { // Link to comic boolean tryArchive = true; if (comicDef.isComicUrl(i.getData())) { try { loadComic(provider.comicDataUrlForUrl(i.getData())); tryArchive = false; } catch (NumberFormatException e) { // Fall through to trying the URL as an archive URL } } if (tryArchive) { if (comicDef.isArchiveUrl(i.getData())) { showArchive(); this.finish(); } else { // it wasn't a link to comic or to the archive // last ditch attempt: was it a link to the home page? if (comicDef.isHomeUrl(i.getData())) { goToFinal(); } else { toast("This comic viewer can't display this content."); this.finish(); } } } } else { // Started by application icon if (isReopenLastComic()) { try { loadComic(createComicUri(getLastReadComic())); } catch (NumberFormatException e) { goToFinal(); } } else { goToFinal(); } } } public void showArchive() { Intent i = new Intent(this, getArchiveActivityClass()); i.setData(comicDef.getArchiveUrl()); i.setAction(Intent.ACTION_VIEW); i.putExtra(getPackageName() + "LoadType", ArchiveActivity.LoadType.ARCHIVE); startActivityForResult(i, PICK_ARCHIVE_ITEM); } public void showBookmarks() { Intent i = new Intent(this, getArchiveActivityClass()); i.setData(comicDef.getArchiveUrl()); i.setAction(Intent.ACTION_VIEW); i.putExtra(getPackageName() + "LoadType", ArchiveActivity.LoadType.BOOKMARKS); startActivityForResult(i, PICK_ARCHIVE_ITEM); } @Override public void onResume() { super.onResume(); resetZoomControlEnable(); } @Override public void onConfigurationChanged(Configuration conf) { super.onConfigurationChanged(conf); // Overrode this so we can catch keyboardHidden|orientation conf changes // do nothing prevents activity destruction. } private void toast(String msg) { Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); } private void longToast(String msg) { Toast.makeText(this, msg, Toast.LENGTH_LONG).show(); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.mainmenu, menu); menu.findItem(R.id.MENU_AUTHOR_LINK).setTitle(comicDef.getAuthorLinkText()); return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); menu.findItem(R.id.MENU_HOVER_TEXT).setVisible(comicDef.hasAltText()); menu.findItem(R.id.MENU_COMIC_LINK).setVisible(comicInfo.getLink() != null); menu.findItem(R.id.MENU_EXPLAIN).setVisible(provider.getExplainUrl(comicInfo) != null); return true; } public void loadAuthorLink() { Intent browser = new Intent(); browser.setAction(Intent.ACTION_VIEW); browser.addCategory(Intent.CATEGORY_BROWSABLE); browser.setData(comicDef.getAuthorLinkUrl()); startActivity(browser); } @Override public boolean onOptionsItemSelected(MenuItem item) { int itemId = item.getItemId(); if (itemId == R.id.MENU_HOVER_TEXT) { if (!"".equals(comicInfo.getAlt())) showDialog(DIALOG_SHOW_HOVER_TEXT); return true; } else if (itemId == R.id.MENU_COMIC_LINK) { if (comicInfo.getLink() != null) openComicLink(); return true; } else if (itemId == R.id.MENU_EXPLAIN) { explain(); return true; } else if (itemId == R.id.MENU_REFRESH) { loadComic(createComicUri(comicInfo.getId())); return true; } else if (itemId == R.id.MENU_RANDOM) { goToRandom(); return true; } else if (itemId == R.id.MENU_SHARE_LINK) { shareComicLink(); return true; } else if (itemId == R.id.MENU_SHARE_IMAGE) { shareComicImage(); return true; } else if (itemId == R.id.MENU_SETTINGS) { showSettings(); return true; } else if (itemId == R.id.MENU_GO_TO_FINAL) { goToFinal(); return true; } else if (itemId == R.id.MENU_GO_TO_NEXT) { goToNext(); return true; } else if (itemId == R.id.MENU_GO_TO_PREV) { goToPrev(); return true; } else if (itemId == R.id.MENU_GO_TO_FIRST) { goToFirst(); return true; } else if (itemId == R.id.MENU_WEBSITE) { launchWebsite(); return true; } else if (itemId == R.id.MENU_ARCHIVE) { showArchive(); return true; } else if (itemId == R.id.MENU_AUTHOR_LINK) { loadAuthorLink(); return true; } else if (itemId == R.id.MENU_DONATE) { donate(); return true; } else if (itemId == R.id.MENU_ABOUT) { showDialog(DIALOG_SHOW_ABOUT); return true; } else if (itemId == R.id.MENU_BOOKMARKS) { showBookmarks(); return true; } else if (itemId == R.id.MENU_SEARCH_TITLE) { showDialog(DIALOG_SEARCH_BY_TITLE); return true; } return false; } public String getVersion() { try { return getPackageManager().getPackageInfo(getPackageName(), 0).versionName; } catch (NameNotFoundException e) { return "???"; } } public void donate() { Intent browser = new Intent(); browser.setAction(Intent.ACTION_VIEW); browser.addCategory(Intent.CATEGORY_BROWSABLE); browser.setData(comicDef.getDonateUrl()); startActivity(browser); } public void developerWebsite() { Intent browser = new Intent(); browser.setAction(Intent.ACTION_VIEW); browser.addCategory(Intent.CATEGORY_BROWSABLE); browser.setData(comicDef.getDeveloperUrl()); startActivity(browser); } public void launchWebsite() { Intent browser = new Intent(); browser.setAction(Intent.ACTION_VIEW); browser.addCategory(Intent.CATEGORY_BROWSABLE); browser.setData(Uri.parse(comicInfo.getUrl())); startActivity(browser); } public void openComicLink() { Intent browser = new Intent(); browser.setAction(Intent.ACTION_VIEW); browser.addCategory(Intent.CATEGORY_BROWSABLE); browser.setData(comicInfo.getLink()); startActivity(browser); } public void showSettings() { Intent intent = new Intent(this, SettingsActivity.class); startActivity(intent); } public void shareComicLink() { Intent intent = new Intent(Intent.ACTION_SEND, null); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_TEXT, getCurrentComicUrl()); startActivity(Intent.createChooser(intent, "Share Link...")); } public String getCurrentComicUrl() { return comicInfo.getUrl(); } public void shareComicImage() { if (comicInfo == null || comicInfo.getImage() == null) { toast("No image loaded."); return; } new Utility.CancellableAsyncTaskWithProgressDialog<Uri, File>(getStringAppName()) { Throwable e; @Override protected File doInBackground(Uri... params) { try { File file = new File(getApplicationContext().getExternalCacheDir(), comicDef.getComicTitleAbbrev() + "-" + params[0].getLastPathSegment()); Utility.blockingSaveFile(file, params[0]); return file; } catch (InterruptedException ex) { return null; } catch (Throwable ex) { e = ex; return null; } } @Override protected void onPostExecute(File result) { super.onPostExecute(result); if (result != null && e == null) { try { Uri uri = Uri.fromFile(result); Intent intent = new Intent(Intent.ACTION_SEND, null); intent.setType(Utility.getContentType(uri)); intent.putExtra(Intent.EXTRA_STREAM, uri); startActivity(Intent.createChooser(intent, "Share image...")); return; } catch (MalformedURLException ex) { e = ex; } catch (IOException ex) { e = ex; } } e.printStackTrace(); failed("Couldn't save attachment: " + e); } }.start(this, "Saving image...", new Uri[] { comicInfo.getImage() }); } public void failed(final String reason) { runOnUiThread(new Runnable() { public void run() { if (failedDialog != null && !failedDialog.isShowing()) { if (!failedDialog.isShowing()) errors = ""; } if (!errors.equals("")) errors += "\n\n"; errors += reason; showDialog(DIALOG_FAILED); } }); } @Override protected Dialog onCreateDialog(int id) { // Set up variables for a dialog and a dialog builder. Only need one of each. Dialog dialog = null; AlertDialog.Builder builder = null; // Determine the type of dialog based on the integer passed. These are defined in constants // at the top of the class. switch (id) { case DIALOG_SHOW_HOVER_TEXT: //Build and show the Hover Text dialog builder = new AlertDialog.Builder(ComicViewerActivity.this); builder.setMessage(comicInfo.getAlt()); builder.setPositiveButton("Open Link...", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { openComicLink(); } }); builder.setNegativeButton("Close", null); dialog = builder.create(); builder = null; break; case DIALOG_SHOW_ABOUT: //Build and show the About dialog builder = new AlertDialog.Builder(this); builder.setTitle(getStringAppName()); builder.setIcon(android.R.drawable.ic_menu_info_details); builder.setNegativeButton(android.R.string.ok, null); builder.setNeutralButton("Donate", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { donate(); } }); builder.setPositiveButton("Developer Website", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { developerWebsite(); } }); View v = LayoutInflater.from(this).inflate(R.layout.about, null); TextView tv = (TextView) v.findViewById(R.id.aboutText); tv.setText(String.format(getStringAboutText(), getVersion())); builder.setView(v); dialog = builder.create(); builder = null; v = null; tv = null; break; case DIALOG_SEARCH_BY_TITLE: //Build and show the Search By Title dialog builder = new AlertDialog.Builder(this); LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); View layout = inflater.inflate(R.layout.search_dlg, (ViewGroup) findViewById(R.id.search_dlg)); final EditText input = (EditText) layout.findViewById(R.id.search_dlg_edit_box); builder.setTitle("Search by Title"); builder.setIcon(android.R.drawable.ic_menu_search); builder.setView(layout); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { String query = input.getText().toString(); Uri uri = comicDef.getArchiveUrl(); Intent i = new Intent(ComicViewerActivity.this, getArchiveActivityClass()); i.setAction(Intent.ACTION_VIEW); i.setData(uri); i.putExtra(getPackageName() + "LoadType", ArchiveActivity.LoadType.SEARCH_TITLE); i.putExtra(getPackageName() + "query", query); startActivityForResult(i, PICK_ARCHIVE_ITEM); } }); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); dialog = builder.create(); builder = null; break; case DIALOG_FAILED: // Probably doesn't need its own builder, but because this is a special case // dialog I gave it one. AlertDialog.Builder adb = new AlertDialog.Builder(this); adb.setTitle("Error"); adb.setIcon(android.R.drawable.ic_dialog_alert); adb.setNeutralButton(android.R.string.ok, null); //Set failedDialog to our dialog so we can dismiss //it manually failedDialog = adb.create(); failedDialog.setMessage(errors); dialog = failedDialog; break; default: dialog = null; } return dialog; } @Override protected void onPrepareDialog(int id, Dialog dialog) { // Determine the type of dialog based on the integer passed. These are defined in constants // at the top of the class. switch (id) { case DIALOG_SHOW_HOVER_TEXT: //Get an alertdialog so we can edit it. AlertDialog adh = (AlertDialog) dialog; adh.setMessage(comicInfo.getAlt()); adh.getButton(AlertDialog.BUTTON_POSITIVE) .setVisibility(comicInfo.getLink() != null ? Button.VISIBLE : Button.GONE); break; case DIALOG_FAILED: //Get the alertdialog for the failedDialog AlertDialog adf = (AlertDialog) dialog; adf.setMessage(errors); //Set failedDialog to our dialog so we can dismiss //it manually failedDialog = adf; break; case DIALOG_SEARCH_BY_TITLE: // Clear the text box AlertDialog ads = (AlertDialog) dialog; ((EditText) ads.findViewById(R.id.search_dlg_edit_box)).setText(""); break; default: break; } super.onPrepareDialog(id, dialog); } /* Comic-loading implementation using AsyncTasks and IComicProvider * interface follows. * * goTo* methods must be called in UI thread * load* methods must be called in UI thread * create* methods can be called anywhere * fetch* methods must be called in a background thread */ public void goToFirst() { loadComic(createComicUri(provider.getFirstId())); } public void goToPrev() { loadComic(createComicUri(comicInfo.getPrevId())); } public void goToNext() { loadComic(createComicUri(comicInfo.getNextId())); } public void goToFinal() { loadComic(provider.getFinalComicUrl()); } public Uri createComicUri(String id) { return provider.createComicUrl(id); } public void goToRandom() { /* Can't just choose a random number and go to the comic, because if * the user cancelled the comic loading at start, we won't know how * many comics there are! */ new Utility.CancellableAsyncTaskWithProgressDialog<Object, Uri>(getStringAppName()) { @Override protected Uri doInBackground(Object... params) { try { return fetchRandomUri(); } catch (Exception e) { e.printStackTrace(); return null; } } @Override protected void onPostExecute(Uri result) { super.onPostExecute(result); if (result != null) loadComic(result); else toast("Failed to get random comic"); } }.start(this, "Randomizing...", new Object[] { null }); } public Uri fetchRandomUri() throws Exception { return provider.fetchRandomComicUrl(); } private class ComicInfoOrError { public IComicInfo comicInfo = null; public Throwable e = null; public ComicInfoOrError(Throwable e) { this.e = e; } public ComicInfoOrError(IComicInfo d) { comicInfo = d; } } public void explain() { final IComicInfo comic = comicInfo; new AsyncTask<Object, Integer, Integer>() { @Override protected Integer doInBackground(Object... params) { try { URL url = new URL(provider.getExplainUrl(comic).toString()); HttpURLConnection http = (HttpURLConnection) url.openConnection(); return http.getResponseCode(); } catch (IOException e) { e.printStackTrace(); return null; } } @Override protected void onPostExecute(Integer result) { super.onPostExecute(result); if (result == null || result != 200) { toast("This comic has no user-supplied explanation."); } } }.execute(new Object[] {}); Intent browser = new Intent(); browser.setAction(Intent.ACTION_VIEW); browser.addCategory(Intent.CATEGORY_BROWSABLE); browser.setData(provider.getExplainUrl(comic)); startActivity(browser); } public void loadComic(final Uri uri) { new Utility.CancellableAsyncTaskWithProgressDialog<Object, ComicInfoOrError>(getStringAppName()) { @Override protected ComicInfoOrError doInBackground(Object... params) { try { return new ComicInfoOrError(fetchComicInfo(uri)); } catch (Throwable e) { return new ComicInfoOrError(e); } } @Override protected void onPostExecute(ComicInfoOrError result) { super.onPostExecute(result); if (result.comicInfo != null) { comicInfo = result.comicInfo; title.setText(comicInfo.getTitle()); comicIdSel.setText(comicInfo.getId()); refreshBookmarkBtn(); setLastReadComic(comicInfo.getId()); loadComicImage(comicInfo.getImage()); if (comicInfo.getLink() != null) { longToast("This comic has a link or larger image attached.\n" + "Tap the image and select 'Open Link' to see it."); } } else { result.e.printStackTrace(); /* Syntaxhack pattern match against type of result.e: */ try { throw result.e; } catch (MalformedURLException e) { failed("Malformed URL: " + e); } catch (FileNotFoundException e) { // Comic doesn't exist. Probably went beyond the final // or before the first. toast("Comic doesn't exist"); } catch (IOException e) { failed("IO error: " + e); } catch (InterruptedException e) { // Do nothing. Loading was cancelled. } catch (JSONException e) { failed("Data returned from website didn't match expected format"); } catch (Throwable e) { failed(e.toString()); } } } }.start(this, "Loading comic...", new Object[] { null }); } public IComicInfo fetchComicInfo(Uri uri) throws Exception { IComicInfo ci = provider.fetchComicInfo(uri); ci.setBookmarked(BookmarksHelper.isBookmarked(this, ci.getId())); return ci; } public void loadComicImage(Uri uri) { webview.clearView(); final ProgressDialog pd = ProgressDialog.show(this, getStringAppName(), "Loading comic image...", false, true, new OnCancelListener() { public void onCancel(DialogInterface dialog) { webview.stopLoading(); webview.requestFocus(); } }); pd.setProgress(0); webview.setWebViewClient(new WebViewClient() { @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); pd.dismiss(); webview.requestFocus(); } }); webview.setWebChromeClient(new WebChromeClient() { @Override public void onProgressChanged(WebView view, int newProgress) { super.onProgressChanged(view, newProgress); pd.setProgress(newProgress * 100); } }); if ("".equals(uri.toString())) { failed("Couldn't identify image in post"); webview.loadUrl("about:blank"); } else { webview.loadUrl(uri.toString()); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == PICK_ARCHIVE_ITEM) { if (resultCode == RESULT_OK) { // This means an archive item was picked. Display it. loadComic(createComicUri(data.getStringExtra(getPackageName() + "comicId"))); } } } }