| Index: src/org/adblockplus/sbrowser/contentblocker/engine/Engine.java | 
| =================================================================== | 
| --- a/src/org/adblockplus/sbrowser/contentblocker/engine/Engine.java | 
| +++ b/src/org/adblockplus/sbrowser/contentblocker/engine/Engine.java | 
| @@ -21,22 +21,27 @@ import java.io.BufferedReader; | 
| import java.io.BufferedWriter; | 
| import java.io.File; | 
| import java.io.FileOutputStream; | 
| import java.io.IOException; | 
| import java.io.InputStream; | 
| import java.io.InputStreamReader; | 
| import java.io.OutputStreamWriter; | 
| import java.io.Writer; | 
| +import java.net.URI; | 
| +import java.net.URISyntaxException; | 
| import java.net.URL; | 
| import java.net.URLEncoder; | 
| import java.util.ArrayList; | 
| +import java.util.Collections; | 
| import java.util.HashMap; | 
| import java.util.List; | 
| import java.util.Map; | 
| +import java.util.Set; | 
| +import java.util.TreeSet; | 
| import java.util.concurrent.LinkedBlockingQueue; | 
| import java.util.concurrent.TimeUnit; | 
| import java.util.concurrent.locks.ReentrantLock; | 
| import java.util.regex.Pattern; | 
|  | 
| import org.adblockplus.adblockplussbrowser.R; | 
|  | 
| import android.content.Context; | 
| @@ -72,17 +77,17 @@ public final class Engine | 
| public static final String USER_EXCEPTIONS_TITLE = "__exceptions"; | 
|  | 
| public static final String ACTION_OPEN_SETTINGS = "com.samsung.android.sbrowser.contentBlocker.ACTION_SETTING"; | 
| public static final String ACTION_UPDATE = "com.samsung.android.sbrowser.contentBlocker.ACTION_UPDATE"; | 
| public static final String EASYLIST_URL = "https://easylist-downloads.adblockplus.org/easylist.txt"; | 
|  | 
| public static final String SUBSCRIPTIONS_EXCEPTIONSURL = "subscriptions_exceptionsurl"; | 
|  | 
| -  private static final String URL_ENCODE_CHARSET = "UTF-8"; | 
| +  public static final String CHARSET_UTF_8 = "UTF-8"; | 
| private static final String PREFS_KEY_PREVIOUS_VERSION = "key_previous_version"; | 
|  | 
| // The value below specifies an interval of [x, 2*x[, where x = | 
| // INITIAL_UPDATE_CHECK_DELAY_SECONDS | 
| private static final long INITIAL_UPDATE_CHECK_DELAY_SECONDS = 5; | 
| private static final long UPDATE_CHECK_INTERVAL_MINUTES = 30; | 
| private static final long BROADCAST_COMBINATION_DELAY_MILLIS = 2500; | 
|  | 
| @@ -148,30 +153,30 @@ public final class Engine | 
| .queryIntentActivities(new Intent(ACTION_OPEN_SETTINGS), 0).size() > 0; | 
| } | 
| catch (final Throwable t) | 
| { | 
| return false; | 
| } | 
| } | 
|  | 
| -  void requestUpdateBroadcast() | 
| +  public void requestUpdateBroadcast() | 
| { | 
| this.lock(); | 
| try | 
| { | 
| this.nextUpdateBroadcast = System.currentTimeMillis() + BROADCAST_COMBINATION_DELAY_MILLIS; | 
| } | 
| finally | 
| { | 
| this.unlock(); | 
| } | 
| } | 
|  | 
| -  private void sendUpdateBroadcast() | 
| +  private void writeFileAndSendUpdateBroadcast() | 
| { | 
| createAndWriteFile(); | 
|  | 
| runOnUiThread(new Runnable() | 
| { | 
| @Override | 
| public void run() | 
| { | 
| @@ -237,23 +242,24 @@ public final class Engine | 
| } | 
|  | 
| void downloadFinished(final String id, final int responseCode, final String response, | 
| final Map<String, String> headers) | 
| { | 
| this.engineEvents.add(new DownloadFinishedEvent(id, responseCode, response, headers)); | 
| } | 
|  | 
| -  public void createAndWriteFile() | 
| +  private void createAndWriteFile() | 
| { | 
| this.lock(); | 
| try | 
| { | 
| Log.d(TAG, "Writing filters..."); | 
| final File filterFile = this.subscriptions.createAndWriteFile(); | 
| +      writeWhitelistedWebsites(this.serviceContext, filterFile); | 
|  | 
| final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.serviceContext); | 
| final String key = this.serviceContext.getString(R.string.key_cached_filter_path); | 
| prefs.edit().putString(key, filterFile.getAbsolutePath()).commit(); | 
|  | 
| Log.d(TAG, "Cleaning up cache..."); | 
| final File dummyFile = getDummyFilterFile(this.serviceContext); | 
| final File[] cacheDirFiles = getFilterCacheDir(this.serviceContext).listFiles(); | 
| @@ -420,43 +426,43 @@ public final class Engine | 
|  | 
| Log.d(TAG, "Added " + additional + " additional default/built-in subscriptions"); | 
| engine.subscriptions.persistSubscriptions(); | 
| } | 
|  | 
| final File cachedFilterFile = getCachedFilterFile(context); | 
| if (cachedFilterFile == null || !cachedFilterFile.exists()) | 
| { | 
| -      engine.sendUpdateBroadcast(); | 
| +      engine.writeFileAndSendUpdateBroadcast(); | 
| } | 
|  | 
| engine.handlerThread = new Thread(new EventHandler(engine)); | 
| engine.handlerThread.setDaemon(true); | 
| engine.handlerThread.start(); | 
|  | 
| engine.downloader = Downloader.create(context, engine); | 
|  | 
| return engine; | 
| } | 
|  | 
| public static String readFileAsString(InputStream instream) throws IOException | 
| { | 
| final StringBuilder sb = new StringBuilder(); | 
| -    final BufferedReader r = new BufferedReader(new InputStreamReader(instream, "UTF-8")); | 
| +    final BufferedReader r = new BufferedReader(new InputStreamReader(instream, CHARSET_UTF_8)); | 
| for (int ch = r.read(); ch != -1; ch = r.read()) | 
| { | 
| sb.append((char) ch); | 
| } | 
| return sb.toString(); | 
| } | 
|  | 
| public static List<String> readLines(InputStream instream) throws IOException | 
| { | 
| final ArrayList<String> list = new ArrayList<String>(); | 
| -    final BufferedReader r = new BufferedReader(new InputStreamReader(instream, "UTF-8")); | 
| +    final BufferedReader r = new BufferedReader(new InputStreamReader(instream, CHARSET_UTF_8)); | 
| for (String line = r.readLine(); line != null; line = r.readLine()) | 
| { | 
| list.add(line); | 
| } | 
| return list; | 
| } | 
|  | 
| public static File getOrCreateCachedFilterFile(Context context) throws IOException | 
| @@ -470,17 +476,17 @@ public final class Engine | 
|  | 
| Log.d(TAG, "Cached filter file not found. Using dummy filter file"); | 
| final File dummyFilterFile = getDummyFilterFile(context); | 
| if (!dummyFilterFile.exists()) | 
| { | 
| Log.d(TAG, "Creating dummy filter file..."); | 
| dummyFilterFile.getParentFile().mkdirs(); | 
| final BufferedWriter writer = new BufferedWriter( | 
| -          new OutputStreamWriter(new FileOutputStream(dummyFilterFile), "UTF-8")); | 
| +          new OutputStreamWriter(new FileOutputStream(dummyFilterFile), CHARSET_UTF_8)); | 
| try | 
| { | 
| writeFilterHeaders(writer); | 
| } | 
| finally | 
| { | 
| writer.close(); | 
| } | 
| @@ -489,16 +495,52 @@ public final class Engine | 
| } | 
|  | 
| public static void writeFilterHeaders(Writer writer) throws IOException | 
| { | 
| writer.write("[Adblock Plus 2.0]\n"); | 
| writer.write("! This file was automatically created.\n"); | 
| } | 
|  | 
| +  private static void writeWhitelistedWebsites(Context context, File filterFile) throws IOException | 
| +  { | 
| +    Log.d(TAG, "Writing whitelisted websites..."); | 
| +    final SharedPreferences prefs = | 
| +        PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()); | 
| +    final String key = context.getString(R.string.key_whitelisted_websites); | 
| + | 
| +    final Set<String> whitelistedWebsites = new TreeSet<String>(); | 
| +    whitelistedWebsites.addAll(prefs.getStringSet(key, Collections.<String>emptySet())); | 
| + | 
| +    final BufferedWriter w = new BufferedWriter( | 
| +        new OutputStreamWriter(new FileOutputStream(filterFile, true), CHARSET_UTF_8)); | 
| +    try | 
| +    { | 
| +      for (final String url : whitelistedWebsites) | 
| +      { | 
| +        try | 
| +        { | 
| +          final URI uri = new URI(url); | 
| +          final String host = uri.getHost() != null ? uri.getHost() : uri.getPath(); | 
| +          w.write("@@||" + host + "^$document"); | 
| +          w.write('\n'); | 
| +        } | 
| +        catch (URISyntaxException e) | 
| +        { | 
| +          Log.w(TAG, "Failed to parse whitelisted website: " + url); | 
| +          continue; | 
| +        } | 
| +      } | 
| +    } | 
| +    finally | 
| +    { | 
| +      w.close(); | 
| +    } | 
| +  } | 
| + | 
| private static File getCachedFilterFile(Context context) | 
| { | 
| final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); | 
| final String cachedFilterPath = prefs.getString(context.getString(R.string.key_cached_filter_path), null); | 
| if (cachedFilterPath != null) | 
| { | 
| return new File(cachedFilterPath); | 
| } | 
| @@ -531,27 +573,27 @@ public final class Engine | 
| sb.append('&'); | 
| } | 
| else | 
| { | 
| sb.append('?'); | 
| } | 
|  | 
| sb.append("addonName="); | 
| -    sb.append(URLEncoder.encode(this.appInfo.addonName, URL_ENCODE_CHARSET)); | 
| +    sb.append(URLEncoder.encode(this.appInfo.addonName, CHARSET_UTF_8)); | 
| sb.append("&addonVersion="); | 
| -    sb.append(URLEncoder.encode(this.appInfo.addonVersion, URL_ENCODE_CHARSET)); | 
| +    sb.append(URLEncoder.encode(this.appInfo.addonVersion, CHARSET_UTF_8)); | 
| sb.append("&application="); | 
| -    sb.append(URLEncoder.encode(this.appInfo.application, URL_ENCODE_CHARSET)); | 
| +    sb.append(URLEncoder.encode(this.appInfo.application, CHARSET_UTF_8)); | 
| sb.append("&applicationVersion="); | 
| -    sb.append(URLEncoder.encode(this.appInfo.applicationVersion, URL_ENCODE_CHARSET)); | 
| +    sb.append(URLEncoder.encode(this.appInfo.applicationVersion, CHARSET_UTF_8)); | 
| sb.append("&platform="); | 
| -    sb.append(URLEncoder.encode(this.appInfo.platform, URL_ENCODE_CHARSET)); | 
| +    sb.append(URLEncoder.encode(this.appInfo.platform, CHARSET_UTF_8)); | 
| sb.append("&platformVersion="); | 
| -    sb.append(URLEncoder.encode(this.appInfo.platformVersion, URL_ENCODE_CHARSET)); | 
| +    sb.append(URLEncoder.encode(this.appInfo.platformVersion, CHARSET_UTF_8)); | 
| sb.append("&lastVersion="); | 
| sb.append(sub.getVersion()); | 
| sb.append("&downloadCount="); | 
| final long downloadCount = sub.getDownloadCount(); | 
| if (downloadCount < 5) | 
| { | 
| sb.append(downloadCount); | 
| } | 
| @@ -621,17 +663,17 @@ public final class Engine | 
|  | 
| this.engine.subscriptions.checkForUpdates(); | 
| } | 
|  | 
| if (currentTime > this.engine.nextUpdateBroadcast) | 
| { | 
| this.engine.nextUpdateBroadcast = Long.MAX_VALUE; | 
| Log.d(TAG, "Sending update broadcast"); | 
| -              this.engine.sendUpdateBroadcast(); | 
| +              this.engine.writeFileAndSendUpdateBroadcast(); | 
| } | 
| } | 
| finally | 
| { | 
| engine.unlock(); | 
| } | 
| } | 
| catch (final InterruptedException e) | 
|  |