| Index: src/org/adblockplus/android/AdblockPlus.java | 
| =================================================================== | 
| new file mode 100755 | 
| --- /dev/null | 
| +++ b/src/org/adblockplus/android/AdblockPlus.java | 
| @@ -0,0 +1,1013 @@ | 
| +package org.adblockplus.android; | 
| + | 
| +import java.io.BufferedReader; | 
| +import java.io.File; | 
| +import java.io.FileInputStream; | 
| +import java.io.FileNotFoundException; | 
| +import java.io.FileOutputStream; | 
| +import java.io.IOException; | 
| +import java.io.InputStreamReader; | 
| +import java.net.HttpURLConnection; | 
| +import java.net.URL; | 
| +import java.util.ArrayList; | 
| +import java.util.Calendar; | 
| +import java.util.LinkedList; | 
| +import java.util.List; | 
| +import java.util.Locale; | 
| +import java.util.Map; | 
| +import java.util.TimeZone; | 
| +import java.util.concurrent.Callable; | 
| +import java.util.concurrent.ExecutionException; | 
| +import java.util.concurrent.Future; | 
| +import java.util.concurrent.FutureTask; | 
| + | 
| +import javax.xml.parsers.ParserConfigurationException; | 
| +import javax.xml.parsers.SAXParser; | 
| +import javax.xml.parsers.SAXParserFactory; | 
| + | 
| +import org.adblockplus.android.updater.AlarmReceiver; | 
| +import org.apache.commons.lang.StringEscapeUtils; | 
| +import org.apache.commons.lang.StringUtils; | 
| +import org.json.JSONException; | 
| +import org.json.JSONObject; | 
| +import org.xml.sax.SAXException; | 
| + | 
| +import android.app.AlarmManager; | 
| +import android.app.Application; | 
| +import android.app.PendingIntent; | 
| +import android.content.Context; | 
| +import android.content.Intent; | 
| +import android.content.SharedPreferences; | 
| +import android.content.pm.PackageManager; | 
| +import android.content.pm.PackageManager.NameNotFoundException; | 
| +import android.content.res.AssetManager; | 
| +import android.net.ConnectivityManager; | 
| +import android.net.NetworkInfo; | 
| +import android.os.AsyncTask; | 
| +import android.os.Bundle; | 
| +import android.os.Handler; | 
| +import android.os.Message; | 
| +import android.os.SystemClock; | 
| +import android.preference.PreferenceManager; | 
| +import android.util.Log; | 
| +import android.widget.Toast; | 
| + | 
| +public class AdblockPlus extends Application | 
| +{ | 
| + private final static String TAG = "Application"; | 
| + | 
| + private final static int MSG_TOAST = 1; | 
| + | 
| + /** | 
| + * Broadcasted when subscription status changes. | 
| + */ | 
| + public final static String BROADCAST_SUBSCRIPTION_STATUS = "org.adblockplus.android.subscription.status"; | 
| + /** | 
| + * Broadcasted when filter match check is performed. | 
| + */ | 
| + public final static String BROADCAST_FILTER_MATCHES = "org.adblockplus.android.filter.matches"; | 
| + | 
| + private List<Subscription> subscriptions; | 
| + | 
| + private JSThread js; | 
| + | 
| + /** | 
| + * Indicates interactive mode (used to listen for subscription status | 
| + * changes). | 
| + */ | 
| + private boolean interactive = false; | 
| + | 
| + private boolean generateCrashReport = false; | 
| + | 
| + private static AdblockPlus myself; | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Nit: Isn't "instance" a more typical name for a si
 
Andrey Novikov
2012/10/30 07:53:22
Done.
 
 | 
| + | 
| + /** | 
| + * Returns pointer to itself (singleton pattern). | 
| + */ | 
| + public static AdblockPlus getApplication() | 
| + { | 
| + return myself; | 
| + } | 
| + | 
| + /** | 
| + * Checks if device has a WiFi connection available. | 
| + */ | 
| + public static boolean isWiFiConnected(Context context) | 
| + { | 
| + ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); | 
| + NetworkInfo networkInfo = null; | 
| + if (connectivityManager != null) | 
| + { | 
| + networkInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); | 
| + } | 
| + return networkInfo == null ? false : networkInfo.isConnected(); | 
| + } | 
| + | 
| + /** | 
| + * Checks if application can write to external storage. | 
| + */ | 
| + public boolean checkWriteExternalPermission() | 
| + { | 
| + | 
| + String permission = "android.permission.WRITE_EXTERNAL_STORAGE"; | 
| + int res = checkCallingOrSelfPermission(permission); | 
| + return res == PackageManager.PERMISSION_GRANTED; | 
| + } | 
| + | 
| + /** | 
| + * Returns list of known subscriptions. | 
| + */ | 
| + public List<Subscription> getSubscriptions() | 
| + { | 
| + if (subscriptions == null) | 
| + { | 
| + subscriptions = new ArrayList<Subscription>(); | 
| + | 
| + SAXParserFactory factory = SAXParserFactory.newInstance(); | 
| + SAXParser parser; | 
| + try | 
| + { | 
| + parser = factory.newSAXParser(); | 
| + parser.parse(getAssets().open("subscriptions.xml"), new SubscriptionParser(subscriptions)); | 
| + } | 
| + catch (ParserConfigurationException e) | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Can we log the following exceptions as an error in
 
Andrey Novikov
2012/10/30 07:53:22
printStackTrace does pretty much the same, I use i
 
Felix Dahlke
2012/10/30 08:08:42
I think Log.e() is the canonical way to log except
 
 | 
| + { | 
| + // TODO Auto-generated catch block | 
| + e.printStackTrace(); | 
| + } | 
| + catch (SAXException e) | 
| + { | 
| + // TODO Auto-generated catch block | 
| + e.printStackTrace(); | 
| + } | 
| + catch (IOException e) | 
| + { | 
| + // TODO Auto-generated catch block | 
| + e.printStackTrace(); | 
| + } | 
| + } | 
| + return subscriptions; | 
| + } | 
| + | 
| + /** | 
| + * Returns subscription information. | 
| + * | 
| + * @param url | 
| + * subscription url | 
| + */ | 
| + public Subscription getSubscription(String url) | 
| + { | 
| + List<Subscription> subscriptions = getSubscriptions(); | 
| + | 
| + for (Subscription subscription : subscriptions) | 
| + { | 
| + if (subscription.url.equals(url)) | 
| + return subscription; | 
| + } | 
| + return null; | 
| + } | 
| + | 
| + /** | 
| + * Adds provided subscription and removes previous subscriptions if any. | 
| + * | 
| + * @param subscription | 
| + * Subscription to add | 
| + */ | 
| + public void setSubscription(Subscription subscription) | 
| + { | 
| + /* | 
| + * Subscription test = new Subscription(); | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Can we remove this commented code? It's in source
 
Andrey Novikov
2012/10/30 07:53:22
Done.
 
 | 
| + * test.url = | 
| + * "https://easylist-downloads.adblockplus.org/exceptionrules.txt"; | 
| + * test.title = "Test"; | 
| + * test.homepage = "https://easylist-downloads.adblockplus.org/"; | 
| + * selectedItem = test; | 
| + */ | 
| + | 
| + if (subscription != null) | 
| + { | 
| + final JSONObject jsonSub = new JSONObject(); | 
| + try | 
| + { | 
| + jsonSub.put("url", subscription.url); | 
| + jsonSub.put("title", subscription.title); | 
| + jsonSub.put("homepage", subscription.homepage); | 
| + js.execute(new Runnable() { | 
| + @Override | 
| + public void run() | 
| + { | 
| + js.evaluate("clearSubscriptions()"); | 
| + js.evaluate("addSubscription(\"" + StringEscapeUtils.escapeJavaScript(jsonSub.toString()) + "\")"); | 
| + } | 
| + }); | 
| + } | 
| + catch (JSONException e) | 
| + { | 
| + // TODO Auto-generated catch block | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Can we log this exception as an error instead of u
 
Andrey Novikov
2012/10/30 09:24:17
Done.
 
 | 
| + e.printStackTrace(); | 
| + } | 
| + } | 
| + } | 
| + | 
| + /** | 
| + * Forces subscriptions refresh. | 
| + */ | 
| + public void refreshSubscription() | 
| + { | 
| + js.execute(new Runnable() { | 
| + @Override | 
| + public void run() | 
| + { | 
| + js.evaluate("refreshSubscriptions()"); | 
| + } | 
| + }); | 
| + } | 
| + | 
| + /** | 
| + * Selects which subscription to offer for the first time. | 
| + * | 
| + * @return offered subscription | 
| + */ | 
| + public Subscription offerSubscription() | 
| + { | 
| + Subscription selectedItem = null; | 
| + String selectedPrefix = null; | 
| + int matchCount = 0; | 
| + for (Subscription subscription : getSubscriptions()) | 
| + { | 
| + if (selectedItem == null) | 
| + selectedItem = subscription; | 
| + | 
| + String prefix = checkLocalePrefixMatch(subscription.prefixes); | 
| + if (prefix != null) | 
| + { | 
| + if (selectedPrefix == null || selectedPrefix.length() < prefix.length()) | 
| + { | 
| + selectedItem = subscription; | 
| + selectedPrefix = prefix; | 
| + matchCount = 1; | 
| + } | 
| + else if (selectedPrefix != null && selectedPrefix.length() == prefix.length()) | 
| + { | 
| + matchCount++; | 
| + | 
| + // If multiple items have a matching prefix of the | 
| + // same length select one of the items randomly, | 
| + // probability should be the same for all items. | 
| + // So we replace the previous match here with | 
| + // probability 1/N (N being the number of matches). | 
| + if (Math.random() * matchCount < 1) | 
| + { | 
| + selectedItem = subscription; | 
| + selectedPrefix = prefix; | 
| + } | 
| + } | 
| + } | 
| + } | 
| + return selectedItem; | 
| + } | 
| + | 
| + /** | 
| + * Verifies that subscriptions are loaded and returns flag of subscription | 
| + * presence. | 
| + * | 
| + * @return true if at least one subscription is present and downloaded | 
| + */ | 
| + public boolean verifySubscriptions() | 
| + { | 
| + Future<Boolean> future = js.submit(new Callable<Boolean>() { | 
| + @Override | 
| + public Boolean call() throws Exception | 
| + { | 
| + Boolean result = (Boolean) js.evaluate("verifySubscriptions()"); | 
| + return result; | 
| + } | 
| + }); | 
| + try | 
| + { | 
| + return future.get().booleanValue(); | 
| + } | 
| + catch (InterruptedException e) | 
| + { | 
| + // TODO Auto-generated catch block | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Like above, log an error instead?
 
Andrey Novikov
2012/10/30 09:24:17
Done.
 
 | 
| + e.printStackTrace(); | 
| + } | 
| + catch (ExecutionException e) | 
| + { | 
| + // TODO Auto-generated catch block | 
| + e.printStackTrace(); | 
| + } | 
| + return false; | 
| + } | 
| + | 
| + /** | 
| + * Returns ElemHide selectors for domain. | 
| + * | 
| + * @return ready to use HTML element with CSS selectors | 
| + */ | 
| + public String getSelectorsForDomain(final String domain) | 
| + { | 
| + Future<String> future = js.submit(new Callable<String>() { | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Is the future really necessary here? It seems you
 
Andrey Novikov
2012/10/30 07:53:22
No, I can't. JS runs in separate asynchronous thre
 
Felix Dahlke
2012/10/30 08:08:42
Oh, I see.
 
 | 
| + @Override | 
| + public String call() throws Exception | 
| + { | 
| + String result = (String) js.evaluate("ElemHide.getSelectorsForDomain('" + domain + "')"); | 
| + return result; | 
| + } | 
| + }); | 
| + try | 
| + { | 
| + return future.get(); | 
| + } | 
| + catch (InterruptedException e) | 
| + { | 
| + // TODO Auto-generated catch block | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Like above, error logging.
 
Andrey Novikov
2012/10/30 09:24:17
Done.
 
 | 
| + e.printStackTrace(); | 
| + } | 
| + catch (ExecutionException e) | 
| + { | 
| + // TODO Auto-generated catch block | 
| + e.printStackTrace(); | 
| + } | 
| + return null; | 
| + } | 
| + | 
| + private class MatchesCallable implements Callable<Boolean> | 
| + { | 
| + private String url; | 
| + private String query; | 
| + private String reqHost; | 
| + private String refHost; | 
| + private String accept; | 
| + | 
| + MatchesCallable(String url, String query, String reqHost, String refHost, String accept) | 
| + { | 
| + this.url = url; | 
| + this.query = query; | 
| + this.reqHost = reqHost != null ? reqHost : ""; | 
| + this.refHost = refHost != null ? refHost : ""; | 
| + this.accept = accept != null ? accept : ""; | 
| + } | 
| + | 
| + @Override | 
| + public Boolean call() throws Exception | 
| + { | 
| + Boolean result = (Boolean) js.evaluate("matchesAny('" + url + "', '" + query + "', '" + reqHost + "', '" + refHost + "', '" + accept + "');"); | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Nit: You could just return this directly
 
Andrey Novikov
2012/10/30 07:53:22
See above.
 
 | 
| + return result; | 
| + } | 
| + } | 
| + | 
| + /** | 
| + * Checks if filters match request parameters. | 
| + * | 
| + * @param url | 
| + * Request URL | 
| + * @param query | 
| + * Request query string | 
| + * @param reqHost | 
| + * Request host | 
| + * @param refHost | 
| + * Request referrer header | 
| + * @param accept | 
| + * Request accept header | 
| + * @return true if matched filter was found | 
| + * @throws Exception | 
| + */ | 
| + public boolean matches(String url, String query, String reqHost, String refHost, String accept) throws Exception | 
| + { | 
| + Callable<Boolean> callable = new MatchesCallable(url, query, reqHost, refHost, accept); | 
| + Future<Boolean> future = js.submit(callable); | 
| + boolean matches = future.get().booleanValue(); | 
| + sendBroadcast(new Intent(BROADCAST_FILTER_MATCHES).putExtra("url", url).putExtra("matches", matches)); | 
| + return matches; | 
| + } | 
| + | 
| + /** | 
| + * Notifies JS code that application entered interactive mode. | 
| + */ | 
| + public void startInteractive() | 
| + { | 
| + js.execute(new Runnable() { | 
| + @Override | 
| + public void run() | 
| + { | 
| + js.evaluate("startInteractive()"); | 
| + } | 
| + }); | 
| + interactive = true; | 
| + } | 
| + | 
| + /** | 
| + * Notifies JS code that application quit interactive mode. | 
| + */ | 
| + public void stopInteractive() | 
| + { | 
| + js.execute(new Runnable() { | 
| + @Override | 
| + public void run() | 
| + { | 
| + js.evaluate("stopInteractive()"); | 
| + } | 
| + }); | 
| + interactive = false; | 
| + } | 
| + | 
| + /** | 
| + * Returns prefixes that match current user locale. | 
| + */ | 
| + public String checkLocalePrefixMatch(String[] prefixes) | 
| + { | 
| + if (prefixes == null || prefixes.length == 0) | 
| + return null; | 
| + | 
| + String locale = Locale.getDefault().toString().toLowerCase(); | 
| + | 
| + for (int i = 0; i < prefixes.length; i++) | 
| + if (locale.startsWith(prefixes[i].toLowerCase())) | 
| + return prefixes[i]; | 
| + | 
| + return null; | 
| + } | 
| + | 
| + /** | 
| + * Starts JS engine. It also initiates subscription refresh if it is enabled | 
| + * in user settings. | 
| + */ | 
| + public void startEngine() | 
| + { | 
| + if (js == null) | 
| + { | 
| + Log.e(TAG, "startEngine"); | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Shouldn't this be Log.d()?
 
Andrey Novikov
2012/10/30 07:53:22
I use logging not only for error reporting but als
 
Felix Dahlke
2012/10/30 08:08:42
That's fine for debugging of course, but I think w
 
Andrey Novikov
2012/10/30 09:24:17
Done.
 
 | 
| + js = new JSThread(this); | 
| + js.start(); | 
| + | 
| + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); | 
| + final int refresh = Integer.valueOf(prefs.getString(getString(R.string.pref_refresh), Integer.toString(getResources().getInteger(R.integer.def_refresh)))); | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
We should use a fixed string for storing these pre
 
Andrey Novikov
2012/10/30 07:53:22
These are not locale dependent. Locale dependent s
 
Felix Dahlke
2012/10/30 08:08:42
The string "pref_refresh" is "Subscription refresh
 
Andrey Novikov
2012/10/30 09:24:17
No, the string pref_refresh is "refresh", and what
 
Felix Dahlke
2012/10/30 09:30:50
True, repeated string literals aren't great. How a
 
 | 
| + final boolean wifionly = prefs.getBoolean(getString(R.string.pref_wifirefresh), getResources().getBoolean(R.bool.def_wifirefresh)); | 
| + // Refresh if user selected refresh on each start | 
| + if (refresh == 1 && (!wifionly || isWiFiConnected(this))) | 
| + { | 
| + refreshSubscription(); | 
| + } | 
| + } | 
| + } | 
| + | 
| + /** | 
| + * Stops JS engine. | 
| + * | 
| + * @param implicitly | 
| + * stop even in interactive mode | 
| + */ | 
| + public void stopEngine(boolean implicitly) | 
| + { | 
| + if ((implicitly || !interactive) && js != null) | 
| + { | 
| + js.stopEngine(); | 
| + try | 
| + { | 
| + js.join(); | 
| + } | 
| + catch (InterruptedException e) | 
| + { | 
| + e.printStackTrace(); | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
As before, error logging?
 
Andrey Novikov
2012/10/30 09:24:17
Done.
 
 | 
| + } | 
| + js = null; | 
| + } | 
| + } | 
| + | 
| + /** | 
| + * Sets or removes crash handler according to user setting | 
| + */ | 
| + public void updateCrashReportStatus() | 
| + { | 
| + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); | 
| + boolean report = prefs.getBoolean(getString(R.string.pref_crashreport), getResources().getBoolean(R.bool.def_crashreport)); | 
| + if (report != generateCrashReport) | 
| + { | 
| + if (report) | 
| + { | 
| + Thread.setDefaultUncaughtExceptionHandler(new CrashHandler(this)); | 
| + } | 
| + else | 
| + { | 
| + try | 
| + { | 
| + CrashHandler handler = (CrashHandler) Thread.getDefaultUncaughtExceptionHandler(); | 
| + Thread.setDefaultUncaughtExceptionHandler(handler.getDefault()); | 
| + } | 
| + catch (ClassCastException e) | 
| + { | 
| + // ignore - already default handler | 
| + } | 
| + } | 
| + generateCrashReport = report; | 
| + } | 
| + } | 
| + | 
| + /** | 
| + * Sets Alarm to call updater after specified number of minutes or after one day if | 
| + * minutes are set to 0. | 
| + * | 
| + * @param minutes | 
| + * number of minutes to wait | 
| + */ | 
| + public void scheduleUpdater(int minutes) | 
| + { | 
| + Calendar updateTime = Calendar.getInstance(); | 
| + | 
| + if (minutes == 0) | 
| + { | 
| + // Start update checks at 10:00 GMT... | 
| + updateTime.setTimeZone(TimeZone.getTimeZone("GMT")); | 
| + updateTime.set(Calendar.HOUR_OF_DAY, 10); | 
| + updateTime.set(Calendar.MINUTE, 0); | 
| + // ...next day | 
| + updateTime.add(Calendar.HOUR_OF_DAY, 24); | 
| + // Spread out the “mass downloading” for 6 hours | 
| + updateTime.add(Calendar.MINUTE, (int) Math.random() * 60 * 6); | 
| + } | 
| + else | 
| + { | 
| + updateTime.add(Calendar.MINUTE, minutes); | 
| + } | 
| + | 
| + Intent updater = new Intent(this, AlarmReceiver.class); | 
| + PendingIntent recurringUpdate = PendingIntent.getBroadcast(this, 0, updater, PendingIntent.FLAG_CANCEL_CURRENT); | 
| + // Set non-waking alarm | 
| + AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE); | 
| + Log.e(TAG, "Set updater alarm"); | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
I guess Log.d() is more appropriate here?
 
