diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 62f0500991fbd9b07a761ac44558a9080a0e44c3..0f79f219d31042061d31d70f67909450a65caa60 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -29,6 +29,7 @@ </activity> <activity android:name=".activity.NewsActivity" android:label="I2P News" + android:configChanges="orientation|keyboardHidden" android.theme="@android:style/Theme.NoTitleBar" > </activity> <activity android:name=".activity.TextResourceActivity" @@ -41,6 +42,7 @@ </activity> <activity android:name=".activity.WebActivity" android:label="I2P Web Browser" + android:configChanges="orientation|keyboardHidden" android.theme="@android:style/Theme.NoTitleBar" > <intent-filter> <action android:name="android.intent.action.VIEW" /> diff --git a/res/layout/main.xml b/res/layout/main.xml index 1375a464ee51d1b30e307144369580657b8786e4..eda98b9944aa04ddd1fcb9f77c01d7fc7e6227dd 100644 --- a/res/layout/main.xml +++ b/res/layout/main.xml @@ -102,19 +102,19 @@ android:id="@+id/router_start_button" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Start router" + android:text="Start Router" /> <Button android:id="@+id/router_stop_button" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Stop router" + android:text="OLD STOP" /> <Button android:id="@+id/router_quit_button" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="QUIT router" + android:text="Stop Router" /> </LinearLayout> <TextView diff --git a/src/net/i2p/android/apps/EepGetFetcher.java b/src/net/i2p/android/apps/EepGetFetcher.java index 8b64836c10d5cdfa4f7aeab0a75b7d623a3c18f1..6c8ee6a33cc9ca1092d26f2d989f9949e7b4cf8d 100644 --- a/src/net/i2p/android/apps/EepGetFetcher.java +++ b/src/net/i2p/android/apps/EepGetFetcher.java @@ -32,7 +32,8 @@ public class EepGetFetcher implements EepGet.StatusListener { /** * Writes to temp file, call getData() - * to get the data as a String + * to get the data as a String. + * Temp file sticks around forever. */ public EepGetFetcher(String url) { _context = I2PAppContext.getGlobalContext(); @@ -50,7 +51,7 @@ public class EepGetFetcher implements EepGet.StatusListener { /** * Writes to output stream */ - public EepGetFetcher(String url, OutputStream out) { + public EepGetFetcher(String url, OutputStream out, boolean writeErrorToStream) { _context = I2PAppContext.getGlobalContext(); _log = _context.logManager().getLog(EepGetFetcher.class); _url = url; @@ -58,7 +59,8 @@ public class EepGetFetcher implements EepGet.StatusListener { _eepget = new EepGet(_context, true, "localhost", 4444, 0, -1, MAX_LEN, null, out, url, true, null, null, null); - //_eepget.setWriteErrorToOutput(); + if (writeErrorToStream) + _eepget.setWriteErrorToOutput(); } public void addStatusListener(EepGet.StatusListener l) { @@ -100,6 +102,13 @@ public class EepGetFetcher implements EepGet.StatusListener { return rv; } + /** + * @return -1 if nothing back from server + */ + public int getStatusCode() { + return _eepget.getStatusCode(); + } + /** * Only for the constructor without the output stream * Only call ONCE! diff --git a/src/net/i2p/android/router/activity/I2PWebViewClient.java b/src/net/i2p/android/router/activity/I2PWebViewClient.java index f7981cf945f1bfbd854ba678f79fe46655389442..24c73d5a9b8abf8485e1601a06d58b25cb8019b9 100644 --- a/src/net/i2p/android/router/activity/I2PWebViewClient.java +++ b/src/net/i2p/android/router/activity/I2PWebViewClient.java @@ -12,13 +12,17 @@ import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.Toast; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import net.i2p.android.apps.EepGetFetcher; import net.i2p.android.router.provider.CacheProvider; import net.i2p.android.router.util.AppCache; import net.i2p.android.router.util.Util; +import net.i2p.data.DataHelper; import net.i2p.util.EepGet; class I2PWebViewClient extends WebViewClient { @@ -26,9 +30,11 @@ class I2PWebViewClient extends WebViewClient { private BGLoad _lastTask; // TODO add some inline style + private static final String CONTENT = "content"; private static final String HEADER = "<html><head></head><body>"; private static final String FOOTER = "</body></html>"; - private static final String ERROR_EEPSITE = HEADER + "Sorry, eepsites not yet supported" + FOOTER; + private static final String ERROR_URL = "<p>Unable to load URL: "; + private static final String ERROR_ROUTER = "<p>Your router does not appear to be up.</p>"; public I2PWebViewClient(Context ctx) { super(); @@ -47,7 +53,7 @@ class I2PWebViewClient extends WebViewClient { } s = s.toLowerCase(); if (!(s.equals("http") || s.equals("https") || - s.equals("content"))) { + s.equals(CONTENT))) { Util.e("Not loading URL " + url); return false; } @@ -75,16 +81,22 @@ class I2PWebViewClient extends WebViewClient { int hash = url.indexOf("#"); if (hash > 0) url = url.substring(0, hash); - view.getSettings().setLoadsImagesAutomatically(false); + view.getSettings().setLoadsImagesAutomatically(true); ///////// API 8 // Otherwise hangs waiting for CSS - view.getSettings().setBlockNetworkLoads(true); - //view.loadData(ERROR_EEPSITE, "text/html", "UTF-8"); + view.getSettings().setBlockNetworkLoads(false); BGLoad task = new BackgroundEepLoad(view, h); _lastTask = task; task.execute(url); } else { - if (s.equals("content")) { + if (s.equals(CONTENT)) { + if (h.equals(CacheProvider.AUTHORITY)) { + if (!url.startsWith(CacheProvider.CONTENT_URI.toString())) + Util.e("Content URI bad nonce, FIXME: " + url); + } else { + Util.e("Content URI but not for us?? " + url); + } + // canonicalize to append query to path // because the resolver doesn't send a query to the provider Uri canon = CacheProvider.getContentUri(uri); @@ -143,6 +155,20 @@ class I2PWebViewClient extends WebViewClient { } } + /** + * This should always be a content url + */ + void deleteCurrentPageCache(WebView view) { + String url = view.getUrl(); + Uri uri = Uri.parse(url); + if (CONTENT.equals(uri.getScheme())) { + // this actually only deletes the row in the provider, + // not the actual file, but it will be overwritten in the reload. + Util.e("clearing provider entry for current page " + url); + view.getContext().getContentResolver().delete(uri, null, null); + } + } + private abstract static class BGLoad extends AsyncTask<String, Integer, Integer> implements DialogInterface.OnCancelListener { protected final WebView _view; protected ProgressDialog _dialog; @@ -223,7 +249,8 @@ class I2PWebViewClient extends WebViewClient { protected Integer doInBackground(String... urls) { String url = urls[0]; Uri uri = Uri.parse(url); - if (AppCache.getInstance(_view.getContext()).getCacheFile(uri).exists()) { + File cacheFile = AppCache.getInstance(_view.getContext()).getCacheFile(uri); + if (cacheFile.exists()) { Uri resUri = AppCache.getInstance(_view.getContext()).getCacheUri(uri); Util.e("Loading " + url + " from resource cache " + resUri); _view.getSettings().setLoadsImagesAutomatically(true); @@ -234,56 +261,82 @@ class I2PWebViewClient extends WebViewClient { // CalledFromWrongThreadException cancel(false); } - return Integer.valueOf(0); + // 1 means show the cache toast message + return Integer.valueOf(1); } publishProgress(Integer.valueOf(-1)); - EepGetFetcher fetcher = new EepGetFetcher(url); - fetcher.addStatusListener(this); - boolean success = fetcher.fetch(); - if (isCancelled()) { - Util.e("Fetch cancelled for " + url); - return Integer.valueOf(0); - } - if (!success) - Util.e("Fetch failed for " + url); - String t = fetcher.getContentType(); - String d = fetcher.getData(); - int len = d.length(); - // http://stackoverflow.com/questions/3961589/android-webview-and-loaddata - if (success && t.startsWith("text/html") && !d.startsWith("<?xml")) - d = XML_HEADER + d; - String e = fetcher.getEncoding(); - Util.e("Len: " + len + " type: \"" + t + "\" encoding: \"" + e + '"'); - if (isCancelled()) { - Util.e("Fetch cancelled for " + url); - return Integer.valueOf(0); - } - String history = url; - if (success) { - OutputStream out = null; - try { - out = AppCache.getInstance(_view.getContext()).createCacheFile(uri); - out.write(d.getBytes(e)); + //EepGetFetcher fetcher = new EepGetFetcher(url); + OutputStream out = null; + try { + out = AppCache.getInstance(_view.getContext()).createCacheFile(uri); + // write error to stream + EepGetFetcher fetcher = new EepGetFetcher(url, out, true); + fetcher.addStatusListener(this); + boolean success = fetcher.fetch(); + if (isCancelled()) { + Util.e("Fetch cancelled for " + url); + return Integer.valueOf(0); + } + try { out.close(); } catch (IOException ioe) {} + if (success) { + // store in cache, get content URL, and load that way Uri content = AppCache.getInstance(_view.getContext()).addCacheFile(uri); - if (content != null) - history = content.toString(); - Util.e("Stored cache in " + history); - } catch (Exception ex) { + if (content != null) { + Util.e("Stored cache in " + content); + } else { + AppCache.getInstance(_view.getContext()).removeCacheFile(uri); + Util.e("cache create error"); + return Integer.valueOf(0); + } + Util.e("loading data, base URL: " + uri + " content URL: " + content); + try { + _view.loadUrl(content.toString()); + } catch (Exception exc) { + // CalledFromWrongThreadException + cancel(false); + } + Util.e("Fetch failed for " + url); + } else { + // Load the error message in as a string, delete the file + String t = fetcher.getContentType(); + String e = fetcher.getEncoding(); + String msg; + int statusCode = fetcher.getStatusCode(); + if (statusCode < 0) { + msg = HEADER + ERROR_URL + "<a href=\"" + url + "\">" + url + + "</a></p>" + ERROR_ROUTER + FOOTER; + } else if (cacheFile.length() <= 0) { + msg = HEADER + ERROR_URL + "<a href=\"" + url + "\">" + url + + "</a> No data returned, error code: " + statusCode + + "</p>" + FOOTER; + } else { + InputStream fis = null; + try { + fis = new FileInputStream(cacheFile); + byte[] data = new byte[(int) cacheFile.length()]; + DataHelper.read(fis, data); + msg = new String(data, e); + } catch (IOException ioe) { + Util.e("WVC", ioe); + msg = HEADER + "I/O error" + FOOTER; + } finally { + if (fis != null) try { fis.close(); } catch (IOException ioe) {} + } + } AppCache.getInstance(_view.getContext()).removeCacheFile(uri); - Util.e("cache create error", ex); - } finally { - if (out != null) try { out.close(); } catch (IOException ioe) {} + try { + Util.e("loading error data URL: " + url); + _view.loadDataWithBaseURL(url, msg, t, e, url); + } catch (Exception exc) { + // CalledFromWrongThreadException + cancel(false); + } } - } else { - history = url; - } - try { - Util.e("loading data, base URL: " + url + " history URL: " + history); - _view.loadDataWithBaseURL(url, d, t, e, history); - } catch (Exception exc) { - // CalledFromWrongThreadException - cancel(false); + } catch (IOException ioe) { + Util.e("IOE for " + url, ioe); + } finally { + if (out != null) try { out.close(); } catch (IOException ioe) {} } return Integer.valueOf(0); } @@ -316,6 +369,16 @@ class I2PWebViewClient extends WebViewClient { } } + @Override + protected void onPostExecute(Integer result) { + if (result.equals(Integer.valueOf(1))) { + Toast toast = Toast.makeText(_view.getContext(), "Loading from cache, click settings to reload", Toast.LENGTH_SHORT); + toast.setGravity(Gravity.CENTER, 0, 0); + toast.show(); + } + super.onPostExecute(result); + } + // EepGet callbacks public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {} diff --git a/src/net/i2p/android/router/activity/MainActivity.java b/src/net/i2p/android/router/activity/MainActivity.java index 102afd11fd66e4c950ceeb0728b7a94e5094c8a3..d5242ea8f3a43795812e74b93bdcf3bd60f2b13a 100644 --- a/src/net/i2p/android/router/activity/MainActivity.java +++ b/src/net/i2p/android/router/activity/MainActivity.java @@ -244,8 +244,9 @@ public class MainActivity extends I2PActivityBase { start.setVisibility(showStart ? View.VISIBLE : View.INVISIBLE); boolean showStop = svc != null && _isBound && svc.canManualStop(); + // Old stop but leave in memory. Always hide for now. Button stop = (Button) findViewById(R.id.router_stop_button); - stop.setVisibility(showStop ? View.VISIBLE : View.INVISIBLE); + stop.setVisibility( /* showStop ? View.VISIBLE : */ View.INVISIBLE); Button quit = (Button) findViewById(R.id.router_quit_button); quit.setVisibility(showStop ? View.VISIBLE : View.INVISIBLE); diff --git a/src/net/i2p/android/router/activity/WebActivity.java b/src/net/i2p/android/router/activity/WebActivity.java index 98a2fa5113155196515d03ea2770c18d3a3bb7fb..4818f64bbd7f02e62a78b2cccfa677e6125d93a4 100644 --- a/src/net/i2p/android/router/activity/WebActivity.java +++ b/src/net/i2p/android/router/activity/WebActivity.java @@ -5,6 +5,7 @@ import android.content.res.Resources; import android.net.Uri; import android.os.Bundle; import android.view.KeyEvent; +import android.view.MenuItem; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.TextView; @@ -91,4 +92,22 @@ public class WebActivity extends I2PActivityBase { } return super.onKeyDown(keyCode, event); } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + WebView wv = (WebView) findViewById(R.id.browser_webview); + switch (item.getItemId()) { + case R.id.menu_reload: + _wvClient.cancelAll(); + wv.stopLoading(); + _wvClient.deleteCurrentPageCache(wv); + // should go through the WVC instead?? + wv.reload(); + return true; + + default: + return super.onOptionsItemSelected(item); + } + } + } diff --git a/src/net/i2p/android/router/provider/CacheProvider.java b/src/net/i2p/android/router/provider/CacheProvider.java index dc34887daa49257cf2c89e464164c47904202c22..8e64fd575c3017e26564f87d49940ed5af7e315d 100644 --- a/src/net/i2p/android/router/provider/CacheProvider.java +++ b/src/net/i2p/android/router/provider/CacheProvider.java @@ -47,7 +47,7 @@ public class CacheProvider extends ContentProvider { //private static final String NONCE = Integer.toString(Math.abs((new java.util.Random()).nextInt())); private static final String NONCE = "0"; private static final String SCHEME = "content"; - private static final String AUTHORITY = "net.i2p.android.router"; + public static final String AUTHORITY = "net.i2p.android.router"; /** includes the nonce */ public static final Uri CONTENT_URI = Uri.parse(SCHEME + "://" + AUTHORITY + '/' + NONCE); /** the database key */ @@ -177,7 +177,7 @@ public class CacheProvider extends ContentProvider { throw new FileNotFoundException(ioe.toString()); } // in this constructor we don't use the error output, for now - EepGetFetcher fetcher = new EepGetFetcher(uri.toString(), out); + EepGetFetcher fetcher = new EepGetFetcher(uri.toString(), out, false); boolean success = fetcher.fetch(); if (success) { File file = cache.getCacheFile(uri);