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