PacManager.java revision 9158825f9c41869689d6b1786d7c7aa8bdd524ce
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.Proxy;
28import android.net.ProxyProperties;
29import android.os.Binder;
30import android.os.Handler;
31import android.os.IBinder;
32import android.os.RemoteException;
33import android.os.ServiceManager;
34import android.os.SystemClock;
35import android.os.SystemProperties;
36import android.os.UserHandle;
37import android.provider.Settings;
38import android.text.TextUtils;
39import android.util.Log;
40
41import com.android.internal.annotations.GuardedBy;
42import com.android.net.IProxyCallback;
43import com.android.net.IProxyPortListener;
44import com.android.net.IProxyService;
45import com.android.server.IoThread;
46
47import libcore.io.Streams;
48
49import java.io.IOException;
50import java.net.URL;
51import java.net.URLConnection;
52
53/**
54 * @hide
55 */
56public 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