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