| Index: src/org/adblockplus/android/configurators/ManualProxyConfigurator.java | 
| diff --git a/src/org/adblockplus/android/configurators/ManualProxyConfigurator.java b/src/org/adblockplus/android/configurators/ManualProxyConfigurator.java | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..4b87c5d7978d21e9ea10ac100c8731c5b6a2ab18 | 
| --- /dev/null | 
| +++ b/src/org/adblockplus/android/configurators/ManualProxyConfigurator.java | 
| @@ -0,0 +1,271 @@ | 
| +/* | 
| + * This file is part of Adblock Plus <http://adblockplus.org/>, | 
| + * Copyright (C) 2006-2014 Eyeo GmbH | 
| + * | 
| + * Adblock Plus is free software: you can redistribute it and/or modify | 
| + * it under the terms of the GNU General Public License version 3 as | 
| + * published by the Free Software Foundation. | 
| + * | 
| + * Adblock Plus 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 Adblock Plus.  If not, see <http://www.gnu.org/licenses/>. | 
| + */ | 
| + | 
| +package org.adblockplus.android.configurators; | 
| + | 
| +import java.net.InetAddress; | 
| +import java.util.concurrent.Semaphore; | 
| +import java.util.concurrent.locks.ReentrantLock; | 
| + | 
| +import org.adblockplus.android.BridgeCommand; | 
| +import org.adblockplus.android.ConfigurationActivity; | 
| +import org.adblockplus.android.ProxyService; | 
| +import org.adblockplus.android.R; | 
| +import org.adblockplus.android.compat.ProxyProperties; | 
| +import org.adblockplus.brazil.RequestHandler; | 
| + | 
| +import android.app.NotificationManager; | 
| +import android.app.PendingIntent; | 
| +import android.content.Context; | 
| +import android.content.Intent; | 
| +import android.os.Handler; | 
| +import android.support.v4.app.NotificationCompat; | 
| + | 
| +/** | 
| + * A dummy registrator only holding callbacks and checks. | 
| + * | 
| + * @author rjeschke | 
| + */ | 
| +public class ManualProxyConfigurator implements ProxyConfigurator | 
| +{ | 
| +  final Context context; | 
| +  private ProxyProperties proxyProperties = null; | 
| +  private static final int NOTRAFFIC_NOTIFICATION_ID = R.string.proxysettings_name; | 
| +  private static final int NO_TRAFFIC_TIMEOUT = 5 * 60 * 1000; // 5 minutes | 
| +  private volatile boolean isRegistered = false; | 
| +  private NoTrafficWorker noTrafficWorker = null; | 
| +  private ReentrantLock noTrafficAccessLock = new ReentrantLock(); | 
| +  private final Handler uiHandler; | 
| + | 
| +  public ManualProxyConfigurator(final Context context) | 
| +  { | 
| +    this.uiHandler = new Handler(); | 
| +    this.context = context; | 
| +  } | 
| + | 
| +  @Override | 
| +  public boolean initialize() | 
| +  { | 
| +    return true; | 
| +  } | 
| + | 
| +  @Override | 
| +  public boolean registerProxy(final InetAddress address, final int port) | 
| +  { | 
| +    this.proxyProperties = new ProxyProperties(address.getHostName(), port, ""); | 
| +    this.startNoTrafficCheck(); | 
| +    return true; | 
| +  } | 
| + | 
| +  @Override | 
| +  public void unregisterProxy() | 
| +  { | 
| +    this.isRegistered = false; | 
| +    this.abortNoTrafficCheck(); | 
| +  } | 
| + | 
| +  @Override | 
| +  public void shutdown() | 
| +  { | 
| +    this.removeErrorNotification(); | 
| +  } | 
| + | 
| +  @Override | 
| +  public ProxyRegistrationType getType() | 
| +  { | 
| +    return ProxyRegistrationType.MANUAL; | 
| +  } | 
| + | 
| +  @Override | 
| +  public boolean isRegistered() | 
| +  { | 
| +    return this.isRegistered; | 
| +  } | 
| + | 
| +  @Override | 
| +  public boolean isSticky() | 
| +  { | 
| +    return true; | 
| +  } | 
| + | 
| +  @Override | 
| +  public String toString() | 
| +  { | 
| +    return "[ProxyConfigurator: " + this.getType() + "]"; | 
| +  } | 
| + | 
| +  private void removeErrorNotification() | 
| +  { | 
| +    final NotificationManager notificationManager = | 
| +        (NotificationManager) this.context.getSystemService(Context.NOTIFICATION_SERVICE); | 
| +    notificationManager.cancel(NOTRAFFIC_NOTIFICATION_ID); | 
| +  } | 
| + | 
| +  private void startNoTrafficCheck() | 
| +  { | 
| +    this.noTrafficAccessLock.lock(); | 
| +    try | 
| +    { | 
| +      if (this.noTrafficWorker == null) | 
| +      { | 
| +        this.noTrafficWorker = new NoTrafficWorker(this); | 
| +        final Thread t = new Thread(this.noTrafficWorker); | 
| +        t.setDaemon(true); | 
| +        t.start(); | 
| +      } | 
| +    } | 
| +    finally | 
| +    { | 
| +      this.noTrafficAccessLock.unlock(); | 
| +    } | 
| +  } | 
| + | 
| +  private void abortNoTrafficCheck() | 
| +  { | 
| +    this.noTrafficAccessLock.lock(); | 
| +    try | 
| +    { | 
| +      if (this.noTrafficWorker != null) | 
| +      { | 
| +        this.noTrafficWorker.stop(); | 
| +      } | 
| +    } | 
| +    finally | 
| +    { | 
| +      this.noTrafficWorker = null; | 
| +      this.noTrafficAccessLock.unlock(); | 
| +    } | 
| +  } | 
| + | 
| +  private synchronized void trafficReceived() | 
| +  { | 
| +    this.isRegistered = true; | 
| +    this.abortNoTrafficCheck(); | 
| + | 
| +    this.uiHandler.post(new Runnable() | 
| +    { | 
| +      @Override | 
| +      public void run() | 
| +      { | 
| +        ManualProxyConfigurator.this.removeErrorNotification(); | 
| +        ProxyService.sendBridgeCommand(ManualProxyConfigurator.this.context, BridgeCommand.STATE_CHANGED); | 
| +      } | 
| +    }); | 
| +  } | 
| + | 
| +  private synchronized void noTrafficReceived() | 
| +  { | 
| +    this.isRegistered = false; | 
| +    this.abortNoTrafficCheck(); | 
| + | 
| +    this.uiHandler.post(new Runnable() | 
| +    { | 
| +      @Override | 
| +      public void run() | 
| +      { | 
| +        final Context context = ManualProxyConfigurator.this.context; | 
| +        // Show warning notification | 
| +        final Intent intent = | 
| +            new Intent(context, ConfigurationActivity.class) | 
| +                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) | 
| +                .putExtra("port", ManualProxyConfigurator.this.proxyProperties.getPort()); | 
| + | 
| +        final PendingIntent contentIntent = | 
| +            PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); | 
| + | 
| +        final NotificationCompat.Builder builder = | 
| +            new NotificationCompat.Builder(context) | 
| +                .setSmallIcon(R.drawable.ic_stat_warning) | 
| +                .setWhen(System.currentTimeMillis()) | 
| +                .setAutoCancel(true) | 
| +                .setContentIntent(contentIntent) | 
| +                .setContentTitle(context.getText(R.string.app_name)) | 
| +                .setContentText(context.getText(R.string.notif_notraffic)); | 
| + | 
| +        final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); | 
| +        notificationManager.notify(NOTRAFFIC_NOTIFICATION_ID, builder.getNotification()); | 
| + | 
| +        ProxyService.sendBridgeCommand(context, BridgeCommand.STATE_CHANGED); | 
| +      } | 
| +    }); | 
| +  } | 
| + | 
| +  private static class NoTrafficWorker implements Runnable | 
| +  { | 
| +    private volatile boolean running = true; | 
| +    private final Semaphore finished = new Semaphore(1); | 
| +    private final ManualProxyConfigurator manualProxyConfigurator; | 
| + | 
| +    public NoTrafficWorker(final ManualProxyConfigurator manualProxyConfigurator) | 
| +    { | 
| +      this.manualProxyConfigurator = manualProxyConfigurator; | 
| +      this.finished.acquireUninterruptibly(); | 
| +    } | 
| + | 
| +    @Override | 
| +    public void run() | 
| +    { | 
| +      try | 
| +      { | 
| +        final long endTime = System.currentTimeMillis() + NO_TRAFFIC_TIMEOUT; | 
| + | 
| +        final long blockedStart = RequestHandler.getBlockedRequestCount(); | 
| +        final long unblockedStart = RequestHandler.getUnblockedRequestCount(); | 
| + | 
| +        while (this.running) | 
| +        { | 
| +          try | 
| +          { | 
| +            if (System.currentTimeMillis() >= endTime) | 
| +            { | 
| +              this.running = false; | 
| +              this.manualProxyConfigurator.noTrafficReceived(); | 
| +              break; | 
| +            } | 
| + | 
| +            if (RequestHandler.getBlockedRequestCount() != blockedStart | 
| +                || RequestHandler.getUnblockedRequestCount() != unblockedStart) | 
| +            { | 
| +              this.running = false; | 
| +              this.manualProxyConfigurator.trafficReceived(); | 
| +              break; | 
| +            } | 
| + | 
| +            Thread.sleep(100); | 
| +          } | 
| +          catch (Throwable t) | 
| +          { | 
| +            // Swallow everything to keep this thread alive at all cost | 
| +          } | 
| +        } | 
| +      } | 
| +      finally | 
| +      { | 
| +        this.finished.release(); | 
| +      } | 
| +    } | 
| + | 
| +    public synchronized void stop() | 
| +    { | 
| +      if (this.running) | 
| +      { | 
| +        this.running = false; | 
| +        this.finished.acquireUninterruptibly(); | 
| +      } | 
| +    } | 
| +  } | 
| +} | 
|  |