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