OLD | NEW |
(Empty) | |
| 1 package org.adblockplus.android; |
| 2 |
| 3 import java.io.BufferedReader; |
| 4 import java.io.File; |
| 5 import java.io.FileInputStream; |
| 6 import java.io.FileNotFoundException; |
| 7 import java.io.FileOutputStream; |
| 8 import java.io.IOException; |
| 9 import java.io.InputStreamReader; |
| 10 import java.net.HttpURLConnection; |
| 11 import java.net.URL; |
| 12 import java.util.ArrayList; |
| 13 import java.util.Calendar; |
| 14 import java.util.LinkedList; |
| 15 import java.util.List; |
| 16 import java.util.Locale; |
| 17 import java.util.Map; |
| 18 import java.util.TimeZone; |
| 19 import java.util.concurrent.Callable; |
| 20 import java.util.concurrent.ExecutionException; |
| 21 import java.util.concurrent.Future; |
| 22 import java.util.concurrent.FutureTask; |
| 23 |
| 24 import javax.xml.parsers.ParserConfigurationException; |
| 25 import javax.xml.parsers.SAXParser; |
| 26 import javax.xml.parsers.SAXParserFactory; |
| 27 |
| 28 import org.adblockplus.android.updater.AlarmReceiver; |
| 29 import org.apache.commons.lang.StringEscapeUtils; |
| 30 import org.apache.commons.lang.StringUtils; |
| 31 import org.json.JSONException; |
| 32 import org.json.JSONObject; |
| 33 import org.xml.sax.SAXException; |
| 34 |
| 35 import android.app.AlarmManager; |
| 36 import android.app.Application; |
| 37 import android.app.PendingIntent; |
| 38 import android.content.Context; |
| 39 import android.content.Intent; |
| 40 import android.content.SharedPreferences; |
| 41 import android.content.pm.PackageInfo; |
| 42 import android.content.pm.PackageManager; |
| 43 import android.content.pm.PackageManager.NameNotFoundException; |
| 44 import android.content.res.AssetManager; |
| 45 import android.net.ConnectivityManager; |
| 46 import android.net.NetworkInfo; |
| 47 import android.os.AsyncTask; |
| 48 import android.os.Build; |
| 49 import android.os.Bundle; |
| 50 import android.os.Handler; |
| 51 import android.os.Message; |
| 52 import android.os.SystemClock; |
| 53 import android.preference.PreferenceManager; |
| 54 import android.util.Log; |
| 55 import android.widget.Toast; |
| 56 |
| 57 public class AdblockPlus extends Application |
| 58 { |
| 59 private final static String TAG = "Application"; |
| 60 |
| 61 private final static int MSG_TOAST = 1; |
| 62 |
| 63 /** |
| 64 * Broadcasted when subscription status changes. |
| 65 */ |
| 66 public final static String BROADCAST_SUBSCRIPTION_STATUS = "org.adblockplus.an
droid.subscription.status"; |
| 67 /** |
| 68 * Broadcasted when filter match check is performed. |
| 69 */ |
| 70 public final static String BROADCAST_FILTER_MATCHES = "org.adblockplus.android
.filter.matches"; |
| 71 |
| 72 private List<Subscription> subscriptions; |
| 73 |
| 74 private JSThread js; |
| 75 |
| 76 /** |
| 77 * Indicates interactive mode (used to listen for subscription status |
| 78 * changes). |
| 79 */ |
| 80 private boolean interactive = false; |
| 81 |
| 82 private boolean generateCrashReport = false; |
| 83 |
| 84 private static AdblockPlus instance; |
| 85 |
| 86 /** |
| 87 * Returns pointer to itself (singleton pattern). |
| 88 */ |
| 89 public static AdblockPlus getApplication() |
| 90 { |
| 91 return instance; |
| 92 } |
| 93 |
| 94 public int getBuildNumber() |
| 95 { |
| 96 int buildNumber = -1; |
| 97 try |
| 98 { |
| 99 PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0); |
| 100 buildNumber = pi.versionCode; |
| 101 } |
| 102 catch (NameNotFoundException e) |
| 103 { |
| 104 // ignore - this shouldn't happen |
| 105 Log.e(TAG, e.getMessage(), e); |
| 106 } |
| 107 return buildNumber; |
| 108 } |
| 109 |
| 110 /** |
| 111 * Returns device name in user-friendly format |
| 112 */ |
| 113 public static String getDeviceName() |
| 114 { |
| 115 String manufacturer = Build.MANUFACTURER; |
| 116 String model = Build.MODEL; |
| 117 if (model.startsWith(manufacturer)) |
| 118 return capitalize(model); |
| 119 else |
| 120 return capitalize(manufacturer) + " " + model; |
| 121 } |
| 122 |
| 123 private static String capitalize(String s) |
| 124 { |
| 125 if (s == null || s.length() == 0) |
| 126 return ""; |
| 127 char first = s.charAt(0); |
| 128 if (Character.isUpperCase(first)) |
| 129 return s; |
| 130 else |
| 131 return Character.toUpperCase(first) + s.substring(1); |
| 132 } |
| 133 |
| 134 /** |
| 135 * Checks if device has a WiFi connection available. |
| 136 */ |
| 137 public static boolean isWiFiConnected(Context context) |
| 138 { |
| 139 ConnectivityManager connectivityManager = (ConnectivityManager) context.getS
ystemService(Context.CONNECTIVITY_SERVICE); |
| 140 NetworkInfo networkInfo = null; |
| 141 if (connectivityManager != null) |
| 142 { |
| 143 networkInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_
WIFI); |
| 144 } |
| 145 return networkInfo == null ? false : networkInfo.isConnected(); |
| 146 } |
| 147 |
| 148 /** |
| 149 * Checks if application can write to external storage. |
| 150 */ |
| 151 public boolean checkWriteExternalPermission() |
| 152 { |
| 153 |
| 154 String permission = "android.permission.WRITE_EXTERNAL_STORAGE"; |
| 155 int res = checkCallingOrSelfPermission(permission); |
| 156 return res == PackageManager.PERMISSION_GRANTED; |
| 157 } |
| 158 |
| 159 /** |
| 160 * Returns list of known subscriptions. |
| 161 */ |
| 162 public List<Subscription> getSubscriptions() |
| 163 { |
| 164 if (subscriptions == null) |
| 165 { |
| 166 subscriptions = new ArrayList<Subscription>(); |
| 167 |
| 168 SAXParserFactory factory = SAXParserFactory.newInstance(); |
| 169 SAXParser parser; |
| 170 try |
| 171 { |
| 172 parser = factory.newSAXParser(); |
| 173 parser.parse(getAssets().open("subscriptions.xml"), new SubscriptionPars
er(subscriptions)); |
| 174 } |
| 175 catch (ParserConfigurationException e) |
| 176 { |
| 177 // TODO Auto-generated catch block |
| 178 Log.e(TAG, e.getMessage(), e); |
| 179 } |
| 180 catch (SAXException e) |
| 181 { |
| 182 // TODO Auto-generated catch block |
| 183 Log.e(TAG, e.getMessage(), e); |
| 184 } |
| 185 catch (IOException e) |
| 186 { |
| 187 // TODO Auto-generated catch block |
| 188 Log.e(TAG, e.getMessage(), e); |
| 189 } |
| 190 } |
| 191 return subscriptions; |
| 192 } |
| 193 |
| 194 /** |
| 195 * Returns subscription information. |
| 196 * |
| 197 * @param url |
| 198 * subscription url |
| 199 */ |
| 200 public Subscription getSubscription(String url) |
| 201 { |
| 202 List<Subscription> subscriptions = getSubscriptions(); |
| 203 |
| 204 for (Subscription subscription : subscriptions) |
| 205 { |
| 206 if (subscription.url.equals(url)) |
| 207 return subscription; |
| 208 } |
| 209 return null; |
| 210 } |
| 211 |
| 212 /** |
| 213 * Adds provided subscription and removes previous subscriptions if any. |
| 214 * |
| 215 * @param subscription |
| 216 * Subscription to add |
| 217 */ |
| 218 public void setSubscription(Subscription subscription) |
| 219 { |
| 220 if (subscription != null) |
| 221 { |
| 222 final JSONObject jsonSub = new JSONObject(); |
| 223 try |
| 224 { |
| 225 jsonSub.put("url", subscription.url); |
| 226 jsonSub.put("title", subscription.title); |
| 227 jsonSub.put("homepage", subscription.homepage); |
| 228 js.execute(new Runnable() |
| 229 { |
| 230 @Override |
| 231 public void run() |
| 232 { |
| 233 js.evaluate("clearSubscriptions()"); |
| 234 js.evaluate("addSubscription(\"" + StringEscapeUtils.escapeJavaScrip
t(jsonSub.toString()) + "\")"); |
| 235 } |
| 236 }); |
| 237 } |
| 238 catch (JSONException e) |
| 239 { |
| 240 // TODO Auto-generated catch block |
| 241 Log.e(TAG, e.getMessage(), e); |
| 242 } |
| 243 } |
| 244 } |
| 245 |
| 246 /** |
| 247 * Forces subscriptions refresh. |
| 248 */ |
| 249 public void refreshSubscription() |
| 250 { |
| 251 js.execute(new Runnable() |
| 252 { |
| 253 @Override |
| 254 public void run() |
| 255 { |
| 256 js.evaluate("refreshSubscriptions()"); |
| 257 } |
| 258 }); |
| 259 } |
| 260 |
| 261 /** |
| 262 * Selects which subscription to offer for the first time. |
| 263 * |
| 264 * @return offered subscription |
| 265 */ |
| 266 public Subscription offerSubscription() |
| 267 { |
| 268 Subscription selectedItem = null; |
| 269 String selectedPrefix = null; |
| 270 int matchCount = 0; |
| 271 for (Subscription subscription : getSubscriptions()) |
| 272 { |
| 273 if (selectedItem == null) |
| 274 selectedItem = subscription; |
| 275 |
| 276 String prefix = checkLocalePrefixMatch(subscription.prefixes); |
| 277 if (prefix != null) |
| 278 { |
| 279 if (selectedPrefix == null || selectedPrefix.length() < prefix.length()) |
| 280 { |
| 281 selectedItem = subscription; |
| 282 selectedPrefix = prefix; |
| 283 matchCount = 1; |
| 284 } |
| 285 else if (selectedPrefix != null && selectedPrefix.length() == prefix.len
gth()) |
| 286 { |
| 287 matchCount++; |
| 288 |
| 289 // If multiple items have a matching prefix of the |
| 290 // same length select one of the items randomly, |
| 291 // probability should be the same for all items. |
| 292 // So we replace the previous match here with |
| 293 // probability 1/N (N being the number of matches). |
| 294 if (Math.random() * matchCount < 1) |
| 295 { |
| 296 selectedItem = subscription; |
| 297 selectedPrefix = prefix; |
| 298 } |
| 299 } |
| 300 } |
| 301 } |
| 302 return selectedItem; |
| 303 } |
| 304 |
| 305 /** |
| 306 * Verifies that subscriptions are loaded and returns flag of subscription |
| 307 * presence. |
| 308 * |
| 309 * @return true if at least one subscription is present and downloaded |
| 310 */ |
| 311 public boolean verifySubscriptions() |
| 312 { |
| 313 Future<Boolean> future = js.submit(new Callable<Boolean>() |
| 314 { |
| 315 @Override |
| 316 public Boolean call() throws Exception |
| 317 { |
| 318 Boolean result = (Boolean) js.evaluate("verifySubscriptions()"); |
| 319 return result; |
| 320 } |
| 321 }); |
| 322 try |
| 323 { |
| 324 return future.get().booleanValue(); |
| 325 } |
| 326 catch (InterruptedException e) |
| 327 { |
| 328 // TODO Auto-generated catch block |
| 329 Log.e(TAG, e.getMessage(), e); |
| 330 } |
| 331 catch (ExecutionException e) |
| 332 { |
| 333 // TODO Auto-generated catch block |
| 334 Log.e(TAG, e.getMessage(), e); |
| 335 } |
| 336 return false; |
| 337 } |
| 338 |
| 339 /** |
| 340 * Returns ElemHide selectors for domain. |
| 341 * |
| 342 * @return ready to use HTML element with CSS selectors |
| 343 */ |
| 344 public String getSelectorsForDomain(final String domain) |
| 345 { |
| 346 Future<String> future = js.submit(new Callable<String>() |
| 347 { |
| 348 @Override |
| 349 public String call() throws Exception |
| 350 { |
| 351 String result = (String) js.evaluate("ElemHide.getSelectorsForDomain('"
+ domain + "')"); |
| 352 return result; |
| 353 } |
| 354 }); |
| 355 try |
| 356 { |
| 357 return future.get(); |
| 358 } |
| 359 catch (InterruptedException e) |
| 360 { |
| 361 // TODO Auto-generated catch block |
| 362 Log.e(TAG, e.getMessage(), e); |
| 363 } |
| 364 catch (ExecutionException e) |
| 365 { |
| 366 // TODO Auto-generated catch block |
| 367 Log.e(TAG, e.getMessage(), e); |
| 368 } |
| 369 return null; |
| 370 } |
| 371 |
| 372 private class MatchesCallable implements Callable<Boolean> |
| 373 { |
| 374 private String url; |
| 375 private String query; |
| 376 private String reqHost; |
| 377 private String refHost; |
| 378 private String accept; |
| 379 |
| 380 MatchesCallable(String url, String query, String reqHost, String refHost, St
ring accept) |
| 381 { |
| 382 this.url = url; |
| 383 this.query = query; |
| 384 this.reqHost = reqHost != null ? reqHost : ""; |
| 385 this.refHost = refHost != null ? refHost : ""; |
| 386 this.accept = accept != null ? accept : ""; |
| 387 } |
| 388 |
| 389 @Override |
| 390 public Boolean call() throws Exception |
| 391 { |
| 392 Boolean result = (Boolean) js.evaluate("matchesAny('" + url + "', '" + que
ry + "', '" + reqHost + "', '" + refHost + "', '" + accept + "');"); |
| 393 return result; |
| 394 } |
| 395 } |
| 396 |
| 397 /** |
| 398 * Checks if filters match request parameters. |
| 399 * |
| 400 * @param url |
| 401 * Request URL |
| 402 * @param query |
| 403 * Request query string |
| 404 * @param reqHost |
| 405 * Request host |
| 406 * @param refHost |
| 407 * Request referrer header |
| 408 * @param accept |
| 409 * Request accept header |
| 410 * @return true if matched filter was found |
| 411 * @throws Exception |
| 412 */ |
| 413 public boolean matches(String url, String query, String reqHost, String refHos
t, String accept) throws Exception |
| 414 { |
| 415 Callable<Boolean> callable = new MatchesCallable(url, query, reqHost, refHos
t, accept); |
| 416 Future<Boolean> future = js.submit(callable); |
| 417 boolean matches = future.get().booleanValue(); |
| 418 sendBroadcast(new Intent(BROADCAST_FILTER_MATCHES).putExtra("url", url).putE
xtra("matches", matches)); |
| 419 return matches; |
| 420 } |
| 421 |
| 422 /** |
| 423 * Notifies JS code that application entered interactive mode. |
| 424 */ |
| 425 public void startInteractive() |
| 426 { |
| 427 js.execute(new Runnable() |
| 428 { |
| 429 @Override |
| 430 public void run() |
| 431 { |
| 432 js.evaluate("startInteractive()"); |
| 433 } |
| 434 }); |
| 435 interactive = true; |
| 436 } |
| 437 |
| 438 /** |
| 439 * Notifies JS code that application quit interactive mode. |
| 440 */ |
| 441 public void stopInteractive() |
| 442 { |
| 443 js.execute(new Runnable() |
| 444 { |
| 445 @Override |
| 446 public void run() |
| 447 { |
| 448 js.evaluate("stopInteractive()"); |
| 449 } |
| 450 }); |
| 451 interactive = false; |
| 452 } |
| 453 |
| 454 /** |
| 455 * Returns prefixes that match current user locale. |
| 456 */ |
| 457 public String checkLocalePrefixMatch(String[] prefixes) |
| 458 { |
| 459 if (prefixes == null || prefixes.length == 0) |
| 460 return null; |
| 461 |
| 462 String locale = Locale.getDefault().toString().toLowerCase(); |
| 463 |
| 464 for (int i = 0; i < prefixes.length; i++) |
| 465 if (locale.startsWith(prefixes[i].toLowerCase())) |
| 466 return prefixes[i]; |
| 467 |
| 468 return null; |
| 469 } |
| 470 |
| 471 /** |
| 472 * Starts JS engine. It also initiates subscription refresh if it is enabled |
| 473 * in user settings. |
| 474 */ |
| 475 public void startEngine() |
| 476 { |
| 477 if (js == null) |
| 478 { |
| 479 Log.i(TAG, "startEngine"); |
| 480 js = new JSThread(this); |
| 481 js.start(); |
| 482 |
| 483 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferen
ces(this); |
| 484 final int refresh = Integer.valueOf(prefs.getString(getString(R.string.pre
f_refresh), Integer.toString(getResources().getInteger(R.integer.def_refresh))))
; |
| 485 final boolean wifionly = prefs.getBoolean(getString(R.string.pref_wifirefr
esh), getResources().getBoolean(R.bool.def_wifirefresh)); |
| 486 // Refresh if user selected refresh on each start |
| 487 if (refresh == 1 && (!wifionly || isWiFiConnected(this))) |
| 488 { |
| 489 refreshSubscription(); |
| 490 } |
| 491 } |
| 492 } |
| 493 |
| 494 /** |
| 495 * Stops JS engine. |
| 496 * |
| 497 * @param implicitly |
| 498 * stop even in interactive mode |
| 499 */ |
| 500 public void stopEngine(boolean implicitly) |
| 501 { |
| 502 if ((implicitly || !interactive) && js != null) |
| 503 { |
| 504 js.stopEngine(); |
| 505 try |
| 506 { |
| 507 js.join(); |
| 508 } |
| 509 catch (InterruptedException e) |
| 510 { |
| 511 Log.e(TAG, e.getMessage(), e); |
| 512 } |
| 513 js = null; |
| 514 } |
| 515 } |
| 516 |
| 517 /** |
| 518 * Sets or removes crash handler according to user setting |
| 519 */ |
| 520 public void updateCrashReportStatus() |
| 521 { |
| 522 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this
); |
| 523 boolean report = prefs.getBoolean(getString(R.string.pref_crashreport), getR
esources().getBoolean(R.bool.def_crashreport)); |
| 524 if (report != generateCrashReport) |
| 525 { |
| 526 if (report) |
| 527 { |
| 528 Thread.setDefaultUncaughtExceptionHandler(new CrashHandler(this)); |
| 529 } |
| 530 else |
| 531 { |
| 532 try |
| 533 { |
| 534 CrashHandler handler = (CrashHandler) Thread.getDefaultUncaughtExcepti
onHandler(); |
| 535 Thread.setDefaultUncaughtExceptionHandler(handler.getDefault()); |
| 536 } |
| 537 catch (ClassCastException e) |
| 538 { |
| 539 // ignore - already default handler |
| 540 } |
| 541 } |
| 542 generateCrashReport = report; |
| 543 } |
| 544 } |
| 545 |
| 546 /** |
| 547 * Sets Alarm to call updater after specified number of minutes or after one |
| 548 * day if |
| 549 * minutes are set to 0. |
| 550 * |
| 551 * @param minutes |
| 552 * number of minutes to wait |
| 553 */ |
| 554 public void scheduleUpdater(int minutes) |
| 555 { |
| 556 Calendar updateTime = Calendar.getInstance(); |
| 557 |
| 558 if (minutes == 0) |
| 559 { |
| 560 // Start update checks at 10:00 GMT... |
| 561 updateTime.setTimeZone(TimeZone.getTimeZone("GMT")); |
| 562 updateTime.set(Calendar.HOUR_OF_DAY, 10); |
| 563 updateTime.set(Calendar.MINUTE, 0); |
| 564 // ...next day |
| 565 updateTime.add(Calendar.HOUR_OF_DAY, 24); |
| 566 // Spread out the “mass downloading” for 6 hours |
| 567 updateTime.add(Calendar.MINUTE, (int) Math.random() * 60 * 6); |
| 568 } |
| 569 else |
| 570 { |
| 571 updateTime.add(Calendar.MINUTE, minutes); |
| 572 } |
| 573 |
| 574 Intent updater = new Intent(this, AlarmReceiver.class); |
| 575 PendingIntent recurringUpdate = PendingIntent.getBroadcast(this, 0, updater,
PendingIntent.FLAG_CANCEL_CURRENT); |
| 576 // Set non-waking alarm |
| 577 AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE)
; |
| 578 alarms.set(AlarmManager.RTC, updateTime.getTimeInMillis(), recurringUpdate); |
| 579 } |
| 580 |
| 581 @Override |
| 582 public void onCreate() |
| 583 { |
| 584 super.onCreate(); |
| 585 instance = this; |
| 586 |
| 587 // Check for crash report |
| 588 try |
| 589 { |
| 590 InputStreamReader reportFile = new InputStreamReader(openFileInput(CrashHa
ndler.REPORT_FILE)); |
| 591 final char[] buffer = new char[0x1000]; |
| 592 StringBuilder out = new StringBuilder(); |
| 593 int read; |
| 594 do |
| 595 { |
| 596 read = reportFile.read(buffer, 0, buffer.length); |
| 597 if (read > 0) |
| 598 out.append(buffer, 0, read); |
| 599 } |
| 600 while (read >= 0); |
| 601 String report = out.toString(); |
| 602 if (!"".equals(report)) |
| 603 { |
| 604 final Intent intent = new Intent(this, CrashReportDialog.class); |
| 605 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| 606 intent.putExtra("report", report); |
| 607 startActivity(intent); |
| 608 } |
| 609 } |
| 610 catch (FileNotFoundException e) |
| 611 { |
| 612 // ignore |
| 613 } |
| 614 catch (IOException e) |
| 615 { |
| 616 // TODO Auto-generated catch block |
| 617 Log.e(TAG, e.getMessage(), e); |
| 618 } |
| 619 |
| 620 if (!getResources().getBoolean(R.bool.def_release)) |
| 621 { |
| 622 // Set crash handler |
| 623 updateCrashReportStatus(); |
| 624 // Initiate update check |
| 625 scheduleUpdater(0); |
| 626 } |
| 627 } |
| 628 |
| 629 /** |
| 630 * Handler for showing toast messages from JS code. |
| 631 */ |
| 632 private static final Handler messageHandler = new Handler() |
| 633 { |
| 634 public void handleMessage(Message msg) |
| 635 { |
| 636 if (msg.what == MSG_TOAST) |
| 637 { |
| 638 Toast.makeText(AdblockPlus.getApplication(), msg.getData().getString("me
ssage"), Toast.LENGTH_LONG).show(); |
| 639 } |
| 640 } |
| 641 }; |
| 642 |
| 643 /** |
| 644 * JS execution thread. |
| 645 */ |
| 646 private final class JSThread extends Thread |
| 647 { |
| 648 private JSEngine jsEngine; |
| 649 private volatile boolean run = true; |
| 650 private Context context; |
| 651 private final LinkedList<Runnable> queue = new LinkedList<Runnable>(); |
| 652 private long delay = -1; |
| 653 |
| 654 JSThread(Context context) |
| 655 { |
| 656 this.context = context; |
| 657 } |
| 658 |
| 659 // JS helper |
| 660 @SuppressWarnings("unused") |
| 661 public String readJSFile(String name) |
| 662 { |
| 663 String result = ""; |
| 664 AssetManager assetManager = getAssets(); |
| 665 try |
| 666 { |
| 667 InputStreamReader reader = new InputStreamReader(assetManager.open("js"
+ File.separator + name)); |
| 668 final char[] buffer = new char[0x10000]; |
| 669 StringBuilder out = new StringBuilder(); |
| 670 int read; |
| 671 do |
| 672 { |
| 673 read = reader.read(buffer, 0, buffer.length); |
| 674 if (read > 0) |
| 675 out.append(buffer, 0, read); |
| 676 } |
| 677 while (read >= 0); |
| 678 result = out.toString(); |
| 679 } |
| 680 catch (IOException e) |
| 681 { |
| 682 Log.e(TAG, e.getMessage(), e); |
| 683 } |
| 684 return result; |
| 685 } |
| 686 |
| 687 // JS helper |
| 688 public FileInputStream getInputStream(String path) |
| 689 { |
| 690 Log.d(TAG, path); |
| 691 File f = new File(path); |
| 692 try |
| 693 { |
| 694 return openFileInput(f.getName()); |
| 695 } |
| 696 catch (FileNotFoundException e) |
| 697 { |
| 698 Log.e(TAG, e.getMessage(), e); |
| 699 } |
| 700 return null; |
| 701 } |
| 702 |
| 703 // JS helper |
| 704 public FileOutputStream getOutputStream(String path) |
| 705 { |
| 706 Log.d(TAG, path); |
| 707 File f = new File(path); |
| 708 try |
| 709 { |
| 710 return openFileOutput(f.getName(), MODE_PRIVATE); |
| 711 } |
| 712 catch (FileNotFoundException e) |
| 713 { |
| 714 Log.e(TAG, e.getMessage(), e); |
| 715 } |
| 716 return null; |
| 717 } |
| 718 |
| 719 // JS helper |
| 720 public String getVersion() |
| 721 { |
| 722 String versionName = null; |
| 723 try |
| 724 { |
| 725 versionName = getPackageManager().getPackageInfo(getPackageName(), 0).ve
rsionName; |
| 726 } |
| 727 catch (NameNotFoundException ex) |
| 728 { |
| 729 versionName = "n/a"; |
| 730 } |
| 731 return versionName; |
| 732 } |
| 733 |
| 734 // JS helper |
| 735 @SuppressWarnings("unused") |
| 736 public boolean canAutoupdate() |
| 737 { |
| 738 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferen
ces(context); |
| 739 final int refresh = Integer.valueOf(prefs.getString(getString(R.string.pre
f_refresh), Integer.toString(context.getResources().getInteger(R.integer.def_ref
resh)))); |
| 740 final boolean wifionly = prefs.getBoolean(getString(R.string.pref_wifirefr
esh), getResources().getBoolean(R.bool.def_wifirefresh)); |
| 741 return refresh == 2 && (!wifionly || isWiFiConnected(context)); |
| 742 } |
| 743 |
| 744 // JS helper |
| 745 @SuppressWarnings("unused") |
| 746 public void httpSend(final String method, final String url, final String[][]
headers, final boolean async, final long callback) |
| 747 { |
| 748 Log.d(TAG, "httpSend('" + method + "', '" + url + "')"); |
| 749 messageHandler.post(new Runnable() |
| 750 { |
| 751 @Override |
| 752 public void run() |
| 753 { |
| 754 try |
| 755 { |
| 756 Task task = new Task(); |
| 757 task.callback = callback; |
| 758 task.connection = (HttpURLConnection) new URL(url).openConnection(); |
| 759 task.connection.setRequestMethod(method); |
| 760 for (int i = 0; i < headers.length; i++) |
| 761 { |
| 762 task.connection.setRequestProperty(headers[i][0], headers[i][1]); |
| 763 } |
| 764 DownloadTask downloadTask = new DownloadTask(context); |
| 765 downloadTask.execute(task); |
| 766 if (!async) |
| 767 { |
| 768 downloadTask.get(); |
| 769 } |
| 770 } |
| 771 catch (Exception e) |
| 772 { |
| 773 Log.e(TAG, e.getMessage(), e); |
| 774 js.callback(callback, null); |
| 775 } |
| 776 } |
| 777 }); |
| 778 } |
| 779 |
| 780 // JS helper |
| 781 @SuppressWarnings("unused") |
| 782 public void setStatus(String text, long time) |
| 783 { |
| 784 sendBroadcast(new Intent(BROADCAST_SUBSCRIPTION_STATUS).putExtra("text", t
ext).putExtra("time", time)); |
| 785 } |
| 786 |
| 787 // JS helper |
| 788 @SuppressWarnings("unused") |
| 789 public void showToast(String text) |
| 790 { |
| 791 Log.d(TAG, "Toast: " + text); |
| 792 Message msg = messageHandler.obtainMessage(MSG_TOAST); |
| 793 Bundle data = new Bundle(); |
| 794 data.putString("message", text); |
| 795 msg.setData(data); |
| 796 messageHandler.sendMessage(msg); |
| 797 } |
| 798 |
| 799 // JS helper |
| 800 @SuppressWarnings("unused") |
| 801 public void notify(long delay) |
| 802 { |
| 803 if (this.delay < 0 || delay < this.delay) |
| 804 { |
| 805 this.delay = delay; |
| 806 synchronized (queue) |
| 807 { |
| 808 queue.notify(); |
| 809 } |
| 810 } |
| 811 } |
| 812 |
| 813 public Object evaluate(String script) |
| 814 { |
| 815 return jsEngine.evaluate(script); |
| 816 } |
| 817 |
| 818 public void callback(long callback, Object[] params) |
| 819 { |
| 820 jsEngine.callback(callback, params); |
| 821 } |
| 822 |
| 823 public final void stopEngine() |
| 824 { |
| 825 run = false; |
| 826 synchronized (queue) |
| 827 { |
| 828 queue.notify(); |
| 829 } |
| 830 } |
| 831 |
| 832 public void execute(Runnable r) |
| 833 { |
| 834 synchronized (queue) |
| 835 { |
| 836 queue.addLast(r); |
| 837 queue.notify(); |
| 838 } |
| 839 } |
| 840 |
| 841 public <T> Future<T> submit(Callable<T> callable) |
| 842 { |
| 843 FutureTask<T> ftask = new FutureTask<T>(callable); |
| 844 execute(ftask); |
| 845 return ftask; |
| 846 } |
| 847 |
| 848 @Override |
| 849 public final void run() |
| 850 { |
| 851 jsEngine = new JSEngine(this); |
| 852 |
| 853 jsEngine.put("_locale", Locale.getDefault().toString()); |
| 854 jsEngine.put("_datapath", getFilesDir().getAbsolutePath()); |
| 855 jsEngine.put("_separator", File.separator); |
| 856 jsEngine.put("_version", getVersion()); |
| 857 |
| 858 try |
| 859 { |
| 860 jsEngine.evaluate("Android.load(\"start.js\");"); |
| 861 } |
| 862 catch (Exception e) |
| 863 { |
| 864 Log.e(TAG, e.getMessage(), e); |
| 865 } |
| 866 |
| 867 while (run) |
| 868 { |
| 869 try |
| 870 { |
| 871 Runnable r = null; |
| 872 synchronized (queue) |
| 873 { |
| 874 r = queue.poll(); |
| 875 } |
| 876 if (r != null) |
| 877 { |
| 878 r.run(); |
| 879 } |
| 880 else if (delay > 0) |
| 881 { |
| 882 long t = SystemClock.uptimeMillis(); |
| 883 synchronized (queue) |
| 884 { |
| 885 try |
| 886 { |
| 887 queue.wait(delay); |
| 888 } |
| 889 catch (InterruptedException e) |
| 890 { |
| 891 } |
| 892 } |
| 893 delay -= SystemClock.uptimeMillis() - t; |
| 894 } |
| 895 else if (delay <= 0) |
| 896 { |
| 897 delay = jsEngine.runCallbacks(); |
| 898 } |
| 899 else |
| 900 { |
| 901 synchronized (queue) |
| 902 { |
| 903 try |
| 904 { |
| 905 queue.wait(); |
| 906 } |
| 907 catch (InterruptedException e) |
| 908 { |
| 909 Log.e(TAG, e.getMessage(), e); |
| 910 } |
| 911 } |
| 912 } |
| 913 } |
| 914 catch (Exception e) |
| 915 { |
| 916 Log.e(TAG, e.getMessage(), e); |
| 917 } |
| 918 } |
| 919 |
| 920 jsEngine.release(); |
| 921 } |
| 922 } |
| 923 |
| 924 /** |
| 925 * Helper class for XMLHttpRequest implementation. |
| 926 */ |
| 927 private class Task |
| 928 { |
| 929 HttpURLConnection connection; |
| 930 long callback; |
| 931 } |
| 932 |
| 933 /** |
| 934 * Helper class for XMLHttpRequest implementation. |
| 935 */ |
| 936 private class Result |
| 937 { |
| 938 long callback; |
| 939 int code; |
| 940 String message; |
| 941 String data; |
| 942 Map<String, List<String>> headers; |
| 943 } |
| 944 |
| 945 /** |
| 946 * Helper class for XMLHttpRequest implementation. |
| 947 */ |
| 948 private class DownloadTask extends AsyncTask<Task, Integer, Result> |
| 949 { |
| 950 public DownloadTask(Context context) |
| 951 { |
| 952 } |
| 953 |
| 954 @Override |
| 955 protected void onPreExecute() |
| 956 { |
| 957 } |
| 958 |
| 959 @Override |
| 960 protected void onPostExecute(Result result) |
| 961 { |
| 962 if (result != null) |
| 963 { |
| 964 final long callback = result.callback; |
| 965 final Object[] params = new Object[4]; |
| 966 |
| 967 String[][] headers = null; |
| 968 if (result.headers != null) |
| 969 { |
| 970 headers = new String[result.headers.size()][2]; |
| 971 int i = 0; |
| 972 for (String header : result.headers.keySet()) |
| 973 { |
| 974 headers[i][0] = header; |
| 975 headers[i][1] = StringUtils.join(result.headers.get(header).toArray(
), "; "); |
| 976 i++; |
| 977 } |
| 978 } |
| 979 params[0] = result.code; |
| 980 params[1] = result.message; |
| 981 params[2] = headers; |
| 982 params[3] = result.data; |
| 983 js.execute(new Runnable() |
| 984 { |
| 985 @Override |
| 986 public void run() |
| 987 { |
| 988 js.callback(callback, params); |
| 989 } |
| 990 |
| 991 }); |
| 992 } |
| 993 } |
| 994 |
| 995 @Override |
| 996 protected void onCancelled() |
| 997 { |
| 998 } |
| 999 |
| 1000 @Override |
| 1001 protected Result doInBackground(Task... tasks) |
| 1002 { |
| 1003 Task task = tasks[0]; |
| 1004 Result result = new Result(); |
| 1005 result.callback = task.callback; |
| 1006 try |
| 1007 { |
| 1008 HttpURLConnection connection = task.connection; |
| 1009 connection.connect(); |
| 1010 int lenghtOfFile = connection.getContentLength(); |
| 1011 Log.d("D", "S: " + lenghtOfFile); |
| 1012 |
| 1013 result.code = connection.getResponseCode(); |
| 1014 result.message = connection.getResponseMessage(); |
| 1015 result.headers = connection.getHeaderFields(); |
| 1016 |
| 1017 // download the file |
| 1018 String encoding = connection.getContentEncoding(); |
| 1019 if (encoding == null) |
| 1020 encoding = "utf-8"; |
| 1021 BufferedReader in = new BufferedReader(new InputStreamReader(connection.
getInputStream(), encoding)); |
| 1022 |
| 1023 final char[] buffer = new char[0x10000]; |
| 1024 StringBuilder out = new StringBuilder(); |
| 1025 long total = 0; |
| 1026 int read; |
| 1027 do |
| 1028 { |
| 1029 read = in.read(buffer, 0, buffer.length); |
| 1030 if (read > 0) |
| 1031 { |
| 1032 out.append(buffer, 0, read); |
| 1033 total += read; |
| 1034 publishProgress((int) (total * 100. / lenghtOfFile)); |
| 1035 } |
| 1036 } |
| 1037 while (!isCancelled() && read >= 0); |
| 1038 result.data = out.toString(); |
| 1039 in.close(); |
| 1040 } |
| 1041 catch (Exception e) |
| 1042 { |
| 1043 Log.e(TAG, e.getMessage(), e); |
| 1044 result.data = ""; |
| 1045 result.code = HttpURLConnection.HTTP_INTERNAL_ERROR; |
| 1046 result.message = e.toString(); |
| 1047 } |
| 1048 return result; |
| 1049 } |
| 1050 |
| 1051 protected void onProgressUpdate(Integer... progress) |
| 1052 { |
| 1053 Log.d("HTTP", "Progress: " + progress[0].intValue()); |
| 1054 } |
| 1055 } |
| 1056 } |
OLD | NEW |