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