Andrey Novikov
2012/10/30 09:24:17
Done.
 
 | 
| + alarms.set(AlarmManager.RTC, updateTime.getTimeInMillis(), recurringUpdate); | 
| + } | 
| + | 
| + @Override | 
| + public void onCreate() | 
| + { | 
| + super.onCreate(); | 
| + myself = this; | 
| + | 
| + // Check for crash report | 
| + try | 
| + { | 
| + InputStreamReader reportFile = new InputStreamReader(openFileInput(CrashHandler.REPORT_FILE)); | 
| + final char[] buffer = new char[0x1000]; | 
| + StringBuilder out = new StringBuilder(); | 
| + int read; | 
| + do | 
| + { | 
| + read = reportFile.read(buffer, 0, buffer.length); | 
| + if (read > 0) | 
| + out.append(buffer, 0, read); | 
| + } | 
| + while (read >= 0); | 
| + String report = out.toString(); | 
| + if (!"".equals(report)) | 
| + { | 
| + final Intent intent = new Intent(this, CrashReportDialog.class); | 
| + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | 
| + intent.putExtra("report", report); | 
| + startActivity(intent); | 
| + } | 
| + } | 
| + catch (FileNotFoundException e) | 
| + { | 
| + // ignore | 
| + } | 
| + catch (IOException e) | 
| + { | 
| + // TODO Auto-generated catch block | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Log.e()?
 
Andrey Novikov
2012/10/30 09:24:17
Done.
 
 | 
| + e.printStackTrace(); | 
| + } | 
| + | 
| + if (!getResources().getBoolean(R.bool.def_release)) | 
| + { | 
| + // Set crash handler | 
| + updateCrashReportStatus(); | 
| + // Initiate update check | 
| + scheduleUpdater(0); | 
| + } | 
| + } | 
| + | 
| + /** | 
| + * Handler for showing toast messages from JS code. | 
| + */ | 
| + private final Handler messageHandler = new Handler() { | 
| + public void handleMessage(Message msg) | 
| + { | 
| + if (msg.what == MSG_TOAST) | 
| + { | 
| + Toast.makeText(AdblockPlus.this, msg.getData().getString("message"), Toast.LENGTH_LONG).show(); | 
| + } | 
| + } | 
| + }; | 
| + | 
| + /** | 
| + * JS execution thread. | 
| + */ | 
| + private final class JSThread extends Thread | 
| + { | 
| + private JSEngine jsEngine; | 
| + private volatile boolean run = true; | 
| + private Context context; | 
| + private final LinkedList<Runnable> queue = new LinkedList<Runnable>(); | 
| + private long delay = -1; | 
| + | 
| + JSThread(Context context) | 
| + { | 
| + this.context = context; | 
| + } | 
| + | 
| + // JS helper | 
| + @SuppressWarnings("unused") | 
| + public String readJSFile(String name) | 
| + { | 
| + String result = ""; | 
| + AssetManager assetManager = getAssets(); | 
| + try | 
| + { | 
| + InputStreamReader reader = new InputStreamReader(assetManager.open("js" + File.separator + name)); | 
| + final char[] buffer = new char[0x10000]; | 
| + StringBuilder out = new StringBuilder(); | 
| + int read; | 
| + do | 
| + { | 
| + read = reader.read(buffer, 0, buffer.length); | 
| + if (read > 0) | 
| + out.append(buffer, 0, read); | 
| + } | 
| + while (read >= 0); | 
| + result = out.toString(); | 
| + } | 
| + catch (IOException e) | 
| + { | 
| + e.printStackTrace(); | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Log.e()?
 
Andrey Novikov
2012/10/30 09:24:17
Done.
 
 | 
| + } | 
| + return result; | 
| + } | 
| + | 
| + // JS helper | 
| + public FileInputStream getInputStream(String path) | 
| + { | 
| + Log.e(TAG, path); | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Log.d()?
 
Andrey Novikov
2012/10/30 09:24:17
Done.
 
 | 
| + File f = new File(path); | 
| + try | 
| + { | 
| + return openFileInput(f.getName()); | 
| + } | 
| + catch (FileNotFoundException e) | 
| + { | 
| + e.printStackTrace(); | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Log.e()?
 
Andrey Novikov
2012/10/30 09:24:17
Done.
 
 | 
| + } | 
| + return null; | 
| + } | 
| + | 
| + // JS helper | 
| + public FileOutputStream getOutputStream(String path) | 
| + { | 
| + Log.e(TAG, path); | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Log.d()?
 
Andrey Novikov
2012/10/30 09:24:17
Done.
 
 | 
| + File f = new File(path); | 
| + try | 
| + { | 
| + return openFileOutput(f.getName(), MODE_PRIVATE); | 
| + } | 
| + catch (FileNotFoundException e) | 
| + { | 
| + e.printStackTrace(); | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Log.e()?
 
Andrey Novikov
2012/10/30 09:24:17
Done.
 
 | 
| + } | 
| + return null; | 
| + } | 
| + | 
| + // JS helper | 
| + public String getVersion() | 
| + { | 
| + String versionName = null; | 
| + try | 
| + { | 
| + versionName = getPackageManager().getPackageInfo(getPackageName(), 0).versionName; | 
| + } | 
| + catch (NameNotFoundException ex) | 
| + { | 
| + versionName = "n/a"; | 
| + } | 
| + return versionName; | 
| + } | 
| + | 
| + // JS helper | 
| + @SuppressWarnings("unused") | 
| + public boolean canAutoupdate() | 
| + { | 
| + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); | 
| + final int refresh = Integer.valueOf(prefs.getString(getString(R.string.pref_refresh), Integer.toString(context.getResources().getInteger(R.integer.def_refresh)))); | 
| + final boolean wifionly = prefs.getBoolean(getString(R.string.pref_wifirefresh), getResources().getBoolean(R.bool.def_wifirefresh)); | 
| + return refresh == 2 && (!wifionly || isWiFiConnected(context)); | 
| + } | 
| + | 
| + // JS helper | 
| + @SuppressWarnings("unused") | 
| + public void httpSend(final String method, final String url, final String[][] headers, final boolean async, final long callback) | 
| + { | 
| + Log.e(TAG, "httpSend('" + method + "', '" + url + "')"); | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Log.d()?
 
Andrey Novikov
2012/10/30 09:24:17
Done.
 
 | 
| + messageHandler.post(new Runnable() { | 
| + @Override | 
| + public void run() | 
| + { | 
| + try | 
| + { | 
| + Task task = new Task(); | 
| + task.callback = callback; | 
| + task.connection = (HttpURLConnection) new URL(url).openConnection(); | 
| + task.connection.setRequestMethod(method); | 
| + for (int i = 0; i < headers.length; i++) | 
| + { | 
| + task.connection.setRequestProperty(headers[i][0], headers[i][1]); | 
| + } | 
| + DownloadTask downloadTask = new DownloadTask(context); | 
| + downloadTask.execute(task); | 
| + if (!async) | 
| + { | 
| + downloadTask.get(); | 
| + } | 
| + } | 
| + catch (Exception e) | 
| + { | 
| + e.printStackTrace(); | 
| + js.callback(callback, null); | 
| + } | 
| + } | 
| + }); | 
| + } | 
| + | 
| + // JS helper | 
| + @SuppressWarnings("unused") | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
I don't think this is necessary
 
