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