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