Andrey Novikov
2012/10/30 07:53:22
This is necessary because Eclipse highlights all w
 
Felix Dahlke
2012/10/30 08:08:42
I know what it's for, but I can't see any unused v
 
Andrey Novikov
2012/10/30 09:24:17
It's not about variables but about the method itse
 
Felix Dahlke
2012/10/30 09:30:50
Ah, I see. You only need that code for debugging?
 
Andrey Novikov
2012/10/30 09:45:28
No, it's called by JS engine from native code, tha
 
 | 
| + public void setStatus(String text, long time) | 
| + { | 
| + sendBroadcast(new Intent(BROADCAST_SUBSCRIPTION_STATUS).putExtra("text", text).putExtra("time", time)); | 
| + } | 
| + | 
| + // JS helper | 
| + @SuppressWarnings("unused") | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
I don't think this is necessary
 
 | 
| + public void showToast(String text) | 
| + { | 
| + Log.e(TAG, "Toast: " + text); | 
| + Message msg = messageHandler.obtainMessage(MSG_TOAST); | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Log.d()?
 
Andrey Novikov
2012/10/30 09:24:17
Done.
 
 | 
| + Bundle data = new Bundle(); | 
| + data.putString("message", text); | 
| + msg.setData(data); | 
| + messageHandler.sendMessage(msg); | 
| + } | 
| + | 
| + // JS helper | 
| + @SuppressWarnings("unused") | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
I don't  think this is necessary
 
 | 
