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 */ 16package com.android.server.connectivity; 17 18import android.app.AlarmManager; 19import android.app.PendingIntent; 20import android.content.BroadcastReceiver; 21import android.content.ComponentName; 22import android.content.ContentResolver; 23import android.content.Context; 24import android.content.Intent; 25import android.content.IntentFilter; 26import android.content.ServiceConnection; 27import android.net.ProxyInfo; 28import android.net.Uri; 29import android.os.Handler; 30import android.os.IBinder; 31import android.os.RemoteException; 32import android.os.ServiceManager; 33import android.os.SystemClock; 34import android.os.SystemProperties; 35import android.provider.Settings; 36import android.util.Log; 37 38import com.android.internal.annotations.GuardedBy; 39import com.android.net.IProxyCallback; 40import com.android.net.IProxyPortListener; 41import com.android.net.IProxyService; 42import com.android.server.IoThread; 43 44import libcore.io.Streams; 45 46import java.io.IOException; 47import java.net.URL; 48import java.net.URLConnection; 49 50/** 51 * @hide 52 */ 53public 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 // Already bound no need to bind again. 269 if ((mProxyConnection != null) && (mConnection != null)) { 270 if (mLastPort != -1) { 271 sendPacBroadcast(new ProxyInfo(mPacUrl, mLastPort)); 272 } else { 273 Log.e(TAG, "Received invalid port from Local Proxy," 274 + " PAC will not be operational"); 275 } 276 return; 277 } 278 mConnection = new ServiceConnection() { 279 @Override 280 public void onServiceDisconnected(ComponentName component) { 281 synchronized (mProxyLock) { 282 mProxyService = null; 283 } 284 } 285 286 @Override 287 public void onServiceConnected(ComponentName component, IBinder binder) { 288 synchronized (mProxyLock) { 289 try { 290 Log.d(TAG, "Adding service " + PAC_SERVICE_NAME + " " 291 + binder.getInterfaceDescriptor()); 292 } catch (RemoteException e1) { 293 Log.e(TAG, "Remote Exception", e1); 294 } 295 ServiceManager.addService(PAC_SERVICE_NAME, binder); 296 mProxyService = IProxyService.Stub.asInterface(binder); 297 if (mProxyService == null) { 298 Log.e(TAG, "No proxy service"); 299 } else { 300 try { 301 mProxyService.startPacSystem(); 302 } catch (RemoteException e) { 303 Log.e(TAG, "Unable to reach ProxyService - PAC will not be started", e); 304 } 305 IoThread.getHandler().post(mPacDownloader); 306 } 307 } 308 } 309 }; 310 mContext.bindService(intent, mConnection, 311 Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_VISIBLE); 312 313 intent = new Intent(); 314 intent.setClassName(PROXY_PACKAGE, PROXY_SERVICE); 315 mProxyConnection = new ServiceConnection() { 316 @Override 317 public void onServiceDisconnected(ComponentName component) { 318 } 319 320 @Override 321 public void onServiceConnected(ComponentName component, IBinder binder) { 322 IProxyCallback callbackService = IProxyCallback.Stub.asInterface(binder); 323 if (callbackService != null) { 324 try { 325 callbackService.getProxyPort(new IProxyPortListener.Stub() { 326 @Override 327 public void setProxyPort(int port) throws RemoteException { 328 if (mLastPort != -1) { 329 // Always need to send if port changed 330 mHasSentBroadcast = false; 331 } 332 mLastPort = port; 333 if (port != -1) { 334 Log.d(TAG, "Local proxy is bound on " + port); 335 sendProxyIfNeeded(); 336 } else { 337 Log.e(TAG, "Received invalid port from Local Proxy," 338 + " PAC will not be operational"); 339 } 340 } 341 }); 342 } catch (RemoteException e) { 343 e.printStackTrace(); 344 } 345 } 346 } 347 }; 348 mContext.bindService(intent, mProxyConnection, 349 Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_VISIBLE); 350 } 351 352 private void unbind() { 353 if (mConnection != null) { 354 mContext.unbindService(mConnection); 355 mConnection = null; 356 } 357 if (mProxyConnection != null) { 358 mContext.unbindService(mProxyConnection); 359 mProxyConnection = null; 360 } 361 mProxyService = null; 362 mLastPort = -1; 363 } 364 365 private void sendPacBroadcast(ProxyInfo proxy) { 366 mConnectivityHandler.sendMessage(mConnectivityHandler.obtainMessage(mProxyMessage, proxy)); 367 } 368 369 private synchronized void sendProxyIfNeeded() { 370 if (!mHasDownloaded || (mLastPort == -1)) { 371 return; 372 } 373 if (!mHasSentBroadcast) { 374 sendPacBroadcast(new ProxyInfo(mPacUrl, mLastPort)); 375 mHasSentBroadcast = true; 376 } 377 } 378} 379