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