| + public void notify(long delay) | 
| + { | 
| + if (this.delay < 0 || delay < this.delay) | 
| + { | 
| + this.delay = delay; | 
| + synchronized (queue) | 
| + { | 
| + queue.notify(); | 
| + } | 
| + } | 
| + } | 
| + | 
| + public Object evaluate(String script) | 
| + { | 
| + return jsEngine.evaluate(script); | 
| + } | 
| + | 
| + public void callback(long callback, Object[] params) | 
| + { | 
| + jsEngine.callback(callback, params); | 
| + } | 
| + | 
| + public final void stopEngine() | 
| + { | 
| + run = false; | 
| + synchronized (queue) | 
| + { | 
| + queue.notify(); | 
| + } | 
| + } | 
| + | 
| + public void execute(Runnable r) | 
| + { | 
| + synchronized (queue) | 
| + { | 
| + queue.addLast(r); | 
| + queue.notify(); | 
| + } | 
| + } | 
| + | 
| + public <T> Future<T> submit(Callable<T> callable) | 
| + { | 
| + FutureTask<T> ftask = new FutureTask<T>(callable); | 
| + execute(ftask); | 
| + return ftask; | 
| + } | 
| + | 
| + @Override | 
| + public final void run() | 
| + { | 
| + jsEngine = new JSEngine(this); | 
| + | 
| + jsEngine.put("_locale", Locale.getDefault().toString()); | 
| + jsEngine.put("_datapath", getFilesDir().getAbsolutePath()); | 
| + jsEngine.put("_separator", File.separator); | 
| + jsEngine.put("_version", getVersion()); | 
| + | 
| + try | 
| + { | 
| + jsEngine.evaluate("Android.load(\"start.js\");"); | 
| + } | 
| + catch (Exception e) | 
| + { | 
| + e.printStackTrace(); | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Log.e()?
 
Andrey Novikov
2012/10/30 09:24:17
Done.
 
 | 
| + } | 
| + | 
| + while (run) | 
| + { | 
| + try | 
| + { | 
| + Runnable r = null; | 
| + synchronized (queue) | 
| + { | 
| + r = queue.poll(); | 
| + } | 
| + if (r != null) | 
| + { | 
| + r.run(); | 
| + } | 
| + else if (delay > 0) | 
| + { | 
| + long t = SystemClock.uptimeMillis(); | 
| + synchronized (queue) | 
| + { | 
| + try | 
| + { | 
| + queue.wait(delay); | 
| + } | 
| + catch (InterruptedException e) | 
| + { | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Log.e()?
 
Andrey Novikov
2012/10/30 09:24:17
Done.
 
 | 
| + } | 
| + } | 
| + delay -= SystemClock.uptimeMillis() - t; | 
| + } | 
| + else if (delay <= 0) | 
| + { | 
| + delay = jsEngine.runCallbacks(); | 
| + } | 
| + else | 
| + { | 
| + synchronized (queue) | 
| + { | 
| + try | 
| + { | 
| + queue.wait(); | 
| + } | 
| + catch (InterruptedException e) | 
| + { | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Log.e()?
 
Andrey Novikov
2012/10/30 09:24:17
Done.
 
 | 
| + } | 
| + } | 
| + } | 
| + } | 
| + catch (Exception e) | 
| + { | 
| + e.printStackTrace(); | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Log.e()?
 
Andrey Novikov
2012/10/30 09:24:17
Done.
 
 | 
| + } | 
| + } | 
| + | 
| + jsEngine.release(); | 
| + } | 
| + } | 
| + | 
| + /** | 
| + * Helper class for XMLHttpRequest implementation. | 
| + */ | 
| + private class Task | 
| + { | 
| + HttpURLConnection connection; | 
| + long callback; | 
| + } | 
| + | 
| + /** | 
| + * Helper class for XMLHttpRequest implementation. | 
| + */ | 
| + private class Result | 
| + { | 
| + long callback; | 
| + int code; | 
| + String message; | 
| + String data; | 
| + Map<String, List<String>> headers; | 
| + } | 
| + | 
| + /** | 
| + * Helper class for XMLHttpRequest implementation. | 
| + */ | 
| + private class DownloadTask extends AsyncTask<Task, Integer, Result> | 
| + { | 
| + public DownloadTask(Context context) | 
| + { | 
| + } | 
| + | 
| + @Override | 
| + protected void onPreExecute() | 
| + { | 
| + } | 
| + | 
| + @Override | 
| + protected void onPostExecute(Result result) | 
| + { | 
| + if (result != null) | 
| + { | 
| + final long callback = result.callback; | 
| + final Object[] params = new Object[4]; | 
| + | 
| + String[][] headers = null; | 
| + if (result.headers != null) | 
| + { | 
| + headers = new String[result.headers.size()][2]; | 
| + int i = 0; | 
| + for (String header : result.headers.keySet()) | 
| + { | 
| + headers[i][0] = header; | 
| + headers[i][1] = StringUtils.join(result.headers.get(header).toArray(), "; "); | 
| + i++; | 
| + } | 
| + } | 
| + params[0] = result.code; | 
| + params[1] = result.message; | 
| + params[2] = headers; | 
| + params[3] = result.data; | 
| + js.execute(new Runnable() { | 
| + @Override | 
| + public void run() | 
| + { | 
| + js.callback(callback, params); | 
| + } | 
| + | 
| + }); | 
| + } | 
| + } | 
| + | 
| + @Override | 
| + protected void onCancelled() | 
| + { | 
| + } | 
| + | 
| + @Override | 
| + protected Result doInBackground(Task... tasks) | 
| + { | 
| + Task task = tasks[0]; | 
| + Result result = new Result(); | 
| + result.callback = task.callback; | 
| + try | 
| + { | 
| + HttpURLConnection connection = task.connection; | 
| + connection.connect(); | 
| + int lenghtOfFile = connection.getContentLength(); | 
| + Log.w("D", "S: " + lenghtOfFile); | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Log.d()?
 
Andrey Novikov
2012/10/30 09:24:17
Done.
 
 | 
| + | 
| + result.code = connection.getResponseCode(); | 
| + result.message = connection.getResponseMessage(); | 
| + result.headers = connection.getHeaderFields(); | 
| + | 
| + // download the file | 
| + String encoding = connection.getContentEncoding(); | 
| + if (encoding == null) | 
| + encoding = "utf-8"; | 
| + BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream(), encoding)); | 
| + | 
| + final char[] buffer = new char[0x10000]; | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Reminds me of the code up there, maybe have a sing
 
Andrey Novikov
2012/10/30 07:53:22
No, I can't. At least do not know how. Because her
 
 | 
| + StringBuilder out = new StringBuilder(); | 
| + long total = 0; | 
| + int read; | 
| + do | 
| + { | 
| + read = in.read(buffer, 0, buffer.length); | 
| + if (read > 0) | 
| + { | 
| + out.append(buffer, 0, read); | 
| + total += read; | 
| + publishProgress((int) (total * 100. / lenghtOfFile)); | 
| + } | 
| + } | 
| + while (!isCancelled() && read >= 0); | 
| + result.data = out.toString(); | 
| + in.close(); | 
| + } | 
| + catch (Exception e) | 
| + { | 
| + e.printStackTrace(); | 
| 
 
Felix Dahlke
2012/10/29 15:06:36
Log.e()?
 
Andrey Novikov
2012/10/30 09:24:17
Done.
 
 | 
| + result.data = ""; | 
| + result.code = HttpURLConnection.HTTP_INTERNAL_ERROR; | 
| + result.message = e.toString(); | 
| + } | 
| + return result; | 
| + } | 
| + | 
| + protected void onProgressUpdate(Integer... progress) | 
| + { | 
| + Log.i("HTTP", "Progress: " + progress[0].intValue()); | 
| + } | 
| + } | 
| +} |