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