• 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.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.ProxyInfo;
28 import android.net.Uri;
29 import android.os.Handler;
30 import android.os.HandlerThread;
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.provider.Settings;
37 import android.util.Log;
38 
39 import com.android.internal.annotations.GuardedBy;
40 import com.android.net.IProxyCallback;
41 import com.android.net.IProxyPortListener;
42 import com.android.net.IProxyService;
43 
44 import libcore.io.Streams;
45 
46 import java.io.ByteArrayOutputStream;
47 import java.io.IOException;
48 import java.net.URL;
49 import java.net.URLConnection;
50 
51 /**
52  * @hide
53  */
54 public 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 {
onReceive(Context context, Intent intent)133         public void onReceive(Context context, Intent intent) {
134             mNetThreadHandler.post(mPacDownloader);
135         }
136     }
137 
PacManager(Context context, Handler handler, int proxyMessage)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 
getAlarmManager()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      */
setCurrentProxyScriptUrl(ProxyInfo proxy)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      */
get(Uri pacUri)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 
getNextDelay(int currentDelay)231     private int getNextDelay(int currentDelay) {
232        if (++currentDelay > DELAY_4) {
233            return DELAY_4;
234        }
235        return currentDelay;
236     }
237 
longSchedule()238     private void longSchedule() {
239         mCurrentDelay = DELAY_1;
240         setDownloadIn(DELAY_LONG);
241     }
242 
reschedule()243     private void reschedule() {
244         mCurrentDelay = getNextDelay(mCurrentDelay);
245         setDownloadIn(mCurrentDelay);
246     }
247 
getPacChangeDelay()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 
getDownloadDelay(int delayIndex)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 
setDownloadIn(int delayIndex)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 
setCurrentProxyScript(String script)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 
bind()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 
unbind()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 
sendPacBroadcast(ProxyInfo proxy)386     private void sendPacBroadcast(ProxyInfo proxy) {
387         mConnectivityHandler.sendMessage(mConnectivityHandler.obtainMessage(mProxyMessage, proxy));
388     }
389 
sendProxyIfNeeded()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