Home | History | Annotate | Download | only in connectivity
      1 /**
      2  * Copyright (c) 2013, The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *     http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.server.connectivity;
     17 
     18 import android.app.AlarmManager;
     19 import android.app.PendingIntent;
     20 import android.content.BroadcastReceiver;
     21 import android.content.ComponentName;
     22 import android.content.ContentResolver;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.IntentFilter;
     26 import android.content.ServiceConnection;
     27 import android.net.ProxyInfo;
     28 import android.net.Uri;
     29 import android.os.Handler;
     30 import android.os.HandlerThread;
     31 import android.os.IBinder;
     32 import android.os.RemoteException;
     33 import android.os.ServiceManager;
     34 import android.os.SystemClock;
     35 import android.os.SystemProperties;
     36 import android.provider.Settings;
     37 import android.util.Log;
     38 
     39 import com.android.internal.annotations.GuardedBy;
     40 import com.android.net.IProxyCallback;
     41 import com.android.net.IProxyPortListener;
     42 import com.android.net.IProxyService;
     43 
     44 import libcore.io.Streams;
     45 
     46 import java.io.ByteArrayOutputStream;
     47 import java.io.IOException;
     48 import java.net.URL;
     49 import java.net.URLConnection;
     50 
     51 /**
     52  * @hide
     53  */
     54 public class PacManager {
     55     public static final String PAC_PACKAGE = "com.android.pacprocessor";
     56     public static final String PAC_SERVICE = "com.android.pacprocessor.PacService";
     57     public static final String PAC_SERVICE_NAME = "com.android.net.IProxyService";
     58 
     59     public static final String PROXY_PACKAGE = "com.android.proxyhandler";
     60     public static final String PROXY_SERVICE = "com.android.proxyhandler.ProxyService";
     61 
     62     private static final String TAG = "PacManager";
     63 
     64     private static final String ACTION_PAC_REFRESH = "android.net.proxy.PAC_REFRESH";
     65 
     66     private static final String DEFAULT_DELAYS = "8 32 120 14400 43200";
     67     private static final int DELAY_1 = 0;
     68     private static final int DELAY_4 = 3;
     69     private static final int DELAY_LONG = 4;
     70     private static final long MAX_PAC_SIZE = 20 * 1000 * 1000;
     71 
     72     /** Keep these values up-to-date with ProxyService.java */
     73     public static final String KEY_PROXY = "keyProxy";
     74     private String mCurrentPac;
     75     @GuardedBy("mProxyLock")
     76     private Uri mPacUrl = Uri.EMPTY;
     77 
     78     private AlarmManager mAlarmManager;
     79     @GuardedBy("mProxyLock")
     80     private IProxyService mProxyService;
     81     private PendingIntent mPacRefreshIntent;
     82     private ServiceConnection mConnection;
     83     private ServiceConnection mProxyConnection;
     84     private Context mContext;
     85 
     86     private int mCurrentDelay;
     87     private int mLastPort;
     88 
     89     private boolean mHasSentBroadcast;
     90     private boolean mHasDownloaded;
     91 
     92     private Handler mConnectivityHandler;
     93     private int mProxyMessage;
     94 
     95     /**
     96      * Used for locking when setting mProxyService and all references to mPacUrl or mCurrentPac.
     97      */
     98     private final Object mProxyLock = new Object();
     99 
    100     private Runnable mPacDownloader = new Runnable() {
    101         @Override
    102         public void run() {
    103             String file;
    104             synchronized (mProxyLock) {
    105                 if (Uri.EMPTY.equals(mPacUrl)) return;
    106                 try {
    107                     file = get(mPacUrl);
    108                 } catch (IOException ioe) {
    109                     file = null;
    110                     Log.w(TAG, "Failed to load PAC file: " + ioe);
    111                 }
    112             }
    113             if (file != null) {
    114                 synchronized (mProxyLock) {
    115                     if (!file.equals(mCurrentPac)) {
    116                         setCurrentProxyScript(file);
    117                     }
    118                 }
    119                 mHasDownloaded = true;
    120                 sendProxyIfNeeded();
    121                 longSchedule();
    122             } else {
    123                 reschedule();
    124             }
    125         }
    126     };
    127 
    128     private final HandlerThread mNetThread = new HandlerThread("android.pacmanager",
    129             android.os.Process.THREAD_PRIORITY_DEFAULT);
    130     private final Handler mNetThreadHandler;
    131 
    132     class PacRefreshIntentReceiver extends BroadcastReceiver {
    133         public void onReceive(Context context, Intent intent) {
    134             mNetThreadHandler.post(mPacDownloader);
    135         }
    136     }
    137 
    138     public PacManager(Context context, Handler handler, int proxyMessage) {
    139         mContext = context;
    140         mLastPort = -1;
    141         mNetThread.start();
    142         mNetThreadHandler = new Handler(mNetThread.getLooper());
    143 
    144         mPacRefreshIntent = PendingIntent.getBroadcast(
    145                 context, 0, new Intent(ACTION_PAC_REFRESH), 0);
    146         context.registerReceiver(new PacRefreshIntentReceiver(),
    147                 new IntentFilter(ACTION_PAC_REFRESH));
    148         mConnectivityHandler = handler;
    149         mProxyMessage = proxyMessage;
    150     }
    151 
    152     private AlarmManager getAlarmManager() {
    153         if (mAlarmManager == null) {
    154             mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
    155         }
    156         return mAlarmManager;
    157     }
    158 
    159     /**
    160      * Updates the PAC Manager with current Proxy information. This is called by
    161      * the ConnectivityService directly before a broadcast takes place to allow
    162      * the PacManager to indicate that the broadcast should not be sent and the
    163      * PacManager will trigger a new broadcast when it is ready.
    164      *
    165      * @param proxy Proxy information that is about to be broadcast.
    166      * @return Returns true when the broadcast should not be sent
    167      */
    168     public synchronized boolean setCurrentProxyScriptUrl(ProxyInfo proxy) {
    169         if (!Uri.EMPTY.equals(proxy.getPacFileUrl())) {
    170             if (proxy.getPacFileUrl().equals(mPacUrl) && (proxy.getPort() > 0)) {
    171                 // Allow to send broadcast, nothing to do.
    172                 return false;
    173             }
    174             synchronized (mProxyLock) {
    175                 mPacUrl = proxy.getPacFileUrl();
    176             }
    177             mCurrentDelay = DELAY_1;
    178             mHasSentBroadcast = false;
    179             mHasDownloaded = false;
    180             getAlarmManager().cancel(mPacRefreshIntent);
    181             bind();
    182             return true;
    183         } else {
    184             getAlarmManager().cancel(mPacRefreshIntent);
    185             synchronized (mProxyLock) {
    186                 mPacUrl = Uri.EMPTY;
    187                 mCurrentPac = null;
    188                 if (mProxyService != null) {
    189                     try {
    190                         mProxyService.stopPacSystem();
    191                     } catch (RemoteException e) {
    192                         Log.w(TAG, "Failed to stop PAC service", e);
    193                     } finally {
    194                         unbind();
    195                     }
    196                 }
    197             }
    198             return false;
    199         }
    200     }
    201 
    202     /**
    203      * Does a post and reports back the status code.
    204      *
    205      * @throws IOException
    206      */
    207     private static String get(Uri pacUri) throws IOException {
    208         URL url = new URL(pacUri.toString());
    209         URLConnection urlConnection = url.openConnection(java.net.Proxy.NO_PROXY);
    210         long contentLength = -1;
    211         try {
    212             contentLength = Long.parseLong(urlConnection.getHeaderField("Content-Length"));
    213         } catch (NumberFormatException e) {
    214             // Ignore
    215         }
    216         if (contentLength > MAX_PAC_SIZE) {
    217             throw new IOException("PAC too big: " + contentLength + " bytes");
    218         }
    219         ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    220         byte[] buffer = new byte[1024];
    221         int count;
    222         while ((count = urlConnection.getInputStream().read(buffer)) != -1) {
    223             bytes.write(buffer, 0, count);
    224             if (bytes.size() > MAX_PAC_SIZE) {
    225                 throw new IOException("PAC too big");
    226             }
    227         }
    228         return bytes.toString();
    229     }
    230 
    231     private int getNextDelay(int currentDelay) {
    232        if (++currentDelay > DELAY_4) {
    233            return DELAY_4;
    234        }
    235        return currentDelay;
    236     }
    237 
    238     private void longSchedule() {
    239         mCurrentDelay = DELAY_1;
    240         setDownloadIn(DELAY_LONG);
    241     }
    242 
    243     private void reschedule() {
    244         mCurrentDelay = getNextDelay(mCurrentDelay);
    245         setDownloadIn(mCurrentDelay);
    246     }
    247 
    248     private String getPacChangeDelay() {
    249         final ContentResolver cr = mContext.getContentResolver();
    250 
    251         /** Check system properties for the default value then use secure settings value, if any. */
    252         String defaultDelay = SystemProperties.get(
    253                 "conn." + Settings.Global.PAC_CHANGE_DELAY,
    254                 DEFAULT_DELAYS);
    255         String val = Settings.Global.getString(cr, Settings.Global.PAC_CHANGE_DELAY);
    256         return (val == null) ? defaultDelay : val;
    257     }
    258 
    259     private long getDownloadDelay(int delayIndex) {
    260         String[] list = getPacChangeDelay().split(" ");
    261         if (delayIndex < list.length) {
    262             return Long.parseLong(list[delayIndex]);
    263         }
    264         return 0;
    265     }
    266 
    267     private void setDownloadIn(int delayIndex) {
    268         long delay = getDownloadDelay(delayIndex);
    269         long timeTillTrigger = 1000 * delay + SystemClock.elapsedRealtime();
    270         getAlarmManager().set(AlarmManager.ELAPSED_REALTIME, timeTillTrigger, mPacRefreshIntent);
    271     }
    272 
    273     private boolean setCurrentProxyScript(String script) {
    274         if (mProxyService == null) {
    275             Log.e(TAG, "setCurrentProxyScript: no proxy service");
    276             return false;
    277         }
    278         try {
    279             mProxyService.setPacFile(script);
    280             mCurrentPac = script;
    281         } catch (RemoteException e) {
    282             Log.e(TAG, "Unable to set PAC file", e);
    283         }
    284         return true;
    285     }
    286 
    287     private void bind() {
    288         if (mContext == null) {
    289             Log.e(TAG, "No context for binding");
    290             return;
    291         }
    292         Intent intent = new Intent();
    293         intent.setClassName(PAC_PACKAGE, PAC_SERVICE);
    294         if ((mProxyConnection != null) && (mConnection != null)) {
    295             // Already bound no need to bind again, just download the new file.
    296             mNetThreadHandler.post(mPacDownloader);
    297             return;
    298         }
    299         mConnection = new ServiceConnection() {
    300             @Override
    301             public void onServiceDisconnected(ComponentName component) {
    302                 synchronized (mProxyLock) {
    303                     mProxyService = null;
    304                 }
    305             }
    306 
    307             @Override
    308             public void onServiceConnected(ComponentName component, IBinder binder) {
    309                 synchronized (mProxyLock) {
    310                     try {
    311                         Log.d(TAG, "Adding service " + PAC_SERVICE_NAME + " "
    312                                 + binder.getInterfaceDescriptor());
    313                     } catch (RemoteException e1) {
    314                         Log.e(TAG, "Remote Exception", e1);
    315                     }
    316                     ServiceManager.addService(PAC_SERVICE_NAME, binder);
    317                     mProxyService = IProxyService.Stub.asInterface(binder);
    318                     if (mProxyService == null) {
    319                         Log.e(TAG, "No proxy service");
    320                     } else {
    321                         try {
    322                             mProxyService.startPacSystem();
    323                         } catch (RemoteException e) {
    324                             Log.e(TAG, "Unable to reach ProxyService - PAC will not be started", e);
    325                         }
    326                         mNetThreadHandler.post(mPacDownloader);
    327                     }
    328                 }
    329             }
    330         };
    331         mContext.bindService(intent, mConnection,
    332                 Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_VISIBLE);
    333 
    334         intent = new Intent();
    335         intent.setClassName(PROXY_PACKAGE, PROXY_SERVICE);
    336         mProxyConnection = new ServiceConnection() {
    337             @Override
    338             public void onServiceDisconnected(ComponentName component) {
    339             }
    340 
    341             @Override
    342             public void onServiceConnected(ComponentName component, IBinder binder) {
    343                 IProxyCallback callbackService = IProxyCallback.Stub.asInterface(binder);
    344                 if (callbackService != null) {
    345                     try {
    346                         callbackService.getProxyPort(new IProxyPortListener.Stub() {
    347                             @Override
    348                             public void setProxyPort(int port) throws RemoteException {
    349                                 if (mLastPort != -1) {
    350                                     // Always need to send if port changed
    351                                     mHasSentBroadcast = false;
    352                                 }
    353                                 mLastPort = port;
    354                                 if (port != -1) {
    355                                     Log.d(TAG, "Local proxy is bound on " + port);
    356                                     sendProxyIfNeeded();
    357                                 } else {
    358                                     Log.e(TAG, "Received invalid port from Local Proxy,"
    359                                             + " PAC will not be operational");
    360                                 }
    361                             }
    362                         });
    363                     } catch (RemoteException e) {
    364                         e.printStackTrace();
    365                     }
    366                 }
    367             }
    368         };
    369         mContext.bindService(intent, mProxyConnection,
    370                 Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_VISIBLE);
    371     }
    372 
    373     private void unbind() {
    374         if (mConnection != null) {
    375             mContext.unbindService(mConnection);
    376             mConnection = null;
    377         }
    378         if (mProxyConnection != null) {
    379             mContext.unbindService(mProxyConnection);
    380             mProxyConnection = null;
    381         }
    382         mProxyService = null;
    383         mLastPort = -1;
    384     }
    385 
    386     private void sendPacBroadcast(ProxyInfo proxy) {
    387         mConnectivityHandler.sendMessage(mConnectivityHandler.obtainMessage(mProxyMessage, proxy));
    388     }
    389 
    390     private synchronized void sendProxyIfNeeded() {
    391         if (!mHasDownloaded || (mLastPort == -1)) {
    392             return;
    393         }
    394         if (!mHasSentBroadcast) {
    395             sendPacBroadcast(new ProxyInfo(mPacUrl, mLastPort));
    396             mHasSentBroadcast = true;
    397         }
    398     }
    399 }